2009-07-17 19:27:04 +02:00
/* 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 . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
// TODO: Add (better) file io error checking
# include <string>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
//#include <unistd.h> //mbg merge 7/17/06 removed
# include <vector>
# include <fstream>
# include "types.h"
# include "x6502.h"
# include "fceu.h"
# include "sound.h"
# include "utils/endian.h"
# include "utils/memory.h"
# include "utils/memorystream.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"
using namespace std ;
2009-07-18 08:19:04 +02:00
static void ( * SPreSave ) ( void ) ;
static void ( * SPostSave ) ( void ) ;
2009-07-17 19:27:04 +02:00
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 ;
char lastSavestateMade [ 2048 ] ; //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
char lastLoadstateMade [ 2048 ] ; //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
# define SFMDATA_SIZE (64)
2009-07-18 08:19:04 +02:00
static SFORMAT SFMDATA [ SFMDATA_SIZE ] ;
2009-07-17 19:27:04 +02:00
static int SFEXINDEX ;
# define RLSB FCEUSTATE_RLSB //0x80000000
extern SFORMAT FCEUPPU_STATEINFO [ ] ;
extern SFORMAT FCEUSND_STATEINFO [ ] ;
extern SFORMAT FCEUCTRL_STATEINFO [ ] ;
extern SFORMAT FCEUMOV_STATEINFO [ ] ;
SFORMAT SFCPU [ ] = {
{ & X . PC , 2 | RLSB , " PC \0 " } ,
{ & X . A , 1 , " A \0 \0 " } ,
{ & X . P , 1 , " P \0 \0 " } ,
{ & X . X , 1 , " X \0 \0 " } ,
{ & X . Y , 1 , " Y \0 \0 " } ,
{ & X . S , 1 , " S \0 \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 ( std : : ostream * os , SFORMAT * sf )
{
uint32 acc = 0 ;
while ( sf - > v )
{
if ( sf - > s = = ~ 0 ) //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 - > write ( sf - > desc , 4 ) ;
write32le ( sf - > s & ( ~ FCEUSTATE_FLAGS ) , os ) ;
# ifndef LSB_FIRST
if ( sf - > s & RLSB )
FlipByteOrder ( ( uint8 * ) sf - > v , sf - > s & ( ~ FCEUSTATE_FLAGS ) ) ;
# endif
if ( sf - > s & FCEUSTATE_INDIRECT )
os - > write ( * ( char * * ) sf - > v , sf - > s & ( ~ FCEUSTATE_FLAGS ) ) ;
else
os - > write ( ( char * ) sf - > v , sf - > s & ( ~ FCEUSTATE_FLAGS ) ) ;
//Now restore the original byte order.
# ifndef LSB_FIRST
if ( sf - > s & RLSB )
FlipByteOrder ( ( uint8 * ) sf - > v , sf - > s & ( ~ FCEUSTATE_FLAGS ) ) ;
# endif
}
sf + + ;
}
return ( acc ) ;
}
static int WriteStateChunk ( std : : ostream * os , int type , SFORMAT * sf )
{
os - > put ( type ) ;
int bsize = SubWrite ( ( std : : ostream * ) 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 = = ~ 0 ) // 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 ( std : : istream * is , SFORMAT * sf , int size )
{
SFORMAT * tmp ;
int temp = is - > tellg ( ) ;
while ( is - > tellg ( ) < temp + size )
{
uint32 tsize ;
char toa [ 4 ] ;
if ( is - > read ( toa , 4 ) . gcount ( ) < 4 )
return false ;
read32le ( & tsize , is ) ;
if ( ( tmp = CheckS ( sf , tsize , toa ) ) )
{
if ( tmp - > s & FCEUSTATE_INDIRECT )
is - > read ( * ( char * * ) tmp - > v , tmp - > s & ( ~ FCEUSTATE_FLAGS ) ) ;
else
is - > read ( ( char * ) tmp - > v , tmp - > s & ( ~ FCEUSTATE_FLAGS ) ) ;
# ifndef LSB_FIRST
if ( tmp - > s & RLSB )
FlipByteOrder ( ( uint8 * ) tmp - > v , tmp - > s & ( ~ FCEUSTATE_FLAGS ) ) ;
# endif
}
else
is - > seekg ( tsize , std : : ios : : 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 ( std : : istream * 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 - > get ( ) ;
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 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 ) )
{
if ( ! ReadStateChunk ( is , FCEUMOV_STATEINFO , size ) ) ret = false ;
}
else
{
is - > seekg ( size , std : : ios : : cur ) ;
}
break ;
case 8 :
// load back buffer
{
extern uint8 * XBackBuf ;
if ( is - > read ( ( char * ) XBackBuf , size ) . gcount ( ) ! = 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 WIN32
else
{
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 ] ;
sprintf ( str , " Warning: Found unknown save chunk of type %d. \n This could indicate the save state is corrupted \n or made with a different (incompatible) emulator version. " , t ) ;
FCEUD_PrintError ( str ) ;
warned = true ;
}
//if(fseek(st,size,SEEK_CUR)<0) goto endo;break;
is - > seekg ( size , std : : ios : : 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 ;
}
2009-07-18 00:54:58 +02:00
int CurrentState = 0 ;
2009-07-17 19:27:04 +02:00
extern int geniestage ;
bool FCEUSS_SaveMS ( std : : ostream * outstream , int compressionLevel )
{
//a temp memory stream. we'll dump some data here and then compress
//TODO - support dumping directly without compressing to save a buffer copy
2009-07-20 08:18:06 +02:00
# ifdef GEKKO
memorystream ms ( 512 * 1024 ) ; // set aside some space, otherwise expand fails on Wii!
# else
2009-07-17 19:27:04 +02:00
memorystream ms ;
2009-07-20 08:18:06 +02:00
# endif
2009-07-17 19:27:04 +02:00
std : : ostream * os = ( std : : ostream * ) & ms ;
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 , 4 , FCEUCTRL_STATEINFO ) ;
totalsize + = WriteStateChunk ( os , 5 , FCEUSND_STATEINFO ) ;
if ( FCEUMOV_Mode ( MOVIEMODE_PLAY | MOVIEMODE_RECORD ) )
{
totalsize + = WriteStateChunk ( os , 6 , FCEUMOV_STATEINFO ) ;
//MBG tasedit HACK HACK HACK!
//do not save the movie state if we are in tasedit! that is a huge waste of time and space!
if ( ! FCEUMOV_Mode ( MOVIEMODE_TASEDIT ) )
{
os - > seekp ( 5 , std : : ios : : cur ) ;
int size = FCEUMOV_WriteState ( os ) ;
os - > seekp ( - ( size + 5 ) , std : : ios : : cur ) ;
os - > put ( 7 ) ;
write32le ( size , os ) ;
os - > seekp ( size , std : : ios : : cur ) ;
totalsize + = 5 + size ;
}
}
// save back buffer
{
extern uint8 * XBackBuf ;
uint32 size = 256 * 256 + 8 ;
os - > put ( 8 ) ;
write32le ( size , os ) ;
os - > write ( ( char * ) XBackBuf , size ) ;
totalsize + = 5 + size ;
}
if ( SPreSave ) SPreSave ( ) ;
totalsize + = WriteStateChunk ( os , 0x10 , SFMDATA ) ;
if ( SPreSave ) SPostSave ( ) ;
//save the length of the file
2009-07-20 08:18:06 +02:00
# ifdef GEKKO
int len = ms . tellp ( ) ;
# else
2009-07-17 19:27:04 +02:00
int len = ms . size ( ) ;
2009-07-20 08:18:06 +02:00
# endif
2009-07-17 19:27:04 +02:00
//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 * ) ms . buf ( ) ;
uLongf comprlen = - 1 ;
if ( compressionLevel ! = Z_NO_COMPRESSION )
{
//worst case compression.
//zlib says "0.1% larger than sourceLen plus 12 bytes"
comprlen = ( len > > 9 ) + 12 + len ;
cbuf = new uint8 [ comprlen ] ;
error = compress2 ( cbuf , & comprlen , ( uint8 * ) ms . 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 - > write ( ( char * ) header , 16 ) ;
outstream - > write ( ( char * ) cbuf , comprlen = = - 1 ? totalsize : comprlen ) ;
if ( cbuf ! = ( uint8 * ) ms . buf ( ) ) delete [ ] cbuf ;
return error = = Z_OK ;
}
void FCEUSS_Save ( const char * fname )
{
std : : fstream * st = 0 ;
char * fn ;
if ( geniestage = = 1 )
{
FCEU_DispMessage ( " Cannot save FCS in GG screen. " ) ;
return ;
}
if ( fname ) //If filename is given use it.
{
st = FCEUD_UTF8_fstream ( fname , " wb " ) ;
}
else //Else, generate one
{
//FCEU_PrintError("daCurrentState=%d",CurrentState);
fn = strdup ( FCEU_MakeFName ( FCEUMKF_STATE , CurrentState , 0 ) . c_str ( ) ) ;
//backup existing savestate first
2009-07-18 08:19:04 +02:00
if ( CheckFileExists ( fn ) )
2009-07-17 19:27:04 +02:00
{
CreateBackupSaveState ( fn ) ; //Make a backup of previous savestate before overwriting it
strcpy ( lastSavestateMade , 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
2009-07-18 08:19:04 +02:00
2009-07-17 19:27:04 +02:00
st = FCEUD_UTF8_fstream ( fn , " wb " ) ;
free ( fn ) ;
}
if ( st = = NULL )
{
FCEU_DispMessage ( " State %d save error. " , CurrentState ) ;
return ;
}
if ( FCEUMOV_Mode ( MOVIEMODE_INACTIVE ) )
FCEUSS_SaveMS ( st , - 1 ) ;
else
FCEUSS_SaveMS ( st , 0 ) ;
delete st ;
if ( ! fname )
{
SaveStateStatus [ CurrentState ] = 1 ;
FCEU_DispMessage ( " State %d saved. " , CurrentState ) ;
}
redoSS = false ; //we have a new savestate so redo is not possible
}
int FCEUSS_LoadFP_old ( std : : istream * 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;
2009-07-18 08:19:04 +02:00
//
2009-07-17 19:27:04 +02:00
// 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 - > read ( ( 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 ) ;
2009-07-18 08:19:04 +02:00
FCEUSND_LoadState ( stateversion ) ;
2009-07-17 19:27:04 +02:00
x = FCEUMOV_PostLoad ( ) ;
}
if ( fn )
{
//if(!x || params == SSLOADPARAM_DUMMY) //is make_backup==2 possible?? oh well.
//{
// * Oops! Load the temporary savestate */
// FILE *fp;
2009-07-18 08:19:04 +02:00
//
2009-07-17 19:27:04 +02:00
// if((fp=fopen(fn,"rb")))
// {
// FCEUSS_LoadFP(fp,SSLOADPARAM_NOBACKUP);
// fclose(fp);
// }
// unlink(fn);
//}
free ( fn ) ;
}
return ( x ) ;
}
bool FCEUSS_LoadFP ( std : : istream * is , ENUM_SSLOADPARAMS params )
{
//maybe make a backup savestate
memorystream msBackupSavestate ;
bool backup = ( params = = SSLOADPARAM_BACKUP ) ;
if ( backup )
FCEUSS_SaveMS ( & msBackupSavestate , Z_NO_COMPRESSION ) ;
uint8 header [ 16 ] ;
//read and analyze the header
is - > read ( ( char * ) & header , 16 ) ;
if ( memcmp ( header , " FCSX " , 4 ) ) {
//its not an fceux save file.. perhaps it is an fceu savefile
is - > seekg ( 0 ) ;
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 ;
}
2009-07-18 08:19:04 +02:00
2009-07-17 19:27:04 +02:00
int totalsize = FCEU_de32lsb ( header + 4 ) ;
int stateversion = FCEU_de32lsb ( header + 8 ) ;
int comprlen = FCEU_de32lsb ( header + 12 ) ;
std : : vector < char > buf ( totalsize ) ;
//not compressed:
if ( comprlen ! = - 1 )
{
//load the compressed chunk and decompress
std : : vector < char > cbuf ( comprlen ) ;
is - > read ( ( char * ) & cbuf [ 0 ] , comprlen ) ;
uLongf uncomprlen = totalsize ;
int error = uncompress ( ( uint8 * ) & buf [ 0 ] , & uncomprlen , ( uint8 * ) & cbuf [ 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
{
is - > read ( ( char * ) & buf [ 0 ] , totalsize ) ;
}
FCEUMOV_PreLoad ( ) ;
memorystream mstemp ( & buf ) ;
bool x = ReadStateChunks ( & mstemp , 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 ( ) ;
}
if ( ! x & & backup ) {
msBackupSavestate . sync ( ) ;
FCEUSS_LoadFP ( & msBackupSavestate , SSLOADPARAM_NOBACKUP ) ;
}
return x ;
}
bool FCEUSS_Load ( const char * fname )
{
std : : fstream * st ;
//mbg movie - this needs to be overhauled
////this fixes read-only toggle problems
//if(FCEUMOV_IsRecording()) {
// FCEUMOV_AddCommand(0);
// MovieFlushHeader();
//}
if ( geniestage = = 1 )
{
FCEU_DispMessage ( " Cannot load FCS in GG screen. " ) ;
return false ;
}
if ( fname )
{
st = FCEUD_UTF8_fstream ( fname , " rb " ) ;
}
else
{
string fn = FCEU_MakeFName ( FCEUMKF_STATE , CurrentState , fname ) ;
st = FCEUD_UTF8_fstream ( fn , " rb " ) ;
strcpy ( lastLoadstateMade , fn . c_str ( ) ) ;
}
if ( st = = NULL )
{
FCEU_DispMessage ( " State %d load error. " , CurrentState ) ;
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 , SSLOADPARAM_BACKUP ) )
{
if ( fname )
{
char szFilename [ 260 ] = { 0 } ;
splitpath ( fname , 0 , 0 , szFilename , 0 ) ;
FCEU_DispMessage ( " State %s loaded. " , szFilename ) ;
}
else
{
//This looks redudant to me... but why bother deleting it:)
SaveStateStatus [ CurrentState ] = 1 ;
FCEU_DispMessage ( " State %d loaded. " , CurrentState ) ;
SaveStateStatus [ CurrentState ] = 1 ;
}
delete st ;
return true ;
}
else
{
if ( ! fname )
{
SaveStateStatus [ CurrentState ] = 1 ;
}
FCEU_DispMessage ( " Error(s) reading state %d! " , CurrentState ) ;
delete st ;
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 )
free ( 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
2009-07-18 08:19:04 +02:00
SFMDATA [ 0 ] . s = 0 ;
2009-07-17 19:27:04 +02:00
SPreSave = PreSave ;
SPostSave = PostSave ;
SFEXINDEX = 0 ;
}
void AddExState ( void * v , uint32 s , int type , char * desc )
{
if ( desc )
{
SFMDATA [ SFEXINDEX ] . desc = ( char * ) FCEU_malloc ( 5 ) ;
strcpy ( 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. \n Somebody 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 )
{
FCEUSS_CheckStates ( ) ;
int oldstate = CurrentState ;
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- " ) ;
}
return oldstate ;
}
void FCEUI_SaveState ( const char * fname )
{
if ( ! FCEU_IsValidUI ( FCEUI_SAVESTATE ) ) return ;
StateShow = 0 ;
2009-07-18 08:19:04 +02:00
2009-07-17 19:27:04 +02:00
FCEUSS_Save ( fname ) ;
}
int loadStateFailed = 0 ; // hack, this function should return a value instead
void FCEUI_LoadState ( const char * fname )
{
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 ; ) ) .
*/
BackupLoadState ( ) ; //Backup the current state before loading a new one
2009-07-18 08:19:04 +02:00
2009-07-17 19:27:04 +02:00
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
{
2009-07-18 08:19:04 +02:00
FCEUI_MakeBackupMovie ( false ) ; //Backup the movie before the contents get altered, but do not display messages
2009-07-17 19:27:04 +02:00
}
if ( FCEUSS_Load ( fname ) )
{
//mbg todo netplay
/*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 ) ;
} */
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
2009-07-18 08:19:04 +02:00
filename . insert ( x , " -bak " ) ; //add "-bak" before the dot.
2009-07-17 19:27:04 +02:00
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
//--------------------------------------------------------------------------------------------
2009-07-18 08:19:04 +02:00
if ( ! lastSavestateMade )
2009-07-17 19:27:04 +02:00
{
FCEUI_DispMessage ( " Can't Undo " ) ;
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 ) ; //Get filename of backup state
2009-07-18 08:19:04 +02:00
if ( ! CheckFileExists ( backup . c_str ( ) ) )
2009-07-17 19:27:04 +02:00
{
FCEUI_DispMessage ( " Can't Undo " ) ;
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
2009-07-18 08:19:04 +02:00
2009-07-17 19:27:04 +02:00
rename ( backup . c_str ( ) , temp . c_str ( ) ) ; //rename backup file to temp file
rename ( lastSavestateMade , backup . c_str ( ) ) ; //rename current as backup
rename ( temp . c_str ( ) , lastSavestateMade ) ; //rename backup as current
2009-07-18 08:19:04 +02:00
2009-07-17 19:27:04 +02:00
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 " , backup . c_str ( ) ) ;
FCEUI_printf ( " %s restored \n " , backup . c_str ( ) ) ;
2009-07-18 08:19:04 +02:00
}
2009-07-17 19:27:04 +02:00
//------------------------------------------------------------------------------------------------------------------------------------------------------
//*************************************************************************
//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 ;
2009-07-18 08:19:04 +02:00
2009-07-17 19:27:04 +02:00
filename = strdup ( FCEU_MakeFName ( FCEUMKF_STATE , CurrentState , 0 ) . c_str ( ) ) ; //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
2009-07-18 08:19:04 +02:00
2009-07-17 19:27:04 +02:00
//Check if this filename exists
fstream test ;
test . open ( filename . c_str ( ) , fstream : : in ) ;
2009-07-18 08:19:04 +02:00
2009-07-17 19:27:04 +02:00
if ( test . fail ( ) )
{
test . close ( ) ;
return false ;
}
else
{
test . close ( ) ;
return true ;
}
}
void BackupLoadState ( )
{
string filename = GetBackupFileName ( ) ;
FCEUSS_Save ( filename . c_str ( ) ) ;
undoLS = true ;
}
void LoadBackup ( )
{
if ( ! undoLS ) return ;
string filename = GetBackupFileName ( ) ; //Get backup filename
if ( CheckBackupSaveStateExist ( ) )
{
FCEUSS_Load ( filename . c_str ( ) ) ; //Load it
redoLS = true ; //Flag redoLoadState
undoLS = false ; //Flag that LoadBackup cannot be run again
}
else
FCEUI_DispMessage ( " Error: Could not load %s " , filename . c_str ( ) ) ;
}
void RedoLoadState ( )
{
if ( ! redoLS ) return ;
if ( lastLoadstateMade & & redoLS )
{
FCEUSS_Load ( lastLoadstateMade ) ;
FCEUI_printf ( " Redoing %s \n " , lastLoadstateMade ) ;
}
redoLS = false ; //Flag that RedoLoadState can not be run again
undoLS = true ; //Flag that LoadBackup can be run again
2009-07-18 08:19:04 +02:00
}