535ffa905c
Minor yet significant change that should enforce consistency in the serialization order of attributes. Making this change in line with a fix Roblox is making to their attribute serialization.
280 lines
8.0 KiB
C#
280 lines
8.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
|
|
namespace RobloxFiles
|
|
{
|
|
// This enum defines existing attributes
|
|
// Commented out values are known types
|
|
// which are unsupported at this time.
|
|
|
|
public enum AttributeType
|
|
{
|
|
// Null = 1,
|
|
String = 2,
|
|
Bool = 3,
|
|
// Int = 4,
|
|
Float = 5,
|
|
Double = 6,
|
|
// Array = 7,
|
|
// Dictionary = 8,
|
|
UDim = 9,
|
|
UDim2 = 10,
|
|
// Ray = 11,
|
|
// Faces = 12,
|
|
// Axes = 13
|
|
BrickColor = 14,
|
|
Color3 = 15,
|
|
Vector2 = 16,
|
|
Vector3 = 17,
|
|
// Vector2int16 = 18,
|
|
// Vector3int16 = 19,
|
|
// CFrame = 20,
|
|
// Enum = 21,
|
|
NumberSequence = 23,
|
|
// NumberSequenceKeypoint = 24,
|
|
ColorSequence = 25,
|
|
// ColorSequenceKeypoint = 26,
|
|
NumberRange = 27,
|
|
Rect = 28,
|
|
// PhysicalProperties = 29
|
|
// Region3 = 31,
|
|
// Region3int16 = 32
|
|
}
|
|
|
|
public class Attribute : IDisposable
|
|
{
|
|
private static readonly IReadOnlyDictionary<AttributeType, Tokenizer> AttributeSupport;
|
|
private static readonly IReadOnlyDictionary<Type, AttributeType> SupportedTypes;
|
|
|
|
public AttributeType DataType { get; private set; }
|
|
public object Value { get; private set; }
|
|
|
|
private struct Tokenizer
|
|
{
|
|
public readonly Type Support;
|
|
public readonly object Token;
|
|
|
|
public readonly MethodInfo Reader;
|
|
public readonly MethodInfo Writer;
|
|
|
|
public Tokenizer(Type tokenType, Type support)
|
|
{
|
|
Support = support;
|
|
Token = Activator.CreateInstance(tokenType);
|
|
|
|
Reader = support.GetMethod("ReadAttribute");
|
|
Writer = support.GetMethod("WriteAttribute");
|
|
}
|
|
|
|
public object ReadAttribute(Attribute attr)
|
|
{
|
|
var args = new object[1] { attr };
|
|
return Reader.Invoke(Token, args);
|
|
}
|
|
|
|
public void WriteAttribute(Attribute attr, object value)
|
|
{
|
|
var args = new object[2] { attr, value };
|
|
Writer.Invoke(Token, args);
|
|
}
|
|
}
|
|
|
|
static Attribute()
|
|
{
|
|
var attributeSupport = new Dictionary<AttributeType, Tokenizer>();
|
|
var supportedTypes = new Dictionary<Type, AttributeType>();
|
|
|
|
var assembly = Assembly.GetExecutingAssembly();
|
|
|
|
var handlerTypes =
|
|
from type in assembly.GetTypes()
|
|
let typeInfo = type.GetTypeInfo()
|
|
let support = typeInfo.GetInterface("IAttributeToken`1")
|
|
where (support != null)
|
|
select new Tokenizer(typeInfo, support);
|
|
|
|
foreach (var tokenizer in handlerTypes)
|
|
{
|
|
var token = tokenizer.Token;
|
|
var support = tokenizer.Support;
|
|
|
|
var genericType = support.GenericTypeArguments.FirstOrDefault();
|
|
var getAttributeType = support.GetMethod("get_AttributeType");
|
|
var attributeType = (AttributeType)getAttributeType.Invoke(token, null);
|
|
|
|
attributeSupport.Add(attributeType, tokenizer);
|
|
supportedTypes.Add(genericType, attributeType);
|
|
}
|
|
|
|
AttributeSupport = attributeSupport;
|
|
SupportedTypes = supportedTypes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the provided type is supported by attributes.
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <returns></returns>
|
|
public static bool SupportsType(Type type)
|
|
{
|
|
return SupportedTypes.ContainsKey(type);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the provided type is supported by attributes.
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <returns></returns>
|
|
public static bool SupportsType<T>()
|
|
{
|
|
Type type = typeof(T);
|
|
return SupportsType(type);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
string value = Value?.ToString() ?? "null";
|
|
return $"[{DataType}: {value}]";
|
|
}
|
|
|
|
internal BinaryReader Reader;
|
|
internal BinaryWriter Writer;
|
|
|
|
internal int ReadInt() => Reader.ReadInt32();
|
|
internal byte ReadByte() => Reader.ReadByte();
|
|
internal bool ReadBool() => Reader.ReadBoolean();
|
|
internal short ReadShort() => Reader.ReadInt16();
|
|
internal float ReadFloat() => Reader.ReadSingle();
|
|
internal double ReadDouble() => Reader.ReadDouble();
|
|
internal string ReadString() => Reader.ReadString(true);
|
|
|
|
internal void WriteInt(int value) => Writer.Write(value);
|
|
internal void WriteBool(bool value) => Writer.Write(value);
|
|
internal void WriteFloat(float value) => Writer.Write(value);
|
|
internal void WriteDouble(double value) => Writer.Write(value);
|
|
|
|
internal void WriteString(string value)
|
|
{
|
|
int length = value.Length;
|
|
Writer.Write(length);
|
|
|
|
byte[] utf8 = Encoding.UTF8.GetBytes(value);
|
|
Writer.Write(utf8);
|
|
}
|
|
|
|
internal void Read()
|
|
{
|
|
if (Reader == null)
|
|
return;
|
|
|
|
var dataType = Reader.ReadByte();
|
|
DataType = (AttributeType)dataType;
|
|
|
|
var tokenizer = AttributeSupport[DataType];
|
|
Value = tokenizer.ReadAttribute(this);
|
|
|
|
Reader = null;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Reader?.Dispose();
|
|
}
|
|
|
|
internal void Write(BinaryWriter writer)
|
|
{
|
|
var tokenizer = AttributeSupport[DataType];
|
|
Writer = writer;
|
|
|
|
writer.Write((byte)DataType);
|
|
tokenizer.WriteAttribute(this, Value);
|
|
|
|
Writer = null;
|
|
}
|
|
|
|
internal Attribute(BinaryReader reader)
|
|
{
|
|
Reader = reader;
|
|
Read();
|
|
}
|
|
|
|
internal Attribute(MemoryStream stream)
|
|
{
|
|
Reader = new BinaryReader(stream);
|
|
Read();
|
|
}
|
|
|
|
internal Attribute(object value)
|
|
{
|
|
Type type = value.GetType();
|
|
|
|
if (SupportedTypes.TryGetValue(type, out AttributeType dataType))
|
|
{
|
|
DataType = dataType;
|
|
Value = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class Attributes : SortedDictionary<string, Attribute>
|
|
{
|
|
private void Initialize(BinaryReader reader)
|
|
{
|
|
Stream stream = reader.BaseStream;
|
|
|
|
if (stream.Length - stream.Position < 4)
|
|
// Not enough room to read the entry count, possibly empty?
|
|
return;
|
|
|
|
int numEntries = reader.ReadInt32();
|
|
|
|
for (int i = 0; i < numEntries; i++)
|
|
{
|
|
string key = reader.ReadString(true);
|
|
var attribute = new Attribute(reader);
|
|
Add(key, attribute);
|
|
}
|
|
}
|
|
|
|
internal Attributes(BinaryReader reader)
|
|
{
|
|
Initialize(reader);
|
|
}
|
|
|
|
internal Attributes(MemoryStream stream)
|
|
{
|
|
using (BinaryReader reader = new BinaryReader(stream))
|
|
Initialize(reader);
|
|
|
|
stream.Dispose();
|
|
}
|
|
|
|
internal byte[] Serialize()
|
|
{
|
|
if (Count == 0)
|
|
return Array.Empty<byte>();
|
|
|
|
using (var output = new MemoryStream())
|
|
using (var writer = new BinaryWriter(output))
|
|
{
|
|
writer.Write(Count);
|
|
|
|
foreach (string key in Keys)
|
|
{
|
|
var attribute = this[key];
|
|
attribute.Writer = writer;
|
|
|
|
attribute.WriteString(key);
|
|
attribute.Write(writer);
|
|
}
|
|
|
|
return output.ToArray();
|
|
}
|
|
}
|
|
}
|
|
}
|