387 lines
10 KiB
C#
387 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using ICSharpCode.SharpZipLib.Zip;
|
|
|
|
namespace BizHawk.Client.Common
|
|
{
|
|
public class BinaryStateLump
|
|
{
|
|
[Name("BizState 1", "0")]
|
|
public static BinaryStateLump Versiontag { get; private set; }
|
|
[Name("BizVersion", "txt")]
|
|
public static BinaryStateLump BizVersion { get; private set; }
|
|
[Name("Core", "bin")]
|
|
public static BinaryStateLump Corestate { get; private set; }
|
|
[Name("Framebuffer", "bmp")]
|
|
public static BinaryStateLump Framebuffer { get; private set; }
|
|
[Name("Input Log", "txt")]
|
|
public static BinaryStateLump Input { get; private set; }
|
|
[Name("CoreText", "txt")]
|
|
public static BinaryStateLump CorestateText { get; private set; }
|
|
[Name("MovieSaveRam", "bin")]
|
|
public static BinaryStateLump MovieSaveRam { get; private set; }
|
|
|
|
// Only for movies they probably shouldn't be leaching this stuff
|
|
[Name("Header", "txt")]
|
|
public static BinaryStateLump Movieheader { get; private set; }
|
|
[Name("Comments", "txt")]
|
|
public static BinaryStateLump Comments { get; private set; }
|
|
[Name("Subtitles", "txt")]
|
|
public static BinaryStateLump Subtitles { get; private set; }
|
|
[Name("SyncSettings", "json")]
|
|
public static BinaryStateLump SyncSettings { get; private set; }
|
|
|
|
// TasMovie
|
|
[Name("LagLog")]
|
|
public static BinaryStateLump LagLog { get; private set; }
|
|
[Name("GreenZone")]
|
|
public static BinaryStateLump StateHistory { get; private set; }
|
|
[Name("GreenZoneSettings", "txt")]
|
|
public static BinaryStateLump StateHistorySettings { get; private set; }
|
|
[Name("Markers", "txt")]
|
|
public static BinaryStateLump Markers { get; private set; }
|
|
[Name("ClientSettings", "json")]
|
|
public static BinaryStateLump ClientSettings { get; private set; }
|
|
[Name("VerificationLog", "txt")]
|
|
public static BinaryStateLump VerificationLog { get; private set; }
|
|
[Name("UserData", "txt")]
|
|
public static BinaryStateLump UserData { get; private set; }
|
|
[Name("Session", "txt")]
|
|
public static BinaryStateLump Session { get; private set; }
|
|
|
|
// branch stuff
|
|
[Name("Branches\\CoreData", "bin")]
|
|
public static BinaryStateLump BranchCoreData { get; private set; }
|
|
[Name("Branches\\InputLog", "txt")]
|
|
public static BinaryStateLump BranchInputLog { get; private set; }
|
|
[Name("Branches\\FrameBuffer", "bmp")]
|
|
public static BinaryStateLump BranchFrameBuffer { get; private set; }
|
|
[Name("Branches\\CoreFrameBuffer", "bmp")]
|
|
public static BinaryStateLump BranchCoreFrameBuffer { get; private set; }
|
|
[Name("Branches\\Header", "json")]
|
|
public static BinaryStateLump BranchHeader { get; private set; }
|
|
[Name("Branches\\Markers", "txt")]
|
|
public static BinaryStateLump BranchMarkers { get; private set; }
|
|
[Name("Branches\\UserText", "txt")]
|
|
public static BinaryStateLump BranchUserText { get; private set; }
|
|
|
|
[AttributeUsage(AttributeTargets.Property)]
|
|
private class NameAttribute : Attribute
|
|
{
|
|
public string Name { get; }
|
|
public string Ext { get; }
|
|
public NameAttribute(string name)
|
|
{
|
|
Name = name;
|
|
}
|
|
|
|
public NameAttribute(string name, string ext)
|
|
{
|
|
Name = name;
|
|
Ext = ext;
|
|
}
|
|
}
|
|
|
|
public string ReadName => Name;
|
|
public string WriteName => Ext != null ? Name + '.' + Ext : Name;
|
|
|
|
public string Name { get; protected set; }
|
|
public string Ext { get; protected set; }
|
|
|
|
private BinaryStateLump(string name, string ext)
|
|
{
|
|
Name = name;
|
|
Ext = ext;
|
|
}
|
|
|
|
protected BinaryStateLump()
|
|
{
|
|
}
|
|
|
|
static BinaryStateLump()
|
|
{
|
|
foreach (var prop in typeof(BinaryStateLump).GetProperties(BindingFlags.Public | BindingFlags.Static))
|
|
{
|
|
var attr = prop.GetCustomAttributes(false).OfType<NameAttribute>().Single();
|
|
object value = new BinaryStateLump(attr.Name, attr.Ext);
|
|
prop.SetValue(null, value, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// describes a BinaryStateLump virtual name that has a numerical index
|
|
/// </summary>
|
|
public class IndexedStateLump : BinaryStateLump
|
|
{
|
|
private readonly BinaryStateLump _root;
|
|
private int _idx;
|
|
public IndexedStateLump(BinaryStateLump root)
|
|
{
|
|
_root = root;
|
|
Ext = _root.Ext;
|
|
Calc();
|
|
}
|
|
|
|
private void Calc()
|
|
{
|
|
Name = _root.Name + _idx;
|
|
}
|
|
|
|
public void Increment()
|
|
{
|
|
_idx++;
|
|
Calc();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// more accurately should be called ZipStateLoader, as it supports both text and binary core data
|
|
/// </summary>
|
|
public class BinaryStateLoader : IDisposable
|
|
{
|
|
private ZipFile _zip;
|
|
private Version _ver;
|
|
private bool _isDisposed;
|
|
private Dictionary<string, ZipEntry> _entriesByName;
|
|
|
|
private BinaryStateLoader()
|
|
{
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!_isDisposed)
|
|
{
|
|
_isDisposed = true;
|
|
if (disposing)
|
|
{
|
|
_zip.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ReadVersion(Stream s, long length)
|
|
{
|
|
// the "BizState 1.0" tag contains an integer in it describing the sub version.
|
|
if (length == 0)
|
|
{
|
|
_ver = new Version(1, 0, 0); // except for the first release, which doesn't
|
|
}
|
|
else
|
|
{
|
|
var sr = new StreamReader(s);
|
|
_ver = new Version(1, 0, int.Parse(sr.ReadLine()));
|
|
}
|
|
|
|
Console.WriteLine("Read a zipstate of version {0}", _ver);
|
|
}
|
|
|
|
private void PopulateEntries()
|
|
{
|
|
_entriesByName = new Dictionary<string, ZipEntry>();
|
|
foreach (ZipEntry z in _zip)
|
|
{
|
|
string name = z.Name;
|
|
int i;
|
|
if ((i = name.LastIndexOf('.')) != -1)
|
|
{
|
|
name = name.Substring(0, i);
|
|
}
|
|
|
|
_entriesByName.Add(name.Replace('/', '\\'), z);
|
|
}
|
|
}
|
|
|
|
private static readonly byte[] Zipheader = { 0x50, 0x4b, 0x03, 0x04 };
|
|
public static BinaryStateLoader LoadAndDetect(string filename, bool isMovieLoad = false)
|
|
{
|
|
var ret = new BinaryStateLoader();
|
|
|
|
using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
|
|
{
|
|
byte[] data = new byte[4];
|
|
fs.Read(data, 0, 4);
|
|
if (!data.SequenceEqual(Zipheader))
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
ret._zip = new ZipFile(filename);
|
|
ret.PopulateEntries();
|
|
if (!isMovieLoad && !ret.GetLump(BinaryStateLump.Versiontag, false, ret.ReadVersion))
|
|
{
|
|
ret._zip.Close();
|
|
return null;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
catch (ZipException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <param name="lump">lump to retrieve</param>
|
|
/// <param name="abort">pass true to throw exception instead of returning false</param>
|
|
/// <param name="callback">function to call with the desired stream</param>
|
|
/// <returns>true iff stream was loaded</returns>
|
|
/// <exception cref="Exception">stream not found and <paramref name="abort"/> is <see langword="true"/></exception>
|
|
public bool GetLump(BinaryStateLump lump, bool abort, Action<Stream, long> callback)
|
|
{
|
|
ZipEntry e;
|
|
if (_entriesByName.TryGetValue(lump.ReadName, out e))
|
|
{
|
|
using (var zs = _zip.GetInputStream(e))
|
|
{
|
|
callback(zs, e.Size);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (abort)
|
|
{
|
|
throw new Exception($"Essential zip section not found: {lump.ReadName}");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool GetLump(BinaryStateLump lump, bool abort, Action<BinaryReader> callback)
|
|
{
|
|
return GetLump(lump, abort, delegate(Stream s, long unused)
|
|
{
|
|
var br = new BinaryReader(s);
|
|
callback(br);
|
|
});
|
|
}
|
|
|
|
public bool GetLump(BinaryStateLump lump, bool abort, Action<BinaryReader, long> callback)
|
|
{
|
|
return GetLump(lump, abort, delegate(Stream s, long length)
|
|
{
|
|
var br = new BinaryReader(s);
|
|
callback(br, length);
|
|
});
|
|
}
|
|
|
|
public bool GetLump(BinaryStateLump lump, bool abort, Action<TextReader> callback)
|
|
{
|
|
return GetLump(lump, abort, delegate(Stream s, long unused)
|
|
{
|
|
var tr = new StreamReader(s);
|
|
callback(tr);
|
|
});
|
|
}
|
|
|
|
/// <exception cref="Exception">couldn't find Binary or Text savestate</exception>
|
|
public void GetCoreState(Action<BinaryReader, long> callbackBinary, Action<TextReader> callbackText)
|
|
{
|
|
if (!GetLump(BinaryStateLump.Corestate, false, callbackBinary)
|
|
&& !GetLump(BinaryStateLump.CorestateText, false, callbackText))
|
|
{
|
|
throw new Exception("Couldn't find Binary or Text savestate");
|
|
}
|
|
}
|
|
|
|
/// <exception cref="Exception">couldn't find Binary or Text savestate</exception>
|
|
public void GetCoreState(Action<BinaryReader> callbackBinary, Action<TextReader> callbackText)
|
|
{
|
|
if (!GetLump(BinaryStateLump.Corestate, false, callbackBinary)
|
|
&& !GetLump(BinaryStateLump.CorestateText, false, callbackText))
|
|
{
|
|
throw new Exception("Couldn't find Binary or Text savestate");
|
|
}
|
|
}
|
|
}
|
|
|
|
public class BinaryStateSaver : IDisposable
|
|
{
|
|
private readonly IZipWriter _zip;
|
|
private bool _isDisposed;
|
|
|
|
private static void WriteVersion(Stream s)
|
|
{
|
|
var sw = new StreamWriter(s);
|
|
sw.WriteLine("1"); // version 1.0.1
|
|
sw.Flush();
|
|
}
|
|
|
|
private static void WriteEmuVersion(Stream s)
|
|
{
|
|
var sw = new StreamWriter(s);
|
|
sw.WriteLine(VersionInfo.GetEmuVersion());
|
|
sw.Flush();
|
|
}
|
|
|
|
public BinaryStateSaver(string path, bool notamovie = true) // notamovie is hack, really should have separate something
|
|
{
|
|
_zip = new FrameworkZipWriter(path, notamovie ? Global.Config.SaveStateCompressionLevelNormal : Global.Config.MovieCompressionLevel);
|
|
|
|
if (notamovie)
|
|
{
|
|
PutLump(BinaryStateLump.Versiontag, WriteVersion);
|
|
PutLump(BinaryStateLump.BizVersion, WriteEmuVersion);
|
|
}
|
|
}
|
|
|
|
public void PutLump(BinaryStateLump lump, Action<Stream> callback)
|
|
{
|
|
_zip.WriteItem(lump.WriteName, callback);
|
|
}
|
|
|
|
public void PutLump(BinaryStateLump lump, Action<BinaryWriter> callback)
|
|
{
|
|
PutLump(lump, delegate(Stream s)
|
|
{
|
|
var bw = new BinaryWriter(s);
|
|
callback(bw);
|
|
bw.Flush();
|
|
});
|
|
}
|
|
|
|
public void PutLump(BinaryStateLump lump, Action<TextWriter> callback)
|
|
{
|
|
PutLump(lump, delegate(Stream s)
|
|
{
|
|
TextWriter tw = new StreamWriter(s);
|
|
callback(tw);
|
|
tw.Flush();
|
|
});
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!_isDisposed)
|
|
{
|
|
_isDisposed = true;
|
|
|
|
if (disposing)
|
|
{
|
|
_zip.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|