You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			289 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Plaintext
		
	
			
		
		
	
	
			289 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Plaintext
		
	
| <code>/******************************************************************************/</code>
 | |
| <code>// SIMPLE INPUT RECORD/PLAYBACK</code>
 | |
| <code>// (c) 2015 Brian Provinciano</code>
 | |
| <code>//</code>
 | |
| <code>// You are free to use this code for your own purposes, no strings attached.</code>
 | |
| //
 | |
| // 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<INPUT_BUTTONS_TOTAL; ++i)
 | |
| 			{
 | |
| 				ButtonRec& btn = m_buttons[i];
 | |
| 
 | |
| 				btn.rledata = dataptr;
 | |
| 				dataptr += MAX_REC_LEN;
 | |
| 
 | |
| 				btn.rlepos = 0;
 | |
| 				btn.currentrun = 0;
 | |
| 				btn.datalen = MAX_REC_LEN;
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		// write RLE button bit streams
 | |
| 		for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
 | |
| 		{
 | |
| 			ButtonRec& btn = m_buttons[i];
 | |
| 
 | |
| 			if(bForce || (newbuttons&(1<<i)) != (m_buttonstate&(1<<i)) || btn.currentrun==0x7FFF)
 | |
| 			{
 | |
| 				if(btn.currentrun)
 | |
| 				{
 | |
| 					int bit = (m_buttonstate>>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<INPUT_BUTTONS_TOTAL; ++i)
 | |
| 		{
 | |
| 			ButtonRec& btn = m_buttons[i];
 | |
| 			if(btn.rledata)
 | |
| 			{
 | |
| 				bIsRunning = true;
 | |
| 				if(!btn.currentrun && btn.rlepos<btn.datalen)
 | |
| 				{
 | |
| 					unsigned short value = btn.rledata[btn.rlepos++];
 | |
| 					btn.currentrun = value&0x7FFF;
 | |
| 					m_buttonstate &= ~(1<<i);
 | |
| 					m_buttonstate |= ((value>>15)&1)<<i;
 | |
| 					--btn.currentrun;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					if(btn.currentrun) 
 | |
| 					{
 | |
| 						--btn.currentrun;
 | |
| 					}
 | |
| 					else if(btn.rlepos==btn.datalen)
 | |
| 					{
 | |
| 						btn.rledata = NULL;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if(bIsRunning)
 | |
| 		{
 | |
| 			// TODO: this is where you can overwrite the live button state to the prerecorded one
 | |
| 			systeminput.buttons = m_buttonstate;
 | |
| 		}
 | |
| 	}
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /******************************************************************************/
 | |
| 
 | |
| #if INPUTREPLAY_CAN_PLAYBACK
 | |
| bool SimpleInputRec::LoadFile(KSTR szfilename)
 | |
| {
 | |
| 	for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
 | |
| 	{
 | |
| 		ButtonRec& btn = m_buttons[i];
 | |
| 
 | |
| 		btn.datalen		= 0;
 | |
| 		btn.rledata		= NULL;
 | |
| 		btn.rlepos		= 0;
 | |
| 		btn.currentrun	= 0;
 | |
| 	}
 | |
| 
 | |
| 	delete[] m_data;
 | |
| 	m_bRecording = false;
 | |
| 
 | |
| 	if(fcheckexists(szfilename))
 | |
| 	{
 | |
| 		FILE* f = fopen(szfilename, "wb");
 | |
| 		if(f)
 | |
| 		{
 | |
| 			// WARNING: You'll want to do more error checking, but just to keep it simple...
 | |
| 
 | |
| 			fseek(f,0,SEEK_END);
 | |
| 			unsigned long filelen = ftell(f);
 | |
| 			fseek(f,0,SEEK_SET);
 | |
| 
 | |
| 			m_data = new unsigned char[filelen];
 | |
| 			fread(m_data, 1, filelen, f);
 | |
| 			fclose(f);
 | |
| 
 | |
| 			unsigned char* bufptr = m_data;
 | |
| 			int numbuttons = bufptr[0] | (bufptr[1]<<8);
 | |
| 			bufptr += 2;
 | |
| 			if(numbuttons <= INPUT_BUTTONS_TOTAL)
 | |
| 			{
 | |
| 				for(int i=0; i<numbuttons; ++i)
 | |
| 				{
 | |
| 					ButtonRec& btn = m_buttons[i];
 | |
| 
 | |
| 					btn.datalen = bufptr[0] | (bufptr[1]<<8);
 | |
| 					bufptr += 2;
 | |
| 				}
 | |
| 
 | |
| 				for(int i=0; i<numbuttons; ++i)
 | |
| 				{
 | |
| 					ButtonRec& btn = m_buttons[i];
 | |
| 					if(btn.datalen)
 | |
| 					{
 | |
| 						// WARNING: Endian dependent for simplcicity
 | |
| 						btn.rledata = (unsigned short*)bufptr;
 | |
| 						bufptr += btn.datalen*2;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			return true;
 | |
| 		}
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /******************************************************************************/
 | |
| 
 | |
| #if INPUTREPLAY_CAN_RECORD
 | |
| void SimpleInputRec::WriteToFile()
 | |
| {
 | |
| 	if(m_data && m_bRecording)
 | |
| 	{
 | |
| 		Update(true);
 | |
| 
 | |
| 		FILE* f = fopen("_autorec.rec","wb");
 | |
| 		if(f)
 | |
| 		{
 | |
| 			fputc(INPUT_BUTTONS_TOTAL, f);
 | |
| 			fputc(0, f);
 | |
| 
 | |
| 			for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
 | |
| 			{
 | |
| 				ButtonRec& btn = m_buttons[i];
 | |
| 
 | |
| 				fputc((unsigned char)btn.rlepos, f);
 | |
| 				fputc(btn.rlepos >> 8, f);
 | |
| 			}
 | |
| 			for(int i=0; i<INPUT_BUTTONS_TOTAL; ++i)
 | |
| 			{
 | |
| 				ButtonRec& btn = m_buttons[i];
 | |
| 
 | |
| 				// WARNING: Endian dependent for simplcicity
 | |
| 				fwrite(btn.rledata, 2, btn.rlepos, f);
 | |
| 			}
 | |
| 
 | |
| 			fclose(f);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /******************************************************************************/
 | |
| 
 | |
| #endif // INPUTREPLAY_INCLUDED
 | |
| </code>
 |