diff --git a/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs b/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs
index 02f8666d93..178af1dcd9 100644
--- a/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs
+++ b/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs
@@ -286,6 +286,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
{
public bool IsSGB { get; private set; }
+ /// disable all external callbacks. the front end should not even know the core is frame advancing
+ bool nocallbacks = false;
+
bool disposed = false;
public void Dispose()
{
@@ -439,7 +442,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
this.DeterministicEmulation = DeterministicEmulation;
if (DeterministicEmulation) // save frame-0 savestate now
- CoreSaveStateInternal(true);
+ {
+ MemoryStream ms = new MemoryStream();
+ BinaryWriter bw = new BinaryWriter(ms);
+ bw.Write(CoreSaveState());
+ bw.Write(true); // framezero, so no controller follows and don't frameadvance on load
+ bw.Close();
+ savestatebuff = ms.ToArray();
+ }
}
//must keep references to these so that they wont get garbage collected
@@ -453,7 +463,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
ushort snes_input_state(int port, int device, int index, int id)
{
- if (CoreInputComm.InputCallback != null) CoreInputComm.InputCallback();
+ if (!nocallbacks && CoreInputComm.InputCallback != null) CoreInputComm.InputCallback();
//Console.WriteLine("{0} {1} {2} {3}", port, device, index, id);
string key = "P" + (1 + port) + " ";
@@ -553,6 +563,21 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
public void FrameAdvance(bool render, bool rendersound)
{
+ // for deterministic emulation, save the state we're going to use before frame advance
+ // don't do this during nocallbacks though, since it's already been done
+ if (!nocallbacks && DeterministicEmulation)
+ {
+ MemoryStream ms = new MemoryStream();
+ BinaryWriter bw = new BinaryWriter(ms);
+ bw.Write(CoreSaveState());
+ bw.Write(false); // not framezero
+ SnesSaveController ssc = new SnesSaveController();
+ ssc.CopyFrom(Controller);
+ ssc.Serialize(bw);
+ bw.Close();
+ savestatebuff = ms.ToArray();
+ }
+
// speedup when sound rendering is not needed
if (!rendersound)
LibsnesDll.snes_set_audio_sample(null);
@@ -591,11 +616,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
if (IsLagFrame)
LagCount++;
- if (DeterministicEmulation)
- {
- // save the one internal savestate for this frame now
- CoreSaveStateInternal(true);
- }
}
public DisplayType DisplayType
@@ -695,6 +715,104 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
}
public void ResetFrameCounter() { timeFrameCounter = 0; }
+
+ #region savestates
+
+ ///
+ /// can freeze a copy of a controller input set and serialize\deserialize it
+ ///
+ class SnesSaveController : IController
+ {
+ // this is all rather general, so perhaps should be moved out of LibsnesCore
+
+ ControllerDefinition def;
+
+ public SnesSaveController()
+ {
+ this.def = null;
+ }
+
+ public SnesSaveController(ControllerDefinition def)
+ {
+ this.def = def;
+ }
+
+ WorkingDictionary buttons = new WorkingDictionary();
+
+ ///
+ /// invalid until CopyFrom has been called
+ ///
+ public ControllerDefinition Type
+ {
+ get { return def; }
+ }
+
+ public void Serialize(BinaryWriter b)
+ {
+ b.Write(buttons.Keys.Count);
+ foreach (var k in buttons.Keys)
+ {
+ b.Write(k);
+ b.Write(buttons[k]);
+ }
+ }
+
+ ///
+ /// no checking to see if the deserialized controls match any definition
+ ///
+ ///
+ public void DeSerialize(BinaryReader b)
+ {
+ buttons.Clear();
+ int numbuttons = b.ReadInt32();
+ for (int i = 0; i < numbuttons; i++)
+ {
+ string k = b.ReadString();
+ float v = b.ReadSingle();
+ buttons.Add(k, v);
+ }
+ }
+
+ ///
+ /// this controller's definition changes to that of source
+ ///
+ ///
+ public void CopyFrom(IController source)
+ {
+ this.def = source.Type;
+ buttons.Clear();
+ foreach (var k in def.BoolButtons)
+ buttons.Add(k, source.IsPressed(k) ? 1.0f : 0);
+ foreach (var k in def.FloatControls)
+ {
+ if (buttons.Keys.Contains(k))
+ throw new Exception("name collision between bool and float lists!");
+ buttons.Add(k, source.GetFloat(k));
+ }
+ }
+
+ public bool this[string button]
+ {
+ get { return buttons[button] != 0; }
+ }
+
+ public bool IsPressed(string button)
+ {
+ return buttons[button] != 0;
+ }
+
+ public float GetFloat(string name)
+ {
+ return buttons[name];
+ }
+
+ public void UpdateControls(int frame)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+
public void SaveStateText(TextWriter writer)
{
var temp = SaveStateBinary();
@@ -712,8 +830,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
public void SaveStateBinary(BinaryWriter writer)
{
- byte[] buf = CoreSaveState();
- writer.Write(buf);
+ if (!DeterministicEmulation)
+ writer.Write(CoreSaveState());
+ else
+ writer.Write(savestatebuff);
// other variables
writer.Write(IsLagFrame);
@@ -728,6 +848,30 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
byte[] buf = reader.ReadBytes(size);
CoreLoadState(buf);
+ if (DeterministicEmulation) // deserialize controller and fast-foward now
+ {
+ // reconstruct savestatebuff at the same time to avoid a costly core serialize
+ MemoryStream ms = new MemoryStream();
+ BinaryWriter bw = new BinaryWriter(ms);
+ bw.Write(buf);
+ bool framezero = reader.ReadBoolean();
+ bw.Write(framezero);
+ if (!framezero)
+ {
+ SnesSaveController ssc = new SnesSaveController(ControllerDefinition);
+ ssc.DeSerialize(reader);
+ IController tmp = this.Controller;
+ this.Controller = ssc;
+ nocallbacks = true;
+ FrameAdvance(false, false);
+ nocallbacks = false;
+ this.Controller = tmp;
+ ssc.Serialize(bw);
+ }
+ bw.Close();
+ savestatebuff = ms.ToArray();
+ }
+
// other variables
IsLagFrame = reader.ReadBoolean();
LagCount = reader.ReadInt32();
@@ -757,40 +901,21 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
/// handle the unmanaged part of savestating
///
byte[] CoreSaveState()
- {
- if (!DeterministicEmulation)
- return CoreSaveStateInternal(false);
- else
- return savestatebuff;
- }
-
- ///
- /// most recent internal savestate, for deterministic mode
- ///
- byte[] savestatebuff;
-
- ///
- /// internal function handling savestate
- /// this can cause determinism problems if called improperly!
- ///
- byte[] CoreSaveStateInternal(bool cache)
{
int size = LibsnesDll.snes_serialize_size();
byte[] buf = new byte[size];
fixed (byte* pbuf = &buf[0])
LibsnesDll.snes_serialize(new IntPtr(pbuf), size);
- if (cache)
- {
- savestatebuff = buf;
- return null;
- }
- else
- {
- savestatebuff = null;
- return buf;
- }
+ return buf;
}
+ ///
+ /// most recent internal savestate, for deterministic mode ONLY
+ ///
+ byte[] savestatebuff;
+
+ #endregion
+
// Arbitrary extensible core comm mechanism
public CoreInputComm CoreInputComm { get; set; }
public CoreOutputComm CoreOutputComm { get { return _CoreOutputComm; } }