/* FCE Ultra - NES/Famicom Emulator
*
* Copyright notice for this file:
*  Copyright (C) 2002 Xodnizel
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

//  TODO: Add (better) file io error checking

#include "version.h"
#include "types.h"
#include "x6502.h"
#include "fceu.h"
#include "sound.h"
#include "utils/endian.h"
#include "utils/memory.h"
#include "utils/xstring.h"
#include "file.h"
#include "fds.h"
#include "state.h"
#include "movie.h"
#include "ppu.h"
#include "netplay.h"
#include "video.h"
#include "input.h"
#include "zlib.h"
#include "driver.h"
#ifdef _S9XLUA_H
#include "fceulua.h"
#endif

//TODO - we really need some kind of global platform-specific options api
#ifdef __WIN_DRIVER__
#include "drivers/win/main.h"
#include "drivers/win/cheat.h"
#include "drivers/win/ram_search.h"
#include "drivers/win/ramwatch.h"
#endif

#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
//#include <unistd.h> //mbg merge 7/17/06 removed

#include <vector>
#include <fstream>

using namespace std;

static void (*SPreSave)(void) = NULL;
static void (*SPostSave)(void) = NULL;
static void (*SPostLoad)(bool) = NULL;

static int SaveStateStatus[10];
static int StateShow;

//tells the save system innards that we're loading the old format
bool FCEU_state_loading_old_format = false;

std::string lastSavestateMade; //Stores the filename of the last savestate made (needed for UndoSavestate)
bool undoSS = false;		  //This will be true if there is lastSavestateMade, it was made since ROM was loaded, a backup state for lastSavestateMade exists
bool redoSS = false;		  //This will be true if UndoSaveState is run, will turn false when a new savestate is made

std::string lastLoadstateMade; //Stores the filename of the last state loaded (needed for Undo/Redo loadstate)
bool undoLS = false;		  //This will be true if a backupstate was made and it was made since ROM was loaded
bool redoLS = false;		  //This will be true if a backupstate was loaded, meaning redoLoadState can be run

bool internalSaveLoad = false;

bool backupSavestates = true;
bool compressSavestates = true;  //By default FCEUX compresses savestates when a movie is inactive.

// a temp memory stream. We'll be dumping some data here and then compress
static EMUFILE_MEMORY memory_savestate;
// temporary buffer for compressed data of a savestate
static std::vector<uint8> compressed_buf;

#define SFMDATA_SIZE (128)
static SFORMAT SFMDATA[SFMDATA_SIZE];
static int SFEXINDEX;

#define RLSB 		FCEUSTATE_RLSB	//0x80000000


extern SFORMAT FCEUPPU_STATEINFO[];
extern SFORMAT FCEU_NEWPPU_STATEINFO[];
extern SFORMAT FCEUSND_STATEINFO[];
extern SFORMAT FCEUCTRL_STATEINFO[];
extern SFORMAT FCEUMOV_STATEINFO[];

//why two separate CPU structs?? who knows

SFORMAT SFCPU[]={
	{ &X.PC, 2|RLSB, "PC\0"},
	{ &X.A, 1, "A\0\0"},
	{ &X.X, 1, "X\0\0"},
	{ &X.Y, 1, "Y\0\0"},
	{ &X.S, 1, "S\0\0"},
	{ &X.P, 1, "P\0\0"},
	{ &X.DB, 1, "DB\0"},
	{ &RAM, 0x800 | FCEUSTATE_INDIRECT, "RAM", },
	{ 0 }
};

SFORMAT SFCPUC[]={
	{ &X.jammed, 1, "JAMM"},
	{ &X.IRQlow, 4|RLSB, "IQLB"},
	{ &X.tcount, 4|RLSB, "ICoa"},
	{ &X.count,  4|RLSB, "ICou"},
	{ &timestampbase, sizeof(timestampbase) | RLSB, "TSBS"},
	{ &X.mooPI, 1, "MooP"}, // alternative to the "quick and dirty hack"
	{ 0 }
};

void foo(uint8* test) { (void)test; }

static int SubWrite(EMUFILE* os, SFORMAT *sf)
{
	uint32 acc=0;

	while(sf->v)
	{
		if(sf->s==~0u)		//Link to another struct
		{
			uint32 tmp;

			if(!(tmp=SubWrite(os,(SFORMAT *)sf->v)))
				return(0);
			acc+=tmp;
			sf++;
			continue;
		}

		acc+=8;			//Description + size
		acc+=sf->s&(~FCEUSTATE_FLAGS);

		if(os)			//Are we writing or calculating the size of this block?
		{
			os->fwrite(sf->desc,4);
			write32le(sf->s&(~FCEUSTATE_FLAGS),os);

#ifdef FCEU_BIG_ENDIAN
			if(sf->s&RLSB)
				FlipByteOrder((uint8*)sf->v,sf->s&(~FCEUSTATE_FLAGS));
#endif

			if(sf->s&FCEUSTATE_INDIRECT)
				os->fwrite(*(char **)sf->v,sf->s&(~FCEUSTATE_FLAGS));
			else
				os->fwrite((char*)sf->v,sf->s&(~FCEUSTATE_FLAGS));

			//Now restore the original byte order.
#ifdef FCEU_BIG_ENDIAN
			if(sf->s&RLSB)
				FlipByteOrder((uint8*)sf->v,sf->s&(~FCEUSTATE_FLAGS));
#endif
		}
		sf++;
	}

	return(acc);
}

static int WriteStateChunk(EMUFILE* os, int type, SFORMAT *sf)
{
	os->fputc(type);
	int bsize = SubWrite((EMUFILE*)0,sf);
	write32le(bsize,os);

	if(!SubWrite(os,sf))
	{
		return 5;
	}
	return (bsize+5);
}

static SFORMAT *CheckS(SFORMAT *sf, uint32 tsize, char *desc)
{
	while(sf->v)
	{
		if(sf->s==~0u)		// Link to another SFORMAT structure.
		{
			SFORMAT *tmp;
			if((tmp= CheckS((SFORMAT *)sf->v, tsize, desc) ))
				return(tmp);
			sf++;
			continue;
		}
		if(!memcmp(desc,sf->desc,4))
		{
			if(tsize!=(sf->s&(~FCEUSTATE_FLAGS)))
				return(0);
			return(sf);
		}
		sf++;
	}
	return(0);
}

static bool ReadStateChunk(EMUFILE* is, SFORMAT *sf, int size)
{
	SFORMAT *tmp;
	int temp = is->ftell();

	while(is->ftell()<temp+size)
	{
		uint32 tsize;
		char toa[4];
		if(is->fread(toa,4)<4)
			return false;

		read32le(&tsize,is);

		if((tmp=CheckS(sf,tsize,toa)))
		{
			if(tmp->s&FCEUSTATE_INDIRECT)
				is->fread(*(char **)tmp->v,tmp->s&(~FCEUSTATE_FLAGS));
			else
				is->fread((char *)tmp->v,tmp->s&(~FCEUSTATE_FLAGS));

#ifdef FCEU_BIG_ENDIAN
			if(tmp->s&RLSB)
				FlipByteOrder((uint8*)tmp->v,tmp->s&(~FCEUSTATE_FLAGS));
#endif
		}
		else
			is->fseek(tsize,SEEK_CUR);
	} // while(...)
	return true;
}

static int read_sfcpuc=0, read_snd=0;

void FCEUD_BlitScreen(uint8 *XBuf); //mbg merge 7/17/06 YUCKY had to add
void UpdateFCEUWindow(void);  //mbg merge 7/17/06 YUCKY had to add
static bool ReadStateChunks(EMUFILE* is, int32 totalsize)
{
	int t;
	uint32 size;
	bool ret=true;
	bool warned=false;

	read_sfcpuc=0;
	read_snd=0;

	//mbg 6/16/08 - wtf
	//// int moo=X.mooPI;
	// if(!scan_chunks)
	//   X.mooPI=/*X.P*/0xFF;

	while(totalsize > 0)
	{
		t=is->fgetc();
		if(t==EOF) break;
		if(!read32le(&size,is)) break;
		totalsize -= size + 5;

		switch(t)
		{
		case 1:if(!ReadStateChunk(is,SFCPU,size)) ret=false;break;
		case 3:if(!ReadStateChunk(is,FCEUPPU_STATEINFO,size)) ret=false;break;
		case 31:if(!ReadStateChunk(is,FCEU_NEWPPU_STATEINFO,size)) ret=false;break;
		case 4:if(!ReadStateChunk(is,FCEUCTRL_STATEINFO,size)) ret=false;break;
		case 7:
			if(!FCEUMOV_ReadState(is,size)) {
				//allow this to fail in old-format savestates.
				if(!FCEU_state_loading_old_format)
					ret=false;
			}
			break;
		case 0x10:
			if(!ReadStateChunk(is,SFMDATA,size)) 
				ret=false; 
			break;

			// now it gets hackier:
		case 5:
			if(!ReadStateChunk(is,FCEUSND_STATEINFO,size))
				ret=false;
			else
				read_snd=1;
			break;
		case 6:
			if(FCEUMOV_Mode(MOVIEMODE_PLAY|MOVIEMODE_RECORD|MOVIEMODE_FINISHED))
			{
				if(!ReadStateChunk(is,FCEUMOV_STATEINFO,size)) ret=false;
			}
			else
			{
				is->fseek(size,SEEK_CUR);
			}
			break;
		case 8:
			// load back buffer
			{
				extern uint8 *XBackBuf;
				//ignore 8 garbage bytes, whose idea was it to write these or even have them there in the first place
				if(size == 256*256+8)
				{
					if(is->fread((char*)XBackBuf,256*256) != 256*256)
						ret = false;
					is->fseek(8,SEEK_CUR);
				}
				else
				{
					if(is->fread((char*)XBackBuf,size) != size)
						ret = false;
				}


				//MBG TODO - can this be moved to a better place?
				//does it even make sense, displaying XBuf when its XBackBuf we just loaded?
#ifdef __WIN_DRIVER__
				if(ret)
				{
					FCEUD_BlitScreen(XBuf);
					UpdateFCEUWindow();
				}
#endif

			}
			break;
		case 2:
			{
				if(!ReadStateChunk(is,SFCPUC,size))
					ret=false;
				else
					read_sfcpuc=1;
			}  break;
		default:
			// for somebody's sanity's sake, at least warn about it:
			if(!warned)
			{
				char str [256];
				snprintf(str, sizeof(str), "Warning: Found unknown save chunk of type %d.\nThis could indicate the save state is corrupted\nor made with a different (incompatible) emulator version.", t);
				FCEUD_PrintError(str);
				warned=true;
			}
			//if(fseek(st,size,SEEK_CUR)<0) goto endo;break;
			is->fseek(size,SEEK_CUR);
		}
	}
	//endo:

	//mbg 6/16/08 - wtf
	// if(X.mooPI==0xFF && !scan_chunks)
	// {
	////	 FCEU_PrintError("prevmoo=%d, p=%d",moo,X.P);
	//   X.mooPI=X.P; // "Quick and dirty hack." //begone
	// }

	extern int resetDMCacc;
	if(read_snd)
		resetDMCacc=0;
	else
		resetDMCacc=1;

	return ret;
}

