/*
* UAE - The Un*x Amiga Emulator
*
* Input record/playback
*
* Copyright 2010 Toni Wilen
*
*/

#define INPUTRECORD_DEBUG 1
#define ENABLE_DEBUGGER 0

#define HEADERSIZE 12
#define TRUE 1
#define FALSE 0

#include "sysconfig.h"
#include "sysdeps.h"

#include "options.h"
#include "inputrecord.h"
#include "zfile.h"
#include "custom.h"
#include "savestate.h"
#include "cia.h"
#include "events.h"
#include "uae.h"
#include "disk.h"

#if INPUTRECORD_DEBUG > 0
#include "memory.h"
#include "newcpu.h"
#endif

int inputrecord_debug = 3;

extern int inputdevice_logging;

#define INPREC_BUFFER_SIZE 10000

static uae_u8 *inprec_buffer, *inprec_p;
static struct zfile *inprec_zf;
static int inprec_size;
int input_record = 0;
int input_play = 0;
static uae_u8 *inprec_plast, *inprec_plastptr;
static int header_end, header_end2;
static int replaypos;
static int lasthsync, endhsync;
static TCHAR inprec_path[MAX_DPATH];
static uae_u32 seed;
static uae_u32 lastcycle;
static uae_u32 cycleoffset;

static uae_u32 pcs[16];
static uae_u32 pcs2[16];
extern void activate_debugger (void);
static int warned;

extern void refreshtitle (void);

static void setlasthsync (void)
{
	if (lasthsync / current_maxvpos () != hsync_counter / current_maxvpos ()) {
		lasthsync = hsync_counter;
		refreshtitle ();
	}
}

static void flush (void)
{
	if (inprec_p > inprec_buffer) {
		zfile_fwrite (inprec_buffer, inprec_p - inprec_buffer, 1, inprec_zf);
		inprec_p = inprec_buffer;
	}
}

static void inprec_ru8 (uae_u8 v)
{
	if (!input_record || !inprec_zf)
		return;
	*inprec_p++= v;
}
static void inprec_ru16 (uae_u16 v)
{
	if (!input_record || !inprec_zf)
		return;
	inprec_ru8 ((uae_u8)(v >> 8));
	inprec_ru8 ((uae_u8)v);
}
void inprec_ru32 (uae_u32 v)
{
	if (!input_record || !inprec_zf)
		return;
	inprec_ru16 ((uae_u16)(v >> 16));
	inprec_ru16 ((uae_u16)v);
}
static void inprec_rstr (const TCHAR *src)
{
	if (!input_record || !inprec_zf)
		return;
	char *s = uutf8 (src);
	char *ss = s;
	while (*s) {
		inprec_ru8 (*s);
		s++;
	}
	inprec_ru8 (0);
	xfree (ss);
}

static bool inprec_rstart (uae_u8 type)
{
	if (!input_record || !inprec_zf || input_record == INPREC_RECORD_PLAYING)
		return false;
	lastcycle = get_cycles ();
	int mvp = current_maxvpos ();
	if ((type != INPREC_DEBUG && type != INPREC_DEBUG2 && type != INPREC_CIADEBUG) || (0 && vsync_counter >= 49 && vsync_counter <= 51))
		write_log ("INPREC: %010d/%03d: %d (%d/%d) %08x\n", hsync_counter, current_hpos (), type, hsync_counter % mvp, mvp, lastcycle);
	inprec_plast = inprec_p;
	inprec_ru8 (type);
	inprec_ru16 (0xffff);
	inprec_ru32 (hsync_counter);
	inprec_ru8 (current_hpos ());
	inprec_ru32 (lastcycle);
	return true;
}

static void inprec_rend (void)
{
	if (!input_record || !inprec_zf)
		return;
	int size = inprec_p - inprec_plast;
	inprec_plast[1] = size >> 8;
	inprec_plast[2] = size >> 0;
	flush ();
	endhsync = hsync_counter;
	setlasthsync ();
}

