/******************************************************************************/ // SIMPLE INPUT RECORD/PLAYBACK // (c) 2015 Brian Provinciano // // You are free to use this code for your own purposes, no strings attached. // // This is a very basic sample to record and playback button input. // It's most useful when activated on startup, deactivated on shutdown for // global button recording/playback. // // For details on more advanced implementations, see my GDC 2015 session: // -> Automated Testing and Instant Replays in Retro City Rampage // The slides and full video will be available on the GDC Vault at a later date. /******************************************************************************/ /******************************************************************************/ // wrap it so it can be conditionally compiled in. // for example, set INPUTREPLAY_CAN_RECORD to 1 to play the game and record the input, set it to 0 when done // INPUTREPLAY_CAN_RECORD takes priority over INPUTREPLAY_CAN_PLAYBACK #define INPUTREPLAY_CAN_PLAYBACK 1 #define INPUTREPLAY_CAN_RECORD 1 #define INPUTREPLAY_INCLUDED (INPUTREPLAY_CAN_PLAYBACK || INPUTREPLAY_CAN_RECORD) /******************************************************************************/ #if INPUTREPLAY_INCLUDED #define INPUT_BUTTONS_TOTAL 32 // up to 32 #define MAX_REC_LEN 0x8000 // the buffer size for storing RLE compressed button input (x each button) /******************************************************************************/ typedef struct { unsigned short *rledata; unsigned short rlepos; unsigned short datalen; unsigned short currentrun; } ButtonRec; /******************************************************************************/ // if INPUTREPLAY_CAN_RECORD, as soon as this class is instanced, it will automatically record when instanced/created. // statically creating this as a global will blanket the entire play session // // if INPUTREPLAY_CAN_PLAYBACK, playback will begin as soon as LoadFile() is used // class SimpleInputRec { unsigned int m_buttonstate; ButtonRec m_buttons[INPUT_BUTTONS_TOTAL]; bool m_bRecording; unsigned char* m_data; public: SimpleInputRec() : m_buttonstate(0) , m_data(NULL) , m_bRecording(true) { } ~SimpleInputRec() { if(m_data) { #if INPUTREPLAY_CAN_RECORD WriteToFile(); #endif delete[] m_data; } } // run each frame before the game uses the live button input. // when recording, it saves the live input // during playback, it overwrites the live input void Update(bool bForce = false); // to start a playback #if INPUTREPLAY_CAN_PLAYBACK bool LoadFile(KSTR szfilename); #endif // to finish recording #if INPUTREPLAY_CAN_RECORD void WriteToFile(); #endif }; /******************************************************************************/ void SimpleInputRec::Update(bool bForce) { #if INPUTREPLAY_CAN_RECORD if(m_bRecording) { unsigned int newbuttons = nesinput.buttons; // allocate and initialize if(!m_data) { m_data = new unsigned char[INPUT_BUTTONS_TOTAL * MAX_REC_LEN * 2]; unsigned short* dataptr = (unsigned short*)m_data; for(int i=0; i>i)&1; btn.rledata[btn.rlepos++] = (bit<<15) | btn.currentrun; } btn.currentrun = bForce? 0 : 1; } else { ++btn.currentrun; } } m_buttonstate = newbuttons; } #endif #if INPUTREPLAY_CAN_PLAYBACK if(!m_bRecording) { bool bIsRunning = false; for(int i=0; i>15)&1)<> 8, f); } for(int i=0; i