int CurrentState=0;
extern int geniestage;


bool FCEUSS_SaveMS(EMUFILE* outstream, int compressionLevel)
{
	// reinit memory_savestate
	// memory_savestate is global variable which already has its vector of bytes, so no need to allocate memory every time we use save/loadstate
	memory_savestate.set_len(0);	// this also seeks to the beginning
	memory_savestate.unfail();

	EMUFILE* os = &memory_savestate;

	uint32 totalsize = 0;

	FCEUPPU_SaveState();
	FCEUSND_SaveState();
	totalsize=WriteStateChunk(os,1,SFCPU);
	totalsize+=WriteStateChunk(os,2,SFCPUC);
	totalsize+=WriteStateChunk(os,3,FCEUPPU_STATEINFO);
	totalsize+=WriteStateChunk(os,31,FCEU_NEWPPU_STATEINFO);
	totalsize+=WriteStateChunk(os,4,FCEUCTRL_STATEINFO);
	totalsize+=WriteStateChunk(os,5,FCEUSND_STATEINFO);
	if(FCEUMOV_Mode(MOVIEMODE_PLAY|MOVIEMODE_RECORD|MOVIEMODE_FINISHED))
	{
		totalsize+=WriteStateChunk(os,6,FCEUMOV_STATEINFO);

		//MBG TAS Editor HACK HACK HACK!
		//do not save the movie state if we are in Taseditor! That would be a huge waste of time and space!
		if(!FCEUMOV_Mode(MOVIEMODE_TASEDITOR))
		{
			os->fseek(5,SEEK_CUR);
			int size = FCEUMOV_WriteState(os);
			os->fseek(-(size+5),SEEK_CUR);
			os->fputc(7);
			write32le(size, os);
			os->fseek(size,SEEK_CUR);

			totalsize += 5 + size;
		}
	}
	// save back buffer
	{
		extern uint8 *XBackBuf;
		uint32 size = 256 * 256;
		os->fputc(8);
		write32le(size, os);
		os->fwrite((char*)XBackBuf,size);
		totalsize += 5 + size;
	}

	if(SPreSave) SPreSave();
	totalsize+=WriteStateChunk(os,0x10,SFMDATA);
	if(SPostSave) SPostSave();

	//save the length of the file
	size_t len = memory_savestate.size();

	//sanity check: len and totalsize should be the same
	if(len != totalsize)
	{
		FCEUD_PrintError("sanity violation: len != totalsize");
		return false;
	}

	int error = Z_OK;
	uint8* cbuf = (uint8*)memory_savestate.buf();
	uLongf comprlen = ~0lu;
	if(compressionLevel != Z_NO_COMPRESSION && (compressSavestates || FCEUMOV_Mode(MOVIEMODE_TASEDITOR)))
	{
		// worst case compression: zlib says "0.1% larger than sourceLen plus 12 bytes"
		comprlen = (len>>9)+12 + len;
		if (compressed_buf.size() < comprlen) compressed_buf.resize(comprlen);
		cbuf = &compressed_buf[0];
		// do compression
		error = compress2(cbuf, &comprlen, (uint8*)memory_savestate.buf(), len, compressionLevel);
	}

	//dump the header
	uint8 header[16]="FCSX";
	FCEU_en32lsb(header+4, totalsize);
	FCEU_en32lsb(header+8, FCEU_VERSION_NUMERIC);
	FCEU_en32lsb(header+12, comprlen);

	//dump it to the destination file
	outstream->fwrite((char*)header,16);
	outstream->fwrite((char*)cbuf,comprlen==~0lu?totalsize:comprlen);

	return error == Z_OK;
}


