149 lines
3.7 KiB
C#
149 lines
3.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace BizHawk.Emulation.Common
|
|
{
|
|
// managed counterpart to unmanaged serialization code in GB and WSWAN cores
|
|
|
|
// T is a class that has any other data you want to serialize in it
|
|
public class TextState<T>
|
|
where T : new()
|
|
{
|
|
public TextState()
|
|
{
|
|
ExtraData = new T();
|
|
}
|
|
|
|
public class Node
|
|
{
|
|
public readonly Dictionary<string, byte[]> Data = new Dictionary<string, byte[]>();
|
|
public readonly Dictionary<string, Node> Objects = new Dictionary<string, Node>();
|
|
|
|
// methods named "ShouldSerialize*" are detected and dynamically invoked by JSON.NET
|
|
// if they return false during serialization, the field/prop is omitted from the created json
|
|
// ReSharper disable once UnusedMember.Global
|
|
public bool ShouldSerializeData()
|
|
{
|
|
return Data.Count > 0;
|
|
}
|
|
|
|
// ReSharper disable once UnusedMember.Global
|
|
public bool ShouldSerializeObjects()
|
|
{
|
|
return Objects.Count > 0;
|
|
}
|
|
}
|
|
|
|
public readonly Node Root = new Node();
|
|
|
|
[JsonIgnore]
|
|
private Stack<Node> Nodes;
|
|
|
|
[JsonIgnore]
|
|
private Node Current => Nodes.Peek();
|
|
|
|
public void Prepare()
|
|
{
|
|
Nodes = new Stack<Node>();
|
|
Nodes.Push(Root);
|
|
}
|
|
|
|
public void Save(IntPtr data, int length, string name)
|
|
{
|
|
byte[] d = new byte[length];
|
|
Marshal.Copy(data, d, 0, length);
|
|
Current.Data.Add(name, d); // will except for us if the key is already present
|
|
}
|
|
|
|
/// <exception cref="InvalidOperationException"><paramref name="length"/> does not match the length of the data saved as <paramref name="name"/></exception>
|
|
public void Load(IntPtr data, int length, string name)
|
|
{
|
|
byte[] d = Current.Data[name];
|
|
if (length != d.Length)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
Marshal.Copy(d, 0, data, length);
|
|
}
|
|
|
|
public void EnterSectionSave(string name)
|
|
{
|
|
Node next = new Node();
|
|
Current.Objects.Add(name, next);
|
|
Nodes.Push(next);
|
|
}
|
|
|
|
public void EnterSectionLoad(string name)
|
|
{
|
|
Node next = Current.Objects[name];
|
|
Nodes.Push(next);
|
|
}
|
|
|
|
public void EnterSection(string name)
|
|
{
|
|
// works for either save or load, but as a consequence cannot report intelligent
|
|
// errors about section name mismatches
|
|
Current.Objects.TryGetValue(name, out var next);
|
|
if (next == null)
|
|
{
|
|
next = new Node();
|
|
Current.Objects.Add(name, next);
|
|
}
|
|
|
|
Nodes.Push(next);
|
|
}
|
|
|
|
/// <exception cref="InvalidOperationException"><paramref name="name"/> doesn't match the section being closed</exception>
|
|
public void ExitSection(string name)
|
|
{
|
|
Node last = Nodes.Pop();
|
|
if (Current.Objects[name] != last)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
}
|
|
|
|
// other data besides the core
|
|
public readonly T ExtraData;
|
|
|
|
public TextStateFPtrs GetFunctionPointersSave()
|
|
{
|
|
return new TextStateFPtrs
|
|
{
|
|
Save = Save,
|
|
Load = null,
|
|
EnterSection = EnterSectionSave,
|
|
ExitSection = ExitSection
|
|
};
|
|
}
|
|
|
|
public TextStateFPtrs GetFunctionPointersLoad()
|
|
{
|
|
return new TextStateFPtrs
|
|
{
|
|
Save = null,
|
|
Load = Load,
|
|
EnterSection = EnterSectionLoad,
|
|
ExitSection = ExitSection
|
|
};
|
|
}
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct TextStateFPtrs
|
|
{
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void DataFunction(IntPtr data, int length, string name);
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void SectionFunction(string name);
|
|
|
|
public DataFunction Save;
|
|
public DataFunction Load;
|
|
public SectionFunction EnterSection;
|
|
public SectionFunction ExitSection;
|
|
}
|
|
}
|