Added initial support for state loading and saving. The relevant methods

are in System.cxx, and can be accessed from the various ports through
the Console.

To save state, call theConsole->system.saveState(string filename, string
md5sum).  The filename should include the full path and can be anything.
The md5sum is obvious and is used to make sure that the filename is actually
a state file for the current ROM.

To load state, call theConsole->system().loadState(string filename, string
md5sum.  These variables have the same meaning as above.

For now, only the SDL port has access to these.  Support will be added for
all ports when I do some more testing.

I'd appreciate some bug reports, especially related to sound handing on
state load.


git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@93 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
stephena 2002-05-13 19:10:25 +00:00
parent 2cafb1d3f0
commit 3e3ca7d10e
10 changed files with 503 additions and 22 deletions

View File

@ -13,13 +13,15 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: Device.hxx,v 1.1.1.1 2001-12-27 19:54:29 bwmott Exp $
// $Id: Device.hxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
//============================================================================
#ifndef DEVICE_HXX
#define DEVICE_HXX
class System;
class Serializer;
class Deserializer;
#include "bspf.hxx"
@ -28,7 +30,7 @@ class System;
based system.
@author Bradford W. Mott
@version $Id: Device.hxx,v 1.1.1.1 2001-12-27 19:54:29 bwmott Exp $
@version $Id: Device.hxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
*/
class Device
{
@ -71,6 +73,22 @@ class Device
*/
virtual void install(System& system) = 0;
/**
Saves the current state of this device to the given Serializer.
@param out The serializer device to save to.
@return The result of the save. True on success, false on failure.
*/
virtual bool save(Serializer& out) = 0;
/**
Loads the current state of this device from the given Deserializer.
@param in The deserializer device to load from.
@return The result of the load. True on success, false on failure.
*/
virtual bool load(Deserializer& in) = 0;
public:
/**
Get the byte at the specified address

View File

@ -13,7 +13,7 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: M6502.hxx,v 1.1.1.1 2001-12-27 19:54:30 bwmott Exp $
// $Id: M6502.hxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
//============================================================================
#ifndef M6502_HXX
@ -21,6 +21,8 @@
class D6502;
class M6502;
class Serializer;
class Deserializer;
#include "bspf.hxx"
#include "System.hxx"
@ -31,7 +33,7 @@ class M6502;
has a 64K addressing space.
@author Bradford W. Mott
@version $Id: M6502.hxx,v 1.1.1.1 2001-12-27 19:54:30 bwmott Exp $
@version $Id: M6502.hxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
*/
class M6502
{
@ -94,6 +96,29 @@ class M6502
*/
virtual void nmi();
/**
Saves the current state of this device to the given Serializer.
@param out The serializer device to save to.
@return The result of the save. True on success, false on failure.
*/
virtual bool save(Serializer& out) = 0;
/**
Loads the current state of this device from the given Deserializer.
@param in The deserializer device to load from.
@return The result of the load. True on success, false on failure.
*/
virtual bool load(Deserializer& in) = 0;
/**
Get a null terminated string which is the processor's name (i.e. "M6532")
@return The name of the device
*/
virtual const char* name() const = 0;
public:
/**
Get the addressing mode of the specified instruction

View File

@ -13,10 +13,12 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: M6502Hi.cxx,v 1.1.1.1 2001-12-27 19:54:30 bwmott Exp $
// $Id: M6502Hi.cxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
//============================================================================
#include "M6502Hi.hxx"
#include "Serializer.hxx"
#include "Deserializer.hxx"
#define debugStream cout
@ -167,3 +169,100 @@ void M6502High::interruptHandler()
myExecutionStatus &= ~(MaskableInterruptBit | NonmaskableInterruptBit);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool M6502High::save(Serializer& out)
{
string CPU = name();
try
{
out.putString(CPU);
out.putLong(A); // Accumulator
out.putLong(X); // X index register
out.putLong(Y); // Y index register
out.putLong(SP); // Stack Pointer
out.putLong(IR); // Instruction register
out.putLong(PC); // Program Counter
out.putBool(N); // N flag for processor status register
out.putBool(V); // V flag for processor status register
out.putBool(B); // B flag for processor status register
out.putBool(D); // D flag for processor status register
out.putBool(I); // I flag for processor status register
out.putBool(notZ); // Z flag complement for processor status register
out.putBool(C); // C flag for processor status register
out.putLong(myExecutionStatus);
// Indicates the number of distinct memory accesses
out.putLong(myNumberOfDistinctAccesses);
// Indicates the last address which was accessed
out.putLong(myLastAddress);
}
catch(char *msg)
{
cerr << msg << endl;
return false;
}
catch(...)
{
cerr << "Unknown error in save state for " << CPU << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool M6502High::load(Deserializer& in)
{
string CPU = name();
try
{
if(in.getString() != CPU)
return false;
A = (uInt8) in.getLong(); // Accumulator
X = (uInt8) in.getLong(); // X index register
Y = (uInt8) in.getLong(); // Y index register
SP = (uInt8) in.getLong(); // Stack Pointer
IR = (uInt8) in.getLong(); // Instruction register
PC = (uInt16) in.getLong(); // Program Counter
N = in.getBool(); // N flag for processor status register
V = in.getBool(); // V flag for processor status register
B = in.getBool(); // B flag for processor status register
D = in.getBool(); // D flag for processor status register
I = in.getBool(); // I flag for processor status register
notZ = in.getBool(); // Z flag complement for processor status register
C = in.getBool(); // C flag for processor status register
myExecutionStatus = (uInt8) in.getLong();
// Indicates the number of distinct memory accesses
myNumberOfDistinctAccesses = (uInt32) in.getLong();
// Indicates the last address which was accessed
myLastAddress = (uInt16) in.getLong();
}
catch(char *msg)
{
cerr << msg << endl;
return false;
}
catch(...)
{
cerr << "Unknown error in load state for " << CPU << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const char* M6502High::name() const
{
return "M6502High";
}

View File

@ -13,13 +13,15 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: M6502Hi.hxx,v 1.1.1.1 2001-12-27 19:54:30 bwmott Exp $
// $Id: M6502Hi.hxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
//============================================================================
#ifndef M6502HIGH_HXX
#define M6502HIGH_HXX
class M6502High;
class Serializer;
class Deserializer;
#include "bspf.hxx"
#include "M6502.hxx"
@ -33,7 +35,7 @@ class M6502High;
effects and for games which are very time sensitive.
@author Bradford W. Mott
@version $Id: M6502Hi.hxx,v 1.1.1.1 2001-12-27 19:54:30 bwmott Exp $
@version $Id: M6502Hi.hxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
*/
class M6502High : public M6502
{
@ -62,6 +64,29 @@ class M6502High : public M6502
*/
virtual bool execute(uInt32 number);
/**
Saves the current state of this device to the given Serializer.
@param out The serializer device to save to.
@return The result of the save. True on success, false on failure.
*/
virtual bool save(Serializer& out);
/**
Loads the current state of this device from the given Deserializer.
@param in The deserializer device to load from.
@return The result of the load. True on success, false on failure.
*/
virtual bool load(Deserializer& in);
/**
Get a null terminated string which is the processors's name (i.e. "M6532")
@return The name of the device
*/
virtual const char* name() const;
public:
/**
Get the number of memory accesses to distinct memory locations

View File

@ -13,10 +13,12 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: M6502Low.cxx,v 1.1.1.1 2001-12-27 19:54:31 bwmott Exp $
// $Id: M6502Low.cxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
//============================================================================
#include "M6502Low.hxx"
#include "Serializer.hxx"
#include "Deserializer.hxx"
#define debugStream cout
@ -157,3 +159,89 @@ void M6502Low::interruptHandler()
myExecutionStatus &= ~(MaskableInterruptBit | NonmaskableInterruptBit);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool M6502Low::save(Serializer& out)
{
string CPU = name();
try
{
out.putString(CPU);
out.putLong(A); // Accumulator
out.putLong(X); // X index register
out.putLong(Y); // Y index register
out.putLong(SP); // Stack Pointer
out.putLong(IR); // Instruction register
out.putLong(PC); // Program Counter
out.putBool(N); // N flag for processor status register
out.putBool(V); // V flag for processor status register
out.putBool(B); // B flag for processor status register
out.putBool(D); // D flag for processor status register
out.putBool(I); // I flag for processor status register
out.putBool(notZ); // Z flag complement for processor status register
out.putBool(C); // C flag for processor status register
out.putLong(myExecutionStatus);
}
catch(char *msg)
{
cerr << msg << endl;
return false;
}
catch(...)
{
cerr << "Unknown error in save state for " << CPU << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool M6502Low::load(Deserializer& in)
{
string CPU = name();
try
{
if(in.getString() != CPU)
return false;
A = (uInt8) in.getLong(); // Accumulator
X = (uInt8) in.getLong(); // X index register
Y = (uInt8) in.getLong(); // Y index register
SP = (uInt8) in.getLong(); // Stack Pointer
IR = (uInt8) in.getLong(); // Instruction register
PC = (uInt16) in.getLong(); // Program Counter
N = in.getBool(); // N flag for processor status register
V = in.getBool(); // V flag for processor status register
B = in.getBool(); // B flag for processor status register
D = in.getBool(); // D flag for processor status register
I = in.getBool(); // I flag for processor status register
notZ = in.getBool(); // Z flag complement for processor status register
C = in.getBool(); // C flag for processor status register
myExecutionStatus = (uInt8) in.getLong();
}
catch(char *msg)
{
cerr << msg << endl;
return false;
}
catch(...)
{
cerr << "Unknown error in load state for " << CPU << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const char* M6502Low::name() const
{
return "M6502Low";
}

View File

@ -13,13 +13,15 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: M6502Low.hxx,v 1.1.1.1 2001-12-27 19:54:31 bwmott Exp $
// $Id: M6502Low.hxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
//============================================================================
#ifndef M6507LOW_HXX
#define M6507LOW_HXX
class M6507Low;
class Serializer;
class Deserializer;
#include "bspf.hxx"
#include "M6502.hxx"
@ -39,7 +41,7 @@ class M6507Low;
better compatibility is neccessary use one of the other 6502 classes.
@author Bradford W. Mott
@version $Id: M6502Low.hxx,v 1.1.1.1 2001-12-27 19:54:31 bwmott Exp $
@version $Id: M6502Low.hxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
*/
class M6502Low : public M6502
{
@ -68,6 +70,29 @@ class M6502Low : public M6502
*/
virtual bool execute(uInt32 number);
/**
Saves the current state of this device to the given Serializer.
@param out The serializer device to save to.
@return The result of the save. True on success, false on failure.
*/
virtual bool save(Serializer& out);
/**
Loads the current state of this device from the given Deserializer.
@param in The deserializer device to load from.
@return The result of the load. True on success, false on failure.
*/
virtual bool load(Deserializer& in);
/**
Get a null terminated string which is the processors's name (i.e. "M6532")
@return The name of the device
*/
virtual const char* name() const;
protected:
/**
Called after an interrupt has be requested using irq() or nmi()

View File

@ -13,10 +13,12 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: NullDev.cxx,v 1.1.1.1 2001-12-27 19:54:31 bwmott Exp $
// $Id: NullDev.cxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
//============================================================================
#include "NullDev.hxx"
#include "Serializer.hxx"
#include "Deserializer.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NullDevice::NullDevice()
@ -58,3 +60,14 @@ void NullDevice::poke(uInt16 address, uInt8 value)
cerr << hex << "NullDevice: poke(" << address << "," << value << ")" << endl;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool NullDevice::save(Serializer& out)
{
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool NullDevice::load(Deserializer& in)
{
return true;
}

View File

@ -13,13 +13,15 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: NullDev.hxx,v 1.1.1.1 2001-12-27 19:54:31 bwmott Exp $
// $Id: NullDev.hxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
//============================================================================
#ifndef NULLDEVICE_HXX
#define NULLDEVICE_HXX
class System;
class Serializer;
class Deserializer;
#include "bspf.hxx"
#include "Device.hxx"
@ -30,7 +32,7 @@ class System;
holes in the address space (i.e. no real device attached).
@author Bradford W. Mott
@version $Id: NullDev.hxx,v 1.1.1.1 2001-12-27 19:54:31 bwmott Exp $
@version $Id: NullDev.hxx,v 1.2 2002-05-13 19:10:25 stephena Exp $
*/
class NullDevice : public Device
{
@ -66,6 +68,22 @@ class NullDevice : public Device
*/
virtual void install(System& system);
/**
Saves the current state of this device to the given Serializer.
@param out The serializer device to save to.
@return The result of the save. True on success, false on failure.
*/
virtual bool save(Serializer& out);
/**
Loads the current state of this device from the given Deserializer.
@param in The deserializer device to load from.
@return The result of the load. True on success, false on failure.
*/
virtual bool load(Deserializer& in);
public:
/**
Get the byte at the specified address

View File

@ -13,13 +13,17 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: System.cxx,v 1.2 2001-12-30 18:36:02 bwmott Exp $
// $Id: System.cxx,v 1.3 2002-05-13 19:10:25 stephena Exp $
//============================================================================
#include <assert.h>
#include <iostream>
#include "Device.hxx"
#include "M6502.hxx"
#include "System.hxx"
#include "Serializer.hxx"
#include "Deserializer.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
System::System(uInt16 n, uInt16 m)
@ -47,6 +51,10 @@ System::System(uInt16 n, uInt16 m)
{
setPageAccess(page, access);
}
// Set up (de)serializer in case we are asked to save/load state
serializer = new Serializer();
deserializer = new Deserializer();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -63,6 +71,12 @@ System::~System()
// Free my page access table
delete[] myPageAccessTable;
// Free the serializer stuff
if(serializer)
delete serializer;
if(deserializer)
delete deserializer;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -71,7 +85,7 @@ void System::reset()
// Reset system cycle counter
resetCycles();
// Frist we reset the devices attached to myself
// First we reset the devices attached to myself
for(uInt32 i = 0; i < myNumberOfDevices; ++i)
{
myDevices[i]->reset();
@ -106,10 +120,60 @@ void System::attach(M6502* m6502)
myM6502->install(*this);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool System::save(Serializer& out)
{
string name = "System";
try
{
out.putString(name);
out.putLong(myCycles);
}
catch(char *msg)
{
cerr << msg << endl;
return false;
}
catch(...)
{
cerr << "Unknown error in save state for " << name << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool System::load(Deserializer& in)
{
string name = "System";
try
{
if(in.getString() != name)
return false;
myCycles = (uInt32) in.getLong();
}
catch(char *msg)
{
cerr << msg << endl;
return false;
}
catch(...)
{
cerr << "Unknown error in load state for " << name << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void System::resetCycles()
{
// Frist we let all of the device attached to me know about the reset
// First we let all of the device attached to me know about the reset
for(uInt32 i = 0; i < myNumberOfDevices; ++i)
{
myDevices[i]->systemCyclesReset();
@ -140,6 +204,67 @@ const System::PageAccess& System::getPageAccess(uInt16 page)
return myPageAccessTable[page];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int System::saveState(string &fileName, string& md5sum)
{
// Open the file as a new Serializer
if(!serializer->open(fileName))
return 2;
// Prepend the state file with the md5sum of this cartridge
// This is the first defensive check for an invalid state file
serializer->putString(md5sum);
// First save state for this system
if(!save(*serializer))
return 3;
// Next, save state for the CPU
if(!myM6502->save(*serializer))
return 3;
// Now save the state of each device
for(uInt32 i = 0; i < myNumberOfDevices; ++i)
{
if(!myDevices[i]->save(*serializer))
return 3;
}
serializer->close();
return 1; // success
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int System::loadState(string &fileName, string& md5sum)
{
// Open the file as a new Deserializer
if(!deserializer->open(fileName))
return 2;
// Look at the beginning of the state file. It should contain the md5sum
// of the current cartridge. If it doesn't, this state file is invalid.
if(deserializer->getString() != md5sum)
return 3;
// First load state for this system
if(!load(*deserializer))
return 3;
// Next, load state for the CPU
if(!myM6502->load(*deserializer))
return 3;
// Now load the state of each device
for(uInt32 i = 0; i < myNumberOfDevices; ++i)
{
if(!myDevices[i]->load(*deserializer))
return 3;
}
deserializer->close();
return 1; // success
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
System::System(const System& s)
: myAddressMask(s.myAddressMask),
@ -157,5 +282,3 @@ System& System::operator = (const System&)
return *this;
}

View File

@ -13,7 +13,7 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: System.hxx,v 1.2 2001-12-30 18:36:02 bwmott Exp $
// $Id: System.hxx,v 1.3 2002-05-13 19:10:25 stephena Exp $
//============================================================================
#ifndef SYSTEM_HXX
@ -22,6 +22,8 @@
class Device;
class M6502;
class NullDevice;
class Serializer;
class Deserializer;
#include "bspf.hxx"
#include "Device.hxx"
@ -44,7 +46,7 @@ class NullDevice;
dynamic code for that page of memory.
@author Bradford W. Mott
@version $Id: System.hxx,v 1.2 2001-12-30 18:36:02 bwmott Exp $
@version $Id: System.hxx,v 1.3 2002-05-13 19:10:25 stephena Exp $
*/
class System
{
@ -70,6 +72,22 @@ class System
*/
void reset();
/**
Saves the current state of this system class to the given Serializer.
@param out The serializer device to save to.
@return The result of the save. True on success, false on failure.
*/
bool save(Serializer& out);
/**
Loads the current state of this system class from the given Deserializer.
@param in The deserializer device to load from.
@return The result of the load. True on success, false on failure.
*/
bool load(Deserializer& in);
public:
/**
Attach the specified device and claim ownership of it. The device
@ -87,6 +105,30 @@ class System
*/
void attach(M6502* m6502);
/**
Saves the current state of Stella to the given file. Calls
save on every device and CPU attached to this system.
@param out The serializer device to save to.
@return The result of the save. Error codes as follows:
1 success
2 file could not be opened for read/write
3 invalid state file
*/
int saveState(string& fileName, string& md5sum);
/**
Loads the current state of Stella from the given file. Calls
load on every device and CPU attached to this system.
@param in The deserializer device to load from.
@return The result of the load. Error codes as follows:
1 success
2 file could not be opened for read/write
3 invalid state file
*/
int loadState(string& fileName, string& md5sum);
public:
/**
Answer the 6502 microprocessor attached to the system. If a
@ -278,6 +320,12 @@ class System
// The current state of the Data Bus
uInt8 myDataBusState;
// The serializer for the system. Used to save state.
Serializer* serializer;
// The deserializer for the system. Used to load state.
Deserializer* deserializer;
private:
// Copy constructor isn't supported by this class so make it private
System(const System&);
@ -332,4 +380,3 @@ inline void System::poke(uInt16 addr, uInt8 value)
myDataBusState = value;
}
#endif