void FCEUSS_Save(const char *fname, bool display_message)
{
	EMUFILE* st = 0;
	std::string fn;

	if (geniestage==1)
	{
		if (display_message)
			FCEU_DispMessage("Cannot save FCS in GG screen.",0);
		return;
	}

	if(fname)	//If filename is given use it.
	{
		st = FCEUD_UTF8_fstream(fname, "wb");
		fn.assign(fname);
	}
	else		//Else, generate one
	{
		//FCEU_PrintError("daCurrentState=%d",CurrentState);
		fn = FCEU_MakeFName(FCEUMKF_STATE,CurrentState,0);

		//backup existing savestate first
		if (CheckFileExists(fn.c_str()) && backupSavestates)	//adelikat:  If the files exists and we are allowed to make backup savestates
		{
			CreateBackupSaveState(fn.c_str());		//Make a backup of previous savestate before overwriting it
			lastSavestateMade.assign(fn);	//Remember what the last savestate filename was (for undoing later)
			undoSS = true;					//Backup was created so undo is possible
		}
		else
			undoSS = false;					//so backup made so lastSavestateMade does have a backup file, so no undo

		st = FCEUD_UTF8_fstream(fn.c_str(),"wb");
	}

	if (st == NULL || st->get_fp() == NULL)
	{
		if (display_message)
			FCEU_DispMessage("State %d save error.", 0, CurrentState);
		return;
	}

	#ifdef _S9XLUA_H
	if (!internalSaveLoad)
	{
		LuaSaveData saveData;
		CallRegisteredLuaSaveFunctions(CurrentState, saveData);

		std::string luaSaveFilename;
		luaSaveFilename.assign(fn.c_str());
		luaSaveFilename.append(".luasav");
		if(saveData.recordList)
		{
			FILE* luaSaveFile = fopen(luaSaveFilename.c_str(), "wb");
			if(luaSaveFile)
			{
				saveData.ExportRecords(luaSaveFile);
				fclose(luaSaveFile);
			}
		}
		else
		{
			unlink(luaSaveFilename.c_str());
		}
	}
	#endif

	if(FCEUMOV_Mode(MOVIEMODE_INACTIVE))
		FCEUSS_SaveMS(st,-1);
	else
		FCEUSS_SaveMS(st,0);

	delete st;

	if(!fname)
	{
		SaveStateStatus[CurrentState] = 1;
		if (display_message)
			FCEU_DispMessage("State %d saved.", 0, CurrentState);
	}
	redoSS = false;					//we have a new savestate so redo is not possible
}

