Basic partial replay support.

This commit is contained in:
Dean Herbert
2017-02-28 20:14:48 +09:00
parent 327300e9a7
commit 58ae9e888d
19 changed files with 984 additions and 40 deletions

View File

@ -0,0 +1,11 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.IO.Legacy
{
public interface ILegacySerializable
{
void ReadFromStream(SerializationReader sr);
void WriteToStream(SerializationWriter sw);
}
}

View File

@ -0,0 +1,279 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
namespace osu.Game.IO.Legacy
{
/// <summary> SerializationReader. Extends BinaryReader to add additional data types,
/// handle null strings and simplify use with ISerializable. </summary>
public class SerializationReader : BinaryReader
{
Stream stream;
public SerializationReader(Stream s)
: base(s, Encoding.UTF8)
{
stream = s;
}
public int RemainingBytes => (int)(stream.Length - stream.Position);
/// <summary> Static method to take a SerializationInfo object (an input to an ISerializable constructor)
/// and produce a SerializationReader from which serialized objects can be read </summary>.
public static SerializationReader GetReader(SerializationInfo info)
{
byte[] byteArray = (byte[])info.GetValue("X", typeof(byte[]));
MemoryStream ms = new MemoryStream(byteArray);
return new SerializationReader(ms);
}
/// <summary> Reads a string from the buffer. Overrides the base implementation so it can cope with nulls. </summary>
public override string ReadString()
{
if (0 == ReadByte()) return null;
return base.ReadString();
}
/// <summary> Reads a byte array from the buffer, handling nulls and the array length. </summary>
public byte[] ReadByteArray()
{
int len = ReadInt32();
if (len > 0) return ReadBytes(len);
if (len < 0) return null;
return new byte[0];
}
/// <summary> Reads a char array from the buffer, handling nulls and the array length. </summary>
public char[] ReadCharArray()
{
int len = ReadInt32();
if (len > 0) return ReadChars(len);
if (len < 0) return null;
return new char[0];
}
/// <summary> Reads a DateTime from the buffer. </summary>
public DateTime ReadDateTime()
{
long ticks = ReadInt64();
if (ticks < 0) throw new AbandonedMutexException("oops");
return new DateTime(ticks, DateTimeKind.Utc);
}
/// <summary> Reads a generic list from the buffer. </summary>
public IList<T> ReadBList<T>(bool skipErrors = false) where T : ILegacySerializable, new()
{
int count = ReadInt32();
if (count < 0) return null;
IList<T> d = new List<T>(count);
SerializationReader sr = new SerializationReader(BaseStream);
for (int i = 0; i < count; i++)
{
T obj = new T();
try
{
obj.ReadFromStream(sr);
}
catch (Exception)
{
if (skipErrors)
continue;
throw;
}
d.Add(obj);
}
return d;
}
/// <summary> Reads a generic list from the buffer. </summary>
public IList<T> ReadList<T>()
{
int count = ReadInt32();
if (count < 0) return null;
IList<T> d = new List<T>(count);
for (int i = 0; i < count; i++) d.Add((T)ReadObject());
return d;
}
/// <summary> Reads a generic Dictionary from the buffer. </summary>
public IDictionary<T, U> ReadDictionary<T, U>()
{
int count = ReadInt32();
if (count < 0) return null;
IDictionary<T, U> d = new Dictionary<T, U>();
for (int i = 0; i < count; i++) d[(T)ReadObject()] = (U)ReadObject();
return d;
}
/// <summary> Reads an object which was added to the buffer by WriteObject. </summary>
public object ReadObject()
{
ObjType t = (ObjType)ReadByte();
switch (t)
{
case ObjType.boolType:
return ReadBoolean();
case ObjType.byteType:
return ReadByte();
case ObjType.uint16Type:
return ReadUInt16();
case ObjType.uint32Type:
return ReadUInt32();
case ObjType.uint64Type:
return ReadUInt64();
case ObjType.sbyteType:
return ReadSByte();
case ObjType.int16Type:
return ReadInt16();
case ObjType.int32Type:
return ReadInt32();
case ObjType.int64Type:
return ReadInt64();
case ObjType.charType:
return ReadChar();
case ObjType.stringType:
return base.ReadString();
case ObjType.singleType:
return ReadSingle();
case ObjType.doubleType:
return ReadDouble();
case ObjType.decimalType:
return ReadDecimal();
case ObjType.dateTimeType:
return ReadDateTime();
case ObjType.byteArrayType:
return ReadByteArray();
case ObjType.charArrayType:
return ReadCharArray();
case ObjType.otherType:
return DynamicDeserializer.Deserialize(BaseStream);
default:
return null;
}
}
public class DynamicDeserializer
{
private static VersionConfigToNamespaceAssemblyObjectBinder versionBinder;
private static BinaryFormatter formatter;
private static void initialize()
{
versionBinder = new VersionConfigToNamespaceAssemblyObjectBinder();
formatter = new BinaryFormatter();
formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
formatter.Binder = versionBinder;
}
public static object Deserialize(Stream stream)
{
if (formatter == null)
initialize();
return formatter.Deserialize(stream);
}
#region Nested type: VersionConfigToNamespaceAssemblyObjectBinder
public sealed class VersionConfigToNamespaceAssemblyObjectBinder : SerializationBinder
{
private readonly Dictionary<string, Type> cache = new Dictionary<string, Type>();
public override Type BindToType(string assemblyName, string typeName)
{
Type typeToDeserialize;
if (cache.TryGetValue(assemblyName + typeName, out typeToDeserialize))
return typeToDeserialize;
List<Type> tmpTypes = new List<Type>();
Type genType = null;
try
{
if (typeName.Contains("System.Collections.Generic") && typeName.Contains("[["))
{
string[] splitTyps = typeName.Split('[');
foreach (string typ in splitTyps)
{
if (typ.Contains("Version"))
{
string asmTmp = typ.Substring(typ.IndexOf(',') + 1);
string asmName = asmTmp.Remove(asmTmp.IndexOf(']')).Trim();
string typName = typ.Remove(typ.IndexOf(','));
tmpTypes.Add(BindToType(asmName, typName));
}
else if (typ.Contains("Generic"))
{
genType = BindToType(assemblyName, typ);
}
}
if (genType != null && tmpTypes.Count > 0)
{
return genType.MakeGenericType(tmpTypes.ToArray());
}
}
string toAssemblyName = assemblyName.Split(',')[0];
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in assemblies)
{
if (a.FullName.Split(',')[0] == toAssemblyName)
{
typeToDeserialize = a.GetType(typeName);
break;
}
}
}
catch (Exception exception)
{
throw exception;
}
cache.Add(assemblyName + typeName, typeToDeserialize);
return typeToDeserialize;
}
}
#endregion
}
}
public enum ObjType : byte
{
nullType,
boolType,
byteType,
uint16Type,
uint32Type,
uint64Type,
sbyteType,
int16Type,
int32Type,
int64Type,
charType,
stringType,
singleType,
doubleType,
decimalType,
dateTimeType,
byteArrayType,
charArrayType,
otherType,
ILegacySerializableType
}
}