static bool inprec_realtime (bool stopstart)
{
	if (input_record == INPREC_RECORD_RERECORD)
		gui_message ("INPREC error");
	write_log ("INPREC: play -> record\n");
	input_record = INPREC_RECORD_RERECORD;
	input_play = 0;
	int offset = inprec_p - inprec_buffer;
	zfile_fseek (inprec_zf, offset, SEEK_SET);
	zfile_truncate (inprec_zf, offset);
	xfree (inprec_buffer);
	inprec_size = INPREC_BUFFER_SIZE;
	inprec_buffer = inprec_p = xmalloc (uae_u8, inprec_size);
	clear_inputstate ();
	return true;
}

static int inprec_pstart (uae_u8 type)
{
	uae_u8 *p = inprec_p;
	uae_u32 hc = hsync_counter;
	uae_u8 hpos = current_hpos ();
	uae_u32 cycles = get_cycles ();
	static uae_u8 *lastp;
	uae_u32 hc_orig, hc2_orig;
	int mvp = current_maxvpos ();
	unsigned int i;

	if (!input_play || !inprec_zf)
		return 0;
	if (savestate_state || hsync_counter > 0xffff0000)
		return 0;
	if (p == inprec_buffer + inprec_size) {
		write_log ("INPREC: STOP\n");
		if (input_play == INPREC_PLAY_RERECORD) {
			input_play = 0;
			inprec_realtime (true);
		} else {
			inprec_close (true);
		}
		return 0;
	} else if (p > inprec_buffer + inprec_size) {
		write_log ("INPREC: buffer error\n");
		gui_message ("INPREC error");
	}
	if (p[0] == INPREC_END) {
		inprec_close (true);
		return 0;
	} else if (p[0] == INPREC_QUIT) {
		inprec_close (true);
		uae_quit ();
		return 0;
	}
	hc_orig = hc;
	for (;;) {
		uae_u32 type2 = p[0];
		uae_u32 hc2 = (p[3] << 24) | (p[4] << 16) | (p[5] << 8) | p[6];
		uae_u32 hpos2 = p[7];
		uae_u32 cycles2 = (p[8] << 24) | (p[9] << 16) | (p[10] << 8) | p[11];

		if (p >= inprec_buffer + inprec_size)
			break;
#if 0
		if (p > lastp) {
			write_log ("INPREC: Next %010d/%03d, %010d/%03d (%d/%d): %d (%d)\n",
				hc2, hpos2, hc, hpos, hc2 - hc, hpos2 - hpos, p[5 + 1], p[5]);
			lastp = p;
		}
#endif
		hc2_orig = hc2;
		if (type2 == type && hc > hc2) {
			write_log ("INPREC: %010d/%03d > %010d/%03d: %d missed!\n", hc, hpos, hc2, hpos2, p[0]);
#if ENABLE_DEBUGGER == 0
			gui_message ("INPREC missed error");
#else
			activate_debugger ();
#endif
			lastcycle = cycles;
			inprec_plast = p;
			inprec_plastptr = p + 12;
			setlasthsync ();
			return 1;
		}
		if (hc2 != hc) {
			lastp = p;
			break;
		}
		if (type2 == type) {
			if (type != INPREC_DEBUG && type != INPREC_DEBUG2 && type != INPREC_CIADEBUG && cycles != cycles2)
				write_log ("INPREC: %010d/%03d: %d (%d/%d) (%d/%d) %08X/%08X\n", hc, hpos, type, hc % mvp, mvp, hc_orig - hc2_orig, hpos - hpos2, cycles, cycles2);
			if (cycles != cycles2 + cycleoffset) {
				if (warned > 0) {
					warned--;
					for (i = 0; i < 7; i++)
						write_log ("%08x (%08x) ", pcs[i], pcs2[i]);
					write_log ("\n");
				}
				cycleoffset = cycles - cycles2;
#if ENABLE_DEBUGGER == 0
				gui_message ("INPREC OFFSET=%d\n", (int)cycleoffset / CYCLE_UNIT);
#else
				activate_debugger ();
#endif
			}
			lastcycle = cycles;
			inprec_plast = p;
			inprec_plastptr = p + 12;
			setlasthsync ();
			return 1;
		}
		if (type2 == INPREC_END || type2 == INPREC_QUIT)
			break;
		p += (p[1] << 8) | (p[2] << 0);
	}
	inprec_plast = NULL;
	return 0;
}