int FCEUSS_LoadFP_old(EMUFILE* is, ENUM_SSLOADPARAMS params)
{
	//if(params==SSLOADPARAM_DUMMY && suppress_scan_chunks)
	//	return 1;

	int x;
	uint8 header[16];
	int stateversion;
	char* fn=0;

	////Make temporary savestate in case something screws up during the load
	//if(params == SSLOADPARAM_BACKUP)
	//{
	//	fn=FCEU_MakeFName(FCEUMKF_NPTEMP,0,0);
	//	FILE *fp;
	//
	//	if((fp=fopen(fn,"wb")))
	//	{
	//		if(FCEUSS_SaveFP(fp))
	//		{
	//			fclose(fp);
	//		}
	//		else
	//		{
	//			fclose(fp);
	//			unlink(fn);
	//			free(fn);
	//			fn=0;
	//		}
	//	}
	//}

	//if(params!=SSLOADPARAM_DUMMY)
	{
		FCEUMOV_PreLoad();
	}
    is->fread((char*)&header,16);
	if(memcmp(header,"FCS",3))
	{
		return(0);
	}
	if(header[3] == 0xFF)
	{
		stateversion = FCEU_de32lsb(header + 8);
	}
	else
	{
		stateversion=header[3] * 100;
	}
	//if(params == SSLOADPARAM_DUMMY)
	//{
	//	scan_chunks=1;
	//}
	x=ReadStateChunks(is,*(uint32*)(header+4));
	//if(params == SSLOADPARAM_DUMMY)
	//{
	//	scan_chunks=0;
	//	return 1;
	//}
	if(read_sfcpuc && stateversion<9500)
	{
		X.IRQlow=0;
	}
	if(GameStateRestore)
	{
		GameStateRestore(stateversion);
	}
	if(x)
	{
		FCEUPPU_LoadState(stateversion);
		FCEUSND_LoadState(stateversion);
		x=FCEUMOV_PostLoad();
	}
	if(fn)
	{
		//if(!x || params == SSLOADPARAM_DUMMY)  //is make_backup==2 possible??  oh well.
		//{
		//	* Oops!  Load the temporary savestate */
		//	FILE *fp;
		//
		//	if((fp=fopen(fn,"rb")))
		//	{
		//		FCEUSS_LoadFP(fp,SSLOADPARAM_NOBACKUP);
		//		fclose(fp);
		//	}
		//	unlink(fn);
		//}
		free(fn);
	}

	return(x);
}

#ifdef __QT_DRIVER__
// Qt Driver NetPlay state load handler. This is to control state loading
// during netplay, only hosts can load states and clients can request loads.
bool NetPlayStateLoadReq(EMUFILE* is);
#endif

bool FCEUSS_LoadFP(EMUFILE* is, ENUM_SSLOADPARAMS params)
{
	if(!is) return false;

	//maybe make a backup savestate
	bool backup = (params == SSLOADPARAM_BACKUP);
	EMUFILE_MEMORY msBackupSavestate;
	if(backup)
	{
		FCEUSS_SaveMS(&msBackupSavestate,Z_NO_COMPRESSION);
	}

	uint8 header[16];
	//read and analyze the header
	is->fread((char*)&header,16);
	if(memcmp(header,"FCSX",4)) {
		//its not an fceux save file.. perhaps it is an fceu savefile
		is->fseek(0,SEEK_SET);
		FCEU_state_loading_old_format = true;
		bool ret = FCEUSS_LoadFP_old(is,params)!=0;
		FCEU_state_loading_old_format = false;
		if(!ret && backup) FCEUSS_LoadFP(&msBackupSavestate,SSLOADPARAM_NOBACKUP);
		return ret;
	}

#ifdef __QT_DRIVER__
	if ( NetPlayStateLoadReq(is) )
	{
		return false;
	}
#endif

	size_t totalsize  = FCEU_de32lsb(header + 4);
	int stateversion  = FCEU_de32lsb(header + 8);
	uint32_t comprlen = FCEU_de32lsb(header + 12);

	// reinit memory_savestate
	// memory_savestate is global variable which already has its vector of bytes, so no need to allocate memory every time we use save/loadstate
	if ((memory_savestate.get_vec())->size() < totalsize)
		(memory_savestate.get_vec())->resize(totalsize);
	memory_savestate.set_len(totalsize);
	memory_savestate.unfail();
	memory_savestate.fseek(0, SEEK_SET);

	if(comprlen != ~0u)
	{
		// the savestate is compressed: read from is to compressed_buf, then decompress from compressed_buf to memory_savestate.vec
		if (compressed_buf.size() < comprlen) compressed_buf.resize(comprlen);
		is->fread(&compressed_buf[0], comprlen);

		uLongf uncomprlen = totalsize;
		int error = uncompress(memory_savestate.buf(), &uncomprlen, &compressed_buf[0], comprlen);
		if(error != Z_OK || uncomprlen != totalsize)
			return false;	// we dont need to restore the backup here because we havent messed with the emulator state yet
	}
	else
	{
		// the savestate is not compressed: just read from is to memory_savestate.vec
		is->fread(memory_savestate.buf(), totalsize);
	}

	FCEUMOV_PreLoad();

	bool x = (ReadStateChunks(&memory_savestate, totalsize) != 0);

	//mbg 5/24/08 - we don't support old states, so this shouldnt matter.
	//if(read_sfcpuc && stateversion<9500)
	//	X.IRQlow=0;

	if(GameStateRestore)
	{
		GameStateRestore(stateversion);
	}
	if (x)
	{
		FCEUPPU_LoadState(stateversion);
		FCEUSND_LoadState(stateversion);
		x=FCEUMOV_PostLoad();
	}
	else if (backup)
	{
		msBackupSavestate.fseek(0,SEEK_SET);
		FCEUSS_LoadFP(&msBackupSavestate,SSLOADPARAM_NOBACKUP);
	}

	// Post state load callback that is used to notify driver code that a new state load occurred.
	if (SPostLoad != NULL)
	{
		SPostLoad(x);
	}
	return x;
}

void FCEUSS_SetLoadCallback( void (*cb)(bool) )
{
	SPostLoad = cb;
}