View File

@ -0,0 +1,255 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
namespace osu.Game.IO.Legacy
{
/// <summary> SerializationWriter. Extends BinaryWriter to add additional data types,
/// handle null strings and simplify use with ISerializable. </summary>
public class SerializationWriter : BinaryWriter
{
public SerializationWriter(Stream s)
: base(s, Encoding.UTF8)
{
}
/// <summary> Static method to initialise the writer with a suitable MemoryStream. </summary>
public static SerializationWriter GetWriter()
{
MemoryStream ms = new MemoryStream(1024);
return new SerializationWriter(ms);
}
/// <summary> Writes a string to the buffer. Overrides the base implementation so it can cope with nulls </summary>
public override void Write(string str)
{
if (str == null)
{
Write((byte)ObjType.nullType);
}
else
{
Write((byte)ObjType.stringType);
base.Write(str);
}
}
/// <summary> Writes a byte array to the buffer. Overrides the base implementation to
/// send the length of the array which is needed when it is retrieved </summary>
public override void Write(byte[] b)
{
if (b == null)
{
Write(-1);
}
else
{
int len = b.Length;
Write(len);
if (len > 0) base.Write(b);
}
}
/// <summary> Writes a char array to the buffer. Overrides the base implementation to
/// sends the length of the array which is needed when it is read. </summary>
public override void Write(char[] c)
{
if (c == null)
{
Write(-1);
}
else
{
int len = c.Length;
Write(len);
if (len > 0) base.Write(c);
}
}
/// <summary> Writes a DateTime to the buffer. <summary>
public void Write(DateTime dt)
{
Write(dt.ToUniversalTime().Ticks);
}
/// <summary> Writes a generic ICollection (such as an IList<T>) to the buffer. </summary>
public void Write<T>(List<T> c) where T : ILegacySerializable
{
if (c == null)
{
Write(-1);
}
else
{
int count = c.Count;
Write(count);
for (int i = 0; i < count; i++)
c[i].WriteToStream(this);
}
}
/// <summary> Writes a generic IDictionary to the buffer. </summary>
public void Write<T, U>(IDictionary<T, U> d)
{
if (d == null)
{
Write(-1);
}
else
{
Write(d.Count);
foreach (KeyValuePair<T, U> kvp in d)
{
WriteObject(kvp.Key);
WriteObject(kvp.Value);
}
}
}
/// <summary> Writes an arbitrary object to the buffer. Useful where we have something of type "object"
/// and don't know how to treat it. This works out the best method to use to write to the buffer. </summary>
public void WriteObject(object obj)
{
if (obj == null)
{
Write((byte)ObjType.nullType);
}
else
{
switch (obj.GetType().Name)
{
case "Boolean":
Write((byte)ObjType.boolType);
Write((bool)obj);
break;
case "Byte":
Write((byte)ObjType.byteType);
Write((byte)obj);
break;
case "UInt16":
Write((byte)ObjType.uint16Type);
Write((ushort)obj);
break;
case "UInt32":
Write((byte)ObjType.uint32Type);
Write((uint)obj);
break;
case "UInt64":
Write((byte)ObjType.uint64Type);
Write((ulong)obj);
break;
case "SByte":
Write((byte)ObjType.sbyteType);
Write((sbyte)obj);
break;
case "Int16":
Write((byte)ObjType.int16Type);
Write((short)obj);
break;
case "Int32":
Write((byte)ObjType.int32Type);
Write((int)obj);
break;
case "Int64":
Write((byte)ObjType.int64Type);
Write((long)obj);
break;
case "Char":
Write((byte)ObjType.charType);
base.Write((char)obj);
break;
case "String":
Write((byte)ObjType.stringType);
base.Write((string)obj);
break;
case "Single":
Write((byte)ObjType.singleType);
Write((float)obj);
break;
case "Double":
Write((byte)ObjType.doubleType);
Write((double)obj);
break;
case "Decimal":
Write((byte)ObjType.decimalType);
Write((decimal)obj);
break;
case "DateTime":
Write((byte)ObjType.dateTimeType);
Write((DateTime)obj);
break;
case "Byte[]":
Write((byte)ObjType.byteArrayType);
base.Write((byte[])obj);
break;
case "Char[]":
Write((byte)ObjType.charArrayType);
base.Write((char[])obj);
break;
default:
Write((byte)ObjType.otherType);
BinaryFormatter b = new BinaryFormatter();
b.AssemblyFormat = FormatterAssemblyStyle.Simple;
b.TypeFormat = FormatterTypeStyle.TypesWhenNeeded;
b.Serialize(BaseStream, obj);
break;
} // switch
} // if obj==null
} // WriteObject
/// <summary> Adds the SerializationWriter buffer to the SerializationInfo at the end of GetObjectData(). </summary>
public void AddToInfo(SerializationInfo info)
{
byte[] b = ((MemoryStream)BaseStream).ToArray();
info.AddValue("X", b, typeof(byte[]));
}
public void WriteRawBytes(byte[] b)
{
base.Write(b);
}
public void WriteByteArray(byte[] b)
{
if (b == null)
{
Write(-1);
}
else
{
int len = b.Length;
Write(len);
if (len > 0) base.Write(b);
}
}
public void WriteUtf8(string str)
{
WriteRawBytes(Encoding.UTF8.GetBytes(str));
}
}
}