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 AttributeSupport; private static readonly IReadOnlyDictionary 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(); var supportedTypes = new Dictionary(); 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; } /// /// Returns true if the provided type is supported by attributes. /// /// /// public static bool SupportsType(Type type) { return SupportedTypes.ContainsKey(type); } /// /// Returns true if the provided type is supported by attributes. /// /// /// public static bool SupportsType() { 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 { 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 (var reader = new BinaryReader(stream)) Initialize(reader); stream.Dispose(); } internal byte[] Serialize() { if (Count == 0) return Array.Empty(); 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(); } } } }