bool FCEUSS_Load(const char *fname, bool display_message)
{
	fceuScopedPtr <EMUFILE> st; // fceuScopedPtr will auto delete the allocated EMUFILE at function return.
	std::string fn;

	//mbg movie - this needs to be overhauled
	////this fixes read-only toggle problems
	//if(FCEUMOV_IsRecording()) {
	//	FCEUMOV_AddCommand(0);
	//	MovieFlushHeader();
	//}

	if (geniestage == 1)
	{
		if (display_message)
			FCEU_DispMessage("Cannot load FCS in GG screen.",0);
		return false;
	}
	if (fname)
	{
		st = FCEUD_UTF8_fstream(fname, "rb");
		fn.assign(fname);
	}
	else
	{
		fn = FCEU_MakeFName(FCEUMKF_STATE,CurrentState,fname);
		st=FCEUD_UTF8_fstream(fn.c_str(),"rb");
        	lastLoadstateMade.assign(fn);
	}

	if (st.get() == NULL || (st.get()->get_fp() == NULL))
	{
		if (display_message)
		{
			FCEU_DispMessage("State %d load error.", 0, CurrentState);
			//FCEU_DispMessage("State %d load error. Filename: %s", 0, CurrentState, fn.c_str());
		}
		SaveStateStatus[CurrentState] = 0;
		return false;
	}

	//If in bot mode, don't do a backup when loading.
	//Otherwise you eat at the hard disk, since so many
	//states are being loaded.
	if (FCEUSS_LoadFP(st.get(), backupSavestates ? SSLOADPARAM_BACKUP : SSLOADPARAM_NOBACKUP))
	{
		if (fname)
		{
			char szFilename[260]={0};
			splitpath(fname, 0, 0, szFilename, 0);
			if (display_message)
			{
				FCEU_DispMessage("State %s loaded.", 0, szFilename);
				//FCEU_DispMessage("State %s loaded. Filename: %s", 0, szFilename, fn.c_str());
			}
		}
		else
		{
			if (display_message)
			{
				FCEU_DispMessage("State %d loaded.", 0, CurrentState);
				//FCEU_DispMessage("State %d loaded. Filename: %s", 0, CurrentState, fn.c_str());
			}
			SaveStateStatus[CurrentState] = 1;
		}

		#ifdef _S9XLUA_H
		if (!internalSaveLoad)
		{
			LuaSaveData saveData;

			std::string luaSaveFilename;
			luaSaveFilename.assign(fn.c_str());
			luaSaveFilename.append(".luasav");
			FILE* luaSaveFile = fopen(luaSaveFilename.c_str(), "rb");
			if(luaSaveFile)
			{
				saveData.ImportRecords(luaSaveFile);
				fclose(luaSaveFile);
			}

			CallRegisteredLuaLoadFunctions(CurrentState, saveData);
		}
		#endif

#ifdef __WIN_DRIVER__
		Update_RAM_Search(); // Update_RAM_Watch() is also called.
#endif

		//Update input display if movie is loaded
		extern uint32 cur_input_display;
		extern uint8 FCEU_GetJoyJoy(void);

		cur_input_display = FCEU_GetJoyJoy(); //Input display should show the last buttons pressed (stored in the savestate)

		return true;
	}
	else
	{
		if(!fname)
			SaveStateStatus[CurrentState] = 1;

		if (display_message)
		{
			FCEU_DispMessage("Error(s) reading state %d!", 0, CurrentState);
			//FCEU_DispMessage("Error(s) reading state %d! Filename: %s", 0, CurrentState, fn);
		}
		return 0;
	}
}

void FCEUSS_CheckStates(void)
{
	FILE *st=NULL;
	int ssel;

	for(ssel=0;ssel<10;ssel++)
	{
		st=FCEUD_UTF8fopen(FCEU_MakeFName(FCEUMKF_STATE,ssel,0),"rb");
		if(st)
		{
			SaveStateStatus[ssel]=1;
			fclose(st);
		}
		else
			SaveStateStatus[ssel]=0;
	}

	CurrentState=1;
	StateShow=0;
}

void ResetExState(void (*PreSave)(void), void (*PostSave)(void))
{
	int x;
	for(x=0;x<SFEXINDEX;x++)
	{
		if(SFMDATA[x].desc)
			FCEU_free( (void*)SFMDATA[x].desc);
	}
	// adelikat, 3/14/09:  had to add this to clear out the size parameter.  NROM(mapper 0) games were having savestate crashes if loaded after a non NROM game	because the size variable was carrying over and causing savestates to save too much data
	SFMDATA[0].s = 0;

	SPreSave = PreSave;
	SPostSave = PostSave;
	SFEXINDEX=0;
}

void AddExState(void *v, uint32 s, int type, const char *desc)
{
	//do not accept extra state information if a null pointer was provided for v, so list won't terminate early
	if (v == 0) return;
	
	if(s==~0u)
	{
		SFORMAT* sf = (SFORMAT*)v;
		std::map<std::string,bool> names;
		while(sf->v)
		{
			char tmp[5] = {0};
			memcpy(tmp,sf->desc,4);
			std::string desc = tmp;
			if(names.find(desc) != names.end())
			{
#ifdef __WIN_DRIVER__
				MessageBox(NULL,"OH NO!!! YOU HAVE AN INVALID SFORMAT! POST A BUG TICKET ALONG WITH INFO ON THE ROM YOURE USING\n","OOPS",MB_OK);
#else
				printf("OH NO!!! YOU HAVE AN INVALID SFORMAT! POST A BUG TICKET ALONG WITH INFO ON THE ROM YOURE USING\n");
#endif
				exit(0);
			}
			names[desc] = true;
			sf++;
		}
	}

	if(desc)
	{
		SFMDATA[SFEXINDEX].desc=(const char *)FCEU_malloc(strlen(desc)+1);
		strcpy( (char*)SFMDATA[SFEXINDEX].desc,desc);
	}
	else
		SFMDATA[SFEXINDEX].desc=0;
	SFMDATA[SFEXINDEX].v=v;
	SFMDATA[SFEXINDEX].s=s;
	if(type) SFMDATA[SFEXINDEX].s|=RLSB;
	if(SFEXINDEX<SFMDATA_SIZE-1)
		SFEXINDEX++;
	else
	{
		static int once=1;
		if(once)
		{
			once=0;
			FCEU_PrintError("Error in AddExState: SFEXINDEX overflow.\nSomebody made SFMDATA_SIZE too small.");
		}
	}
	SFMDATA[SFEXINDEX].v=0;		// End marker.
}

void FCEUI_SelectStateNext(int n)
{
	if(n>0)
		CurrentState=(CurrentState+1)%10;
	else
		CurrentState=(CurrentState+9)%10;
	FCEUI_SelectState(CurrentState, 1);
}

int FCEUI_SelectState(int w, int show)
{
	int oldstate=CurrentState;
	FCEUSS_CheckStates();
	if(w == -1) { StateShow = 0; return 0; } //mbg merge 7/17/06 had to make return a value

	CurrentState=w;
	if(show)
	{
		StateShow=180;
		FCEU_DispMessage("-select state-",0);
	}
	return oldstate;
}

void FCEUI_SaveState(const char *fname, bool display_message)
{
	if(!FCEU_IsValidUI(FCEUI_SAVESTATE)) return;

	StateShow = 0;

	FCEUSS_Save(fname, display_message);
}

int loadStateFailed = 0; // hack, this function should return a value instead

bool file_exists(const char * filename)
{
    if (FILE * file = fopen(filename, "r")) //I'm sure, you meant for READING =)
    {
        fclose(file);
        return true;
    }
    return false;
}
void FCEUI_LoadState(const char *fname, bool display_message)
{
	if(!FCEU_IsValidUI(FCEUI_LOADSTATE)) return;

	StateShow = 0;
	loadStateFailed = 0;

	/* For network play, be load the state locally, and then save the state to a temporary file,
	and send that.  This insures that if an older state is loaded that is missing some
	information expected in newer save states, desynchronization won't occur(at least not
	from this ;)).
	*/
	if (backupSavestates)
		BackupLoadState();	// If allowed, backup the current state before loading a new one

	if (!movie_readonly && autoMovieBackup && freshMovie) //If auto-backup is on, movie has not been altered this session and the movie is in read+write mode
	{
		FCEUI_MakeBackupMovie(false);	//Backup the movie before the contents get altered, but do not display messages
	}
	if (fname != NULL && !file_exists(fname))
	{
		loadStateFailed = 1;
		return; // state doesn't exist; exit cleanly
	}

	if (FCEUSS_Load(fname, display_message))
	{
		//in case we're loading a savestate made with old ppu, we need to make sure ppur's regs used for dividing are ready to go
		newppu_hacky_emergency_reset();

		//mbg todo netplay
#if 0 
		if(FCEUnetplay)
		{
			char *fn = strdup(FCEU_MakeFName(FCEUMKF_NPTEMP, 0, 0).c_str());
			FILE *fp;

			if((fp = fopen(fn," wb")))
			{
				if(FCEUSS_SaveFP(fp,0))
				{
					fclose(fp);
					FCEUNET_SendFile(FCEUNPCMD_LOADSTATE, fn);
				}
				else
				{
					fclose(fp);
				}

				unlink(fn);
			}

			free(fn);
		}
#endif
		freshMovie = false;		//The movie has been altered so it is no longer fresh
	} else
	{
		loadStateFailed = 1;
	}
}

void FCEU_DrawSaveStates(uint8 *XBuf)
{
	if(!StateShow) return;

	FCEU_DrawNumberRow(XBuf,SaveStateStatus,CurrentState);
	StateShow--;
}

//*************************************************************************
//Savestate backup functions
//(Used when making savestates)
//*************************************************************************

string GenerateBackupSaveStateFn(const char *fname)
{
	//This backup is for the backup "slot" for any savestate made.  Example: smb.fc0 becomes smb-bak.fc0
	string filename;
	filename = fname;	//Convert fname to a string object
	int x = filename.find_last_of("."); //Find file extension
	filename.insert(x,"-bak");		//add "-bak" before the dot.

	return filename;
}


void CreateBackupSaveState(const char *fname)
{
	string newFilename = GenerateBackupSaveStateFn(fname);	//Get backup savestate filename
	if (CheckFileExists(newFilename.c_str()))				//See if backup already exists
		remove(newFilename.c_str())	;						//If so, delete it
	rename(fname,newFilename.c_str());						//Rename savestate to backup filename
	undoSS = true;		//There is a backup savestate file to mast last loaded, so undo is possible
}

void SwapSaveState()
{
	//--------------------------------------------------------------------------------------------
	//Both files must exist
	//--------------------------------------------------------------------------------------------

	if (lastSavestateMade.empty())
	{
		FCEUI_DispMessage("Can't Undo",0);
		FCEUI_printf("Undo savestate was attempted but unsuccessful because there was not a recently used savestate.\n");
		return;		//If there is no last savestate, can't undo
	}
	string backup = GenerateBackupSaveStateFn(lastSavestateMade.c_str());	//Get filename of backup state
	if (!CheckFileExists(backup.c_str()))
	{
		FCEUI_DispMessage("Can't Undo",0);
		FCEUI_printf("Undo savestate was attempted but unsuccessful because there was not a backup of the last used savestate.\n");
		return;		//If no backup, can't undo
	}

	//--------------------------------------------------------------------------------------------
	//So both exists, now swap the last savestate and its backup
	//--------------------------------------------------------------------------------------------
	string temp = backup;					//Put backup filename in temp
	temp.append("x");						//Add x

	rename(backup.c_str(),temp.c_str());			//rename backup file to temp file
	rename(lastSavestateMade.c_str(),backup.c_str());	//rename current as backup
	rename(temp.c_str(),lastSavestateMade.c_str());		//rename backup as current

	undoSS = true;	//Just in case, if this was run, then there is definately a last savestate and backup
	if (redoSS)				//This was a redo function, so if run again it will be an undo again
		redoSS = false;
	else					//This was an undo function so next will be redo, so flag it
		redoSS = true;

	FCEUI_DispMessage("%s restored",0,backup.c_str());
	FCEUI_printf("%s restored\n",backup.c_str());
}