static void inprec_pend (void)
{
	uae_u8 *p = inprec_p;
	uae_u32 hc = hsync_counter;
	uae_u32 hpos = current_hpos ();

	if (!input_play || !inprec_zf)
		return;
	if (!inprec_plast)
		return;
	inprec_plast[0] |= 0x80;
	inprec_plast = NULL;
	inprec_plastptr = NULL;
	for (;;) {
		uae_u32 hc2 = (p[3] << 24) | (p[4] << 16) | (p[5] << 8) | p[6];
		uae_u32 hpos2 = p[7];
		if (hc2 != hc)
			break;
		if ((p[0] & 0x80) == 0)
			return;
		p += (p[1] << 8) | (p[2] << 0);
		inprec_p = p;
	}
}

static uae_u8 inprec_pu8 (void)
{
	return *inprec_plastptr++;
}
static uae_u16 inprec_pu16 (void)
{
	uae_u16 v = inprec_pu8 () << 8;
	v |= inprec_pu8 ();
	return v;
}
static uae_s16 inprec_ps16 (void)
{
	uae_u16 v = inprec_pu8 () << 8;
	v |= inprec_pu8 ();
	return (uae_s16)v;
}
static uae_u32 inprec_pu32 (void)
{
	uae_u32 v = inprec_pu16 () << 16;
	v |= inprec_pu16 ();
	return v;
}
static int inprec_pstr (TCHAR *dst)
{
	char tmp[MAX_DPATH];
	char *s;
	int len = 0;

	*dst = 0;
	s = tmp;
	for(;;) {
		char v = inprec_pu8 ();
		*s++ = v;
		if (!v)
			break;
		len++;
	}
	if (tmp[0]) {
		//TCHAR *d = utf8u (tmp);
		_tcscpy (dst, tmp);
		//xfree (d);
	}
	return len;
}

static void findlast (void)
{
	uae_u32 hsync = 0;
	uae_u8 *p = inprec_p;
	while (p < inprec_buffer + inprec_size) {
		hsync = (p[3] << 24) | (p[4] << 16) | (p[5] << 8) | p[6];
		uae_u16 len = (p[1] << 8) | (p[2] << 0);
		p += len;
	}
	endhsync = hsync;
}


int inprec_open (const TCHAR *fname, const TCHAR *statefilename)
{
	int i;

	inprec_close (false);
	if (fname == NULL)
		inprec_zf = zfile_fopen_empty (NULL, "inp", false);
	else
		inprec_zf = zfile_fopen (fname, input_record ? "wb" : "rb", ZFD_NORMAL);
	if (inprec_zf == NULL)
		return 0;

	currprefs.cs_rtc = changed_prefs.cs_rtc = 0;

	inprec_path[0] = 0;
	if (fname)
		getpathpart (inprec_path, sizeof inprec_path / sizeof (TCHAR), fname);
	seed = (uae_u32)time(0);
	inprec_size = INPREC_BUFFER_SIZE;
	lasthsync = 0;
	endhsync = 0;
	warned = 10;
	cycleoffset = 0;
	header_end2 = 0;
	if (input_play) {
		uae_u32 id;
		zfile_fseek (inprec_zf, 0, SEEK_END);
		inprec_size = zfile_ftell (inprec_zf);
		zfile_fseek (inprec_zf, 0, SEEK_SET);
		inprec_buffer = inprec_p = xmalloc (uae_u8, inprec_size);
		zfile_fread (inprec_buffer, inprec_size, 1, inprec_zf);
		inprec_plastptr = inprec_buffer;
		id = inprec_pu32();
		if (id != 'UAE\0') {
			inprec_close (true);
			return 0;
		}
		int v = inprec_pu8 ();
		if (v != 2) {
			inprec_close (true);
			return 0;
		}
		inprec_pu8 ();
		inprec_pu8 ();
		inprec_pu8 ();
		seed = inprec_pu32();
		seed = uaesrand (seed);
		vsync_counter = inprec_pu32 ();
		hsync_counter = inprec_pu32 ();
		i = inprec_pu32 ();
		while (i-- > 0)
			inprec_pu8 ();
		header_end = inprec_plastptr - inprec_buffer;
		inprec_pstr (savestate_fname);
		if (savestate_fname[0]) {
			savestate_state = STATE_RESTORE;
			for (;;) {
				TCHAR tmp[MAX_DPATH];
				_tcscpy (tmp, fname);
				_tcscat (tmp, ".uss");
				if (zfile_exists (tmp)) {
					_tcscpy (savestate_fname, tmp);
					break;
				}
				if (zfile_exists (savestate_fname))
					break;
				TCHAR *p = _tcsrchr (savestate_fname, '\\');
				if (!p)
					p = _tcsrchr (savestate_fname, '/');
				if (!p)
					p = savestate_fname;
				else
					p++;
				if (zfile_exists (p)) {
					_tcscpy (savestate_fname, p);
					break;
				}
				fetch_statefilepath (tmp, sizeof tmp / sizeof (TCHAR));
				_tcscat (tmp, p);
				if (zfile_exists (tmp)) {
					_tcscpy (savestate_fname, tmp);
					break;
				}
				fetch_inputfilepath (tmp, sizeof tmp / sizeof (TCHAR));
				_tcscat (tmp, p);
				if (zfile_exists (tmp)) {
					_tcscpy (savestate_fname, tmp);
					break;
				}
				write_log ("Failed to open linked statefile '%s'\n", savestate_fname);
				savestate_fname[0] = 0;
				savestate_state = 0;
				break;
			}
		}
		inprec_p = inprec_plastptr;
		header_end2 = inprec_plastptr - inprec_buffer;
		findlast ();
	} else if (input_record) {
		seed = uaesrand (seed);
		inprec_buffer = inprec_p = xmalloc (uae_u8, inprec_size);
		inprec_ru32 ('UAE\0');
		inprec_ru8 (2);
		inprec_ru8 (UAEMAJOR);
		inprec_ru8 (UAEMINOR);
		inprec_ru8 (UAESUBREV);
		inprec_ru32 (seed);
		inprec_ru32 (vsync_counter);
		inprec_ru32 (hsync_counter);
		inprec_ru32 (0); // extra header size
		flush ();
		header_end2 = header_end = zfile_ftell (inprec_zf);
	} else {
		input_record = input_play = 0;
		return 0;
	}
	if (inputrecord_debug) {
		if (disk_debug_logging < 1)
			disk_debug_logging = 1 | 2;
	}
	write_log ("inprec initialized '%s', play=%d rec=%d\n", fname ? fname : "<internal>", input_play, input_record);
	refreshtitle ();
	return 1;
}

void inprec_startup (void)
{
	uaesrand (seed);
}

bool inprec_prepare_record (const TCHAR *statefilename)
{
	TCHAR state[MAX_DPATH];
	int mode = statefilename ? 2 : 1;
	state[0] = 0;
	if (statefilename)
		_tcscpy (state, statefilename);
	if (hsync_counter > 0 && savestate_state == 0) {
		TCHAR *s = _tcsrchr (changed_prefs.inprecfile, '\\');
		if (!s)
			s = _tcsrchr (changed_prefs.inprecfile, '/');
		if (s) {
			fetch_statefilepath (state, sizeof state / sizeof (TCHAR));
			_tcscat (state, s + 1);
		} else {
			_tcscpy (state, changed_prefs.inprecfile);
		}
		_tcscat (state, ".uss");
		savestate_initsave (state, 1, 1, true); 
		save_state (state, "input recording test");
		mode = 2;
	}
	input_record = INPREC_RECORD_NORMAL;
	inprec_open (changed_prefs.inprecfile, state);
	changed_prefs.inprecfile[0] = currprefs.inprecfile[0] = 0;
	return true;
}


void inprec_close (bool clear)
{
	if (clear)
		input_play = input_record = 0;
	if (!inprec_zf)
		return;
	if (inprec_buffer && input_record) {
		if (inprec_rstart (INPREC_END))
			inprec_rend ();
	}
	zfile_fclose (inprec_zf);
	inprec_zf = NULL;
	xfree (inprec_buffer);
	inprec_buffer = NULL;
	input_play = input_record = 0;
	write_log ("inprec finished\n");
	refreshtitle ();
}

static void setwriteprotect (const TCHAR *fname, bool readonly)
{
	struct stat64 st;
	int mode, oldmode;
	if (stat (fname, &st))
		return;
	oldmode = mode = st.st_mode;
	mode &= ~0x04;
	if (!readonly)
		mode |= 0x04;
	if (mode != oldmode)
		chmod (fname, mode);
}

void inprec_playdiskchange (void)
{
	if (!input_play)
		return;
	while (inprec_pstart (INPREC_DISKREMOVE)) {
		int drv = inprec_pu8 ();
		inprec_pend ();
		write_log ("INPREC: disk eject drive %d\n", drv);
		disk_eject (drv);
	}
	while (inprec_pstart (INPREC_DISKINSERT)) {
		int drv = inprec_pu8 ();
		bool wp = inprec_pu8 () != 0;
		TCHAR tmp[MAX_DPATH], tmp2[MAX_DPATH];
		inprec_pstr (tmp);
		_tcscpy (tmp2, tmp);
		if (!zfile_exists (tmp)) {
			TCHAR tmp3[MAX_DPATH];
			_tcscpy (tmp3, inprec_path);
			_tcscat (tmp3, tmp);
			_tcscpy (tmp, tmp3);
		}
		if (!zfile_exists (tmp)) {
			gui_message ("INPREC: Disk image\n'%s'\nnot found!\n", tmp2);
		}
		_tcscpy (currprefs.floppyslots[drv].df, tmp);
		_tcscpy (changed_prefs.floppyslots[drv].df, tmp);
		setwriteprotect (tmp, wp);
		disk_insert_force (drv, tmp, wp);
		write_log ("INPREC: disk insert drive %d '%s'\n", drv, tmp);
		inprec_pend ();
	}
}

bool inprec_playevent (int *nr, int *state, int *max, int *autofire)
{
	if (inprec_pstart (INPREC_EVENT)) {
		*nr = inprec_ps16 ();
		*state = inprec_ps16 ();
		*max = inprec_pu16 ();
		*autofire = inprec_ps16 () & 1;
		inprec_pend ();
		return true;
	}
	return false;
}

void inprec_recorddebug_cia (uae_u32 v1, uae_u32 v2, uae_u32 v3)
{
#if INPUTRECORD_DEBUG > 0
	if (inprec_rstart (INPREC_CIADEBUG)) {
		inprec_ru32 (v1);
		inprec_ru32 (v2);
		inprec_ru32 (v3);
		inprec_rend ();
	}
#endif
}
void inprec_playdebug_cia (uae_u32 v1, uae_u32 v2, uae_u32 v3)
{
#if INPUTRECORD_DEBUG > 0
	int err = 0;
	if (inprec_pstart (INPREC_CIADEBUG)) {
		uae_u32 vv1 = inprec_pu32 ();
		uae_u32 vv2 = inprec_pu32 ();
		uae_u32 vv3 = inprec_pu32 ();
		if (vv1 != v1 || vv2 != v2 || vv3 != v3)
			write_log ("CIA SYNC ERROR %08x,%08x %08x,%08x %08x,%08x\n", vv1, v1, vv2, v2, vv3, v3);
		inprec_pend ();
	}
#endif
}

void inprec_recorddebug_cpu (int mode)
{
#if INPUTRECORD_DEBUG > 0
	if (inprec_rstart (INPREC_DEBUG2)) {
		inprec_ru32 (m68k_getpc ());
		inprec_ru32 (get_cycles () | mode);
		inprec_rend ();
	}
#endif
}
void inprec_playdebug_cpu (int mode)
{
#if INPUTRECORD_DEBUG > 0
	int err = 0;
	unsigned int i;

	if (inprec_pstart (INPREC_DEBUG2)) {
		uae_u32 pc1 = m68k_getpc ();
		uae_u32 pc2 = inprec_pu32 ();
		uae_u32 v1 = get_cycles () | mode;
		uae_u32 v2 = inprec_pu32 ();
		if (pc1 != pc2) {
			if (warned > 0) {
				warned--;
				write_log ("SYNC ERROR2 PC %08x != %08x\n", pc1, pc2);
				for (i = 0; i < 15; i++)
					write_log ("%08x ", pcs[i]);
				write_log ("\n");

			}
			err = 1;
		} else {
			memmove (pcs + 1, pcs, 15 * 4);
			pcs[0] = pc1;
			memmove (pcs2 + 1, pcs2, 15 * 4);
			pcs2[0] = get_cycles ();
		}
		if (v1 != v2) {
			if (warned > 0) {
				warned--;
				write_log ("SYNC ERROR2 %08x != %08x\n", v1, v2);
				for (i = 0; i < 15; i++)
					write_log ("%08x ", pcs[i]);
				write_log ("\n");
			}
			err = 1;
		}
		inprec_pend ();
	} else if (input_play > 0) {
		if (warned > 0) {
			warned--;
			write_log ("SYNC ERROR2 debug event missing!?\n");
		}
	}
#endif
}

void inprec_recorddebug (uae_u32 val)
{
#if INPUTRECORD_DEBUG > 0
	if (inprec_rstart (INPREC_DEBUG)) {
		inprec_ru32 (uaerandgetseed ());
		inprec_ru32 (val);
		inprec_rend ();
	}
#endif
}
void inprec_playdebug (uae_u32 val)
{
#if INPUTRECORD_DEBUG > 0
	extern void activate_debugger (void);
	static uae_u32 pcs[16];
	int err = 0;
	if (inprec_pstart (INPREC_DEBUG)) {
		uae_u32 seed1 = uaerandgetseed ();
		uae_u32 seed2 = inprec_pu32 ();
		if (seed1 != seed2) {
			write_log ("SYNC ERROR seed %08x != %08x\n", seed1, seed2);
			err = 1;
		}
		uae_u32 val2 = inprec_pu32 ();
		if (val != val2) {
			write_log ("SYNC ERROR val %08x != %08x\n", val, val2);
			err = 1;
		}
		inprec_pend ();
	} else if (input_play > 0) {
		gui_message ("SYNC ERROR debug event missing!?\n");
	}
#endif
}


void inprec_recordevent (int nr, int state, int max, int autofire)
{
	if (savestate_state)
		return;
	if (input_record < INPREC_RECORD_NORMAL)
		return;
	if (inprec_rstart (INPREC_EVENT)) {
		inprec_ru16 (nr);
		inprec_ru16 (state);
		inprec_ru16 (max);
		inprec_ru16 (autofire ? 1 : 0);
		inprec_rend ();
		if (input_record == INPREC_RECORD_NORMAL)
			input_record = INPREC_RECORD_RERECORD;
	}
}

void inprec_recorddiskchange (int nr, const TCHAR *fname, bool writeprotected)
{
	if (savestate_state)
		return;
	if (input_record < INPREC_RECORD_NORMAL)
		return;
	if (fname && fname[0]) {
		if (inprec_rstart (INPREC_DISKINSERT)) {
			inprec_ru8 (nr);
			inprec_ru8 (writeprotected ? 1 : 0);
			inprec_rstr (fname);
			write_log ("INPREC: disk insert %d '%s'\n", nr, fname);
			inprec_rend ();
		}
	} else {
		if (inprec_rstart (INPREC_DISKREMOVE)) {
			inprec_ru8 (nr);
			write_log ("INPREC: disk eject %d\n", nr);
			inprec_rend ();
		}
	}
}

int inprec_getposition (void)
{
	int pos = -1;
	if (input_play == INPREC_PLAY_RERECORD) {
		pos = inprec_p - inprec_buffer;
	} else if (input_record) {
		pos = zfile_ftell (inprec_zf);
	}
	write_log ("INPREC: getpos=%d cycles=%08X\n", pos, lastcycle);
	if (pos < 0) {
		write_log ("INPREC: getpos failure\n");
		gui_message ("INPREC error");
	}
	return pos;
}

// normal play to re-record
void inprec_playtorecord (void)
{
	write_log ("INPREC: PLAY to RE-RECORD\n");
	replaypos = 0;
	findlast ();
	input_play = INPREC_PLAY_RERECORD;
	input_record = INPREC_RECORD_PLAYING;
	zfile_fclose (inprec_zf);
	inprec_zf = zfile_fopen_empty (NULL, "inp", false);
	zfile_fwrite (inprec_buffer, header_end2, 1, inprec_zf);
	uae_u8 *p = inprec_buffer + header_end2;
	uae_u8 *end = inprec_buffer + inprec_size;
	while (p < end) {
		int len = (p[1] << 8) | (p[2] << 0);
		p[0] &= ~0x80;
		p += len;
	}
	zfile_fwrite (inprec_buffer + header_end2, inprec_size - header_end2, 1, inprec_zf);
	inprec_realtime (false);
	savestate_capture_request ();
}

void inprec_setposition (int offset, int replaycounter)
{
	if (!inprec_buffer)
		return;
	replaypos = replaycounter;
	write_log ("INPREC: setpos=%d\n", offset);
	if (offset < header_end || offset > zfile_size (inprec_zf)) {
		write_log ("INPREC: buffer corruption. offset=%d, size=%d\n", offset, zfile_size (inprec_zf));
		gui_message ("INPREC error");
	}
	zfile_fseek (inprec_zf, 0, SEEK_SET);
	xfree (inprec_buffer);
	inprec_size = zfile_size (inprec_zf);
	inprec_buffer = xmalloc (uae_u8, inprec_size);
	zfile_fread (inprec_buffer, inprec_size, 1, inprec_zf);
	inprec_p = inprec_plastptr = inprec_buffer + offset;
	findlast ();
	input_play = INPREC_PLAY_RERECORD;
	input_record = INPREC_RECORD_PLAYING;
	if (currprefs.inprec_autoplay == false)
		inprec_realtime (false);
}

static void savelog (const TCHAR *path, const TCHAR *file)
{
	TCHAR tmp[MAX_DPATH];

	_tcscpy (tmp, path);
	_tcscat (tmp, file);
	_tcscat (tmp, ".log.txt");
	struct zfile *zfd = zfile_fopen (tmp, "wb", 0);
	if (zfd) {
		int loglen;
		uae_u8 *log;
		loglen = 0;
		log = save_log (TRUE, &loglen);
		if (log)
			zfile_fwrite (log, loglen, 1, zfd);
		xfree (log);
		loglen = 0;
		log = save_log (FALSE, &loglen);
		if (log)
			zfile_fwrite (log, loglen, 1, zfd);
		xfree (log);
		zfile_fclose (zfd);
		write_log ("log '%s' saved\n", tmp);
	}
}

static int savedisk (const TCHAR *path, const TCHAR *file, uae_u8 *data, uae_u8 *outdata)
{
	int len = 0;
	TCHAR *fname = utf8u ((const char*)data + 2);
	if (fname[0]) {
		TCHAR tmp[MAX_DPATH];
		TCHAR filename[MAX_DPATH];
		filename[0] = 0;
		struct zfile *zf = zfile_fopen (fname, "rb", ZFD_NORMAL);
		if (!zf) {
			_tcscpy (tmp, path);
			_tcscat (tmp, fname);
			zf = zfile_fopen (tmp, "rb", ZFD_NORMAL);
			if (!zf)
				write_log ("failed to open '%s'\n", tmp);
		}
		if (zf) {
			_tcscpy (tmp, path);
			_tcscpy (filename, file);
			_tcscat (filename, ".");
			getfilepart (filename + _tcslen (filename), MAX_DPATH, zfile_getname (zf));
			_tcscat (tmp, filename);
			struct zfile *zfd = zfile_fopen (tmp, "wb", 0);
			if (zfd) {
				int size = zfile_size (zf);
				uae_u8 *data = zfile_getdata (zf, 0, size);
				zfile_fwrite (data, size, 1, zfd);
				zfile_fclose (zfd);
				xfree (data);
			}
			zfile_fclose (zf);
			setwriteprotect (fname, data[1] != 0);
		}
		if (filename[0]) {
			outdata[0] = data[0];
			char *fn = uutf8 (filename);
			strcpy ((char*)outdata + 2, fn);
			xfree (fn);
			len = 2 + strlen ((char*)outdata + 2) + 1;
		}
	}
	xfree (fname);
	return len;
}

void inprec_save (const TCHAR *filename, const TCHAR *statefilename)
{
	TCHAR path[MAX_DPATH], file[MAX_DPATH];
	if (!inprec_buffer)
		return;
	getpathpart (path, sizeof path / sizeof (TCHAR), filename);
	getfilepart (file, sizeof file / sizeof (TCHAR), filename);
	struct zfile *zf = zfile_fopen (filename, "wb", 0);
	if (zf) {
		TCHAR fn[MAX_DPATH];
		uae_u8 *data;
		data = zfile_getdata (inprec_zf, 0, header_end);
		zfile_fwrite (data, header_end, 1, zf);
		xfree (data);
		getfilepart (fn, MAX_DPATH, statefilename);
		char *s = uutf8 (fn);
		zfile_fwrite (s, strlen (s) + 1, 1, zf);
		int len = zfile_size (inprec_zf) -  header_end2;
		data = zfile_getdata (inprec_zf, header_end2, len);
		uae_u8 *p = data;
		uae_u8 *end = data + len;
		while (p < end) {
			uae_u8 tmp[MAX_DPATH];
			int plen = (p[1] << 8) | (p[2] << 0);
			int wlen = plen - HEADERSIZE;
			memcpy (tmp, p + HEADERSIZE, wlen);
			if (p[0] == INPREC_DISKINSERT) {
				wlen = savedisk (path, file, p + HEADERSIZE, tmp);
			}
			if (wlen) {
				wlen += HEADERSIZE;
				p[1] = wlen >> 8;
				p[2] = wlen;
				zfile_fwrite (p, HEADERSIZE, 1, zf);
				zfile_fwrite (tmp, wlen - HEADERSIZE, 1, zf);
			} else {
				zfile_fwrite (p, plen, 1, zf);
			}
			p += plen;
		}
		xfree (data);
		zfile_fclose (zf);
		savelog (path, file);
		write_log ("inputfile '%s' saved\n", filename);
	} else {
		write_log ("failed to open '%s'\n", filename);
	}
}

bool inprec_realtimev (void)
{
	if (input_record != INPREC_RECORD_PLAYING || input_play != INPREC_PLAY_RERECORD)
		return false;
	//clear_inputstate ();
	return inprec_realtime (false);
}

void inprec_getstatus (TCHAR *title)
{
	TCHAR *p;
	if (!input_record && !input_play)
		return;
	_tcscat (title, "[");
	if (input_record) {
		if (input_record != INPREC_RECORD_PLAYING)
			_tcscat (title, "-REC-");
		else
			_tcscat (title, "REPLAY");
	} else if (input_play) {
		_tcscat (title, "PLAY-");
	}
	_tcscat (title, " ");
	p = title + _tcslen (title);
	int mvp = current_maxvpos ();
	_stprintf (p, "%03d %02d:%02d:%02d/%02d:%02d:%02d", replaypos,
		lasthsync / (vblank_hz * mvp * 60), (lasthsync / (vblank_hz * mvp) % 60), (lasthsync / mvp) % vblank_hz,
		endhsync / (vblank_hz * mvp * 60), (endhsync / (vblank_hz * mvp) % 60), (endhsync / mvp) % vblank_hz);
	p += _tcslen (p);
	_tcscat (p, "] ");

}