//------------------------------------------------------------------------------------------------------------------------------------------------------
//*************************************************************************
//Loadstate backup functions
//(Used when Loading savestates)
//*************************************************************************

string GetBackupFileName()
{
	//This backup savestate is a special one specifically made whenever a loadstate occurs so that the user's place in a movie/game is never lost
	//particularly from unintentional loadstating
	string filename;
	int x;

	filename = FCEU_MakeFName(FCEUMKF_STATE,CurrentState,0);	//Generate normal savestate filename
	x = filename.find_last_of(".");		//Find last dot
	filename = filename.substr(0,x);	//Chop off file extension
	filename.append(".bak.fc0");		//add .bak

	return filename;
}

bool CheckBackupSaveStateExist()
{
	//This function simply checks to see if the backup loadstate exists, the backup loadstate is a special savestate
	//That is made before loading any state, so that the user never loses his data
	string filename = GetBackupFileName(); //Get backup savestate filename

	//Check if this filename exists
	fstream test;
	test.open(filename.c_str(),fstream::in);

	if (test.fail())
	{
		test.close();
		return false;
	}
	else
	{
		test.close();
		return true;
	}
}

void BackupLoadState()
{
	string filename = GetBackupFileName();
	internalSaveLoad = true;
	FCEUSS_Save(filename.c_str());
	internalSaveLoad = false;
	undoLS = true;
}

void LoadBackup()
{
	if (!undoLS) return;
	string filename = GetBackupFileName();	//Get backup filename
	if (CheckBackupSaveStateExist())
	{
		//internalSaveLoad = true;
		FCEUSS_Load(filename.c_str());		//Load it
		//internalSaveLoad = false;
		redoLS = true;						//Flag redoLoadState
		undoLS = false;						//Flag that LoadBackup cannot be run again
	}
	else
		FCEUI_DispMessage("Error: Could not load %s",0,filename.c_str());
}

void RedoLoadState()
{
	if (!redoLS) return;
	if (!lastLoadstateMade.empty() && redoLS)
	{
		FCEUSS_Load(lastLoadstateMade.c_str());
		FCEUI_printf("Redoing %s\n",lastLoadstateMade.c_str());
	}
	redoLS = false;		//Flag that RedoLoadState can not be run again
	undoLS = true;		//Flag that LoadBackup can be run again
}

//-----------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------
//----------- Save State History ----------------
//-----------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------
static StateRecorderConfigData stateRecorderConfig;

class StateRecorder
{
	public:
		StateRecorder(void)
		{
			loadConfig( stateRecorderConfig );

			for (int i=0; i<ringBufSize; i++)
			{
				EMUFILE_MEMORY *em = new EMUFILE_MEMORY( 0x1000 );

				ringBuf.push_back(em);
			}
			ringStart = ringHead = ringTail = 0;
			frameCounter = 0;
			lastState = ringHead;
			loadIndexReset = false;
			lastLoadFrame = 0;
		}

		~StateRecorder(void)
		{
			for (size_t i=0; i<ringBuf.size(); i++)
			{
				delete ringBuf[i];
			}
			ringBuf.clear();
		}

		void loadConfig( StateRecorderConfigData &config )
		{
			if (config.framesBetweenSnaps < 1)
			{
				config.framesBetweenSnaps = 1;
			}
			if (config.timeBetweenSnapsMinutes < 0.0)
			{
				config.timeBetweenSnapsMinutes = 3.0f / 60.0f;
			}
			if (config.timeBetweenSnapsMinutes > config.historyDurationMinutes)
			{
				config.historyDurationMinutes = config.timeBetweenSnapsMinutes;
			}

			if (config.timingMode)
			{
				const double fhistMin  = config.historyDurationMinutes;
				const double fsnapMin  = config.timeBetweenSnapsMinutes;
				const double fnumSnaps = fhistMin / fsnapMin;

				ringBufSize = static_cast<int>( fnumSnaps + 0.5f );

				int32_t fps = FCEUI_GetDesiredFPS(); // Do >> 24 to get in Hz

				double hz = ( ((double)fps) / 16777216.0 );

				double framesPerSnapf = hz * fsnapMin * 60.0;

				framesPerSnap = static_cast<unsigned int>( framesPerSnapf + 0.50 );
			}
			else
			{
				const double fhistMin  = config.historyDurationMinutes;
				int32_t fps = FCEUI_GetDesiredFPS(); // Do >> 24 to get in Hz
				double hz = ( ((double)fps) / 16777216.0 );

				const double fsnapMin  = static_cast<double>(config.framesBetweenSnaps) / (hz * 60.0);
				const double fnumSnaps = fhistMin / fsnapMin;

				ringBufSize = static_cast<int>( fnumSnaps + 0.5f );
				framesPerSnap = config.framesBetweenSnaps;
			}

			printf("ringBufSize:%i  framesPerSnap:%i\n", ringBufSize, framesPerSnap );

			compressionLevel = config.compressionLevel;
			loadPauseTime    = config.loadPauseTimeSeconds;
			pauseOnLoad      = config.pauseOnLoad;
		}

		void update(void)
		{
			bool isPaused = EmulationPaused ? true : false;

			unsigned int curFrame = static_cast<unsigned int>(currFrameCounter);

			if (!isPaused && loadIndexReset)
			{
				ringHead = (lastState + 1) % ringBufSize;

				frameCounter = curFrame;

				loadIndexReset = false;
			}

			if (!isPaused && (curFrame > frameCounter) )
			{
				frameCounter = curFrame;

				if ( (frameCounter % framesPerSnap) == 0 )
				{
					EMUFILE_MEMORY *em = ringBuf[ ringHead ];

					em->set_len(0);

					FCEUSS_SaveMS( em, compressionLevel );

					//printf("Frame:%u  Save:%i  Size:%zu  Total:%zukB \n", frameCounter, ringHead, em->size(), dataSize() / 1024 );

					lastState = ringHead;

					ringHead = (ringHead + 1) % ringBufSize;

					if (ringStart == ringHead)
					{
						ringStart = (ringHead + 1) % ringBufSize;
					}
				}
			}
		}

		int loadStateRelativeToEnd( int numSnapsFromLatest )
		{
			if (numSnapsFromLatest < 0)
			{
				numSnapsFromLatest = 0;
			}
			numSnapsFromLatest = numSnapsFromLatest % ringBufSize;

			int snapIdx = ringHead - numSnapsFromLatest - 1;

			loadStateByIndex(snapIdx);

			return 0;
		}

		int loadStateByIndex( int snapIdx )
		{
			if (snapIdx < 0)
			{
				snapIdx = snapIdx + ringBufSize;
			}
			snapIdx = snapIdx % ringBufSize;

			EMUFILE_MEMORY *em = ringBuf[ snapIdx ];

			em->fseek(SEEK_SET, 0);

			FCEUSS_LoadFP( em, SSLOADPARAM_NOBACKUP );

			frameCounter = lastLoadFrame = static_cast<unsigned int>(currFrameCounter);

			lastState = snapIdx;
			loadIndexReset = true;

			if (pauseOnLoad == StateRecorderConfigData::TEMPORARY_PAUSE)
			{
				if (loadPauseTime > 0)
				{	// Temporary pause after loading new state for user to have time to process
					FCEUI_PauseForDuration(loadPauseTime);
				}
			}
			else if (pauseOnLoad == StateRecorderConfigData::FULL_PAUSE)
			{
				FCEUI_SetEmulationPaused( EMULATIONPAUSED_PAUSED );
			}
			return 0;
		}

		int loadPrevState(void)
		{
			int snapIdx = lastState;

			if ( lastState == ringHead )
			{	// No States to Load
				return -1;
			}
			if ( lastState != ringStart )
			{
				if ( (lastLoadFrame+30) > frameCounter)
				{
					snapIdx--;

					if (snapIdx < 0)
					{
						snapIdx += ringBufSize;
					}
				}
			}
			return loadStateByIndex( snapIdx );
		}

		int loadNextState(void)
		{
			int snapIdx =  lastState;
			int nextIdx = (lastState + 1) % ringBufSize;

			if ( nextIdx != ringHead )
			{
				snapIdx = nextIdx;
			}
			return loadStateByIndex( snapIdx );
		}

		int getHeadIndex(void)
		{
			return ringHead;
		}

		int getStartIndex(void)
		{
			return ringStart;
		}

		int numSnapsSaved(void)
		{
			int numSnaps = ringHead - ringStart;

			if (numSnaps < 0)
			{
				numSnaps = numSnaps + static_cast<int>( ringBuf.size() );
			}
			return numSnaps;
		}

		size_t  dataSize(void)
		{
			return ringBuf.size() * ringBuf[0]->size();
		}

		size_t  ringBufferSize(void)
		{
			return ringBuf.size();
		}
		static bool enabled;
		static int  lastState;
	private:

		void doSnap(void)
		{

		}

		std::vector <EMUFILE_MEMORY*> ringBuf;
		int  ringHead;
		int  ringTail;
		int  ringStart;
		int  ringBufSize;
		int  compressionLevel;
		int  loadPauseTime;
		StateRecorderConfigData::PauseType pauseOnLoad;
		unsigned int frameCounter;
		unsigned int framesPerSnap;
		unsigned int lastLoadFrame;
		bool loadIndexReset;

};

static StateRecorder *stateRecorder = nullptr;
bool StateRecorder::enabled = false;
int StateRecorder::lastState = 0;

int FCEU_StateRecorderStart(void)
{
	if (stateRecorder == nullptr)
	{
		stateRecorder = new StateRecorder();
	}
	return stateRecorder == nullptr;
}

int FCEU_StateRecorderStop(void)
{
	if (stateRecorder != nullptr)
	{
		delete stateRecorder; stateRecorder = nullptr;
	}
	return stateRecorder != nullptr;
}

int FCEU_StateRecorderUpdate(void)
{
	if (stateRecorder != nullptr)
	{
		stateRecorder->update();
	}
	return 0;
}

bool FCEU_StateRecorderIsEnabled(void)
{
	return StateRecorder::enabled;
}

void FCEU_StateRecorderSetEnabled(bool enabled)
{
	StateRecorder::enabled = enabled;
}

bool FCEU_StateRecorderRunning(void)
{
	return stateRecorder != nullptr;
}

int FCEU_StateRecorderGetMaxSnaps(void)
{
	int size = 0;

	if (stateRecorder != nullptr)
	{
		size = stateRecorder->ringBufferSize();
	}
	return size;
}

int FCEU_StateRecorderGetNumSnapsSaved(void)
{
	int n = 0;

	if (stateRecorder != nullptr)
	{
		n = stateRecorder->numSnapsSaved();
	}
	return n;
}

int FCEU_StateRecorderLoadState(int snapIndex)
{
	int ret = -1;

	if (stateRecorder != nullptr)
	{
		ret = stateRecorder->loadStateByIndex(snapIndex);
	}
	return ret;
}

int FCEU_StateRecorderGetStateIndex(void)
{
	return StateRecorder::lastState;
}

int FCEU_StateRecorderLoadPrevState(void)
{
	int ret = -1;

	if (stateRecorder != nullptr)
	{
		ret = stateRecorder->loadPrevState();
	}
	return ret;
}

int FCEU_StateRecorderLoadNextState(void)
{
	int ret = -1;

	if (stateRecorder != nullptr)
	{
		ret = stateRecorder->loadNextState();
	}
	return ret;
}

const StateRecorderConfigData& FCEU_StateRecorderGetConfigData(void)
{
	return stateRecorderConfig;
}
int FCEU_StateRecorderSetConfigData(const StateRecorderConfigData &newConfig)
{
	stateRecorderConfig = newConfig;

	if (stateRecorder != nullptr)
	{
		stateRecorder->loadConfig( stateRecorderConfig );
	}
	return 0;
}
