using System; using System.Collections.Generic; using System.IO; using System.Linq; using RobloxFiles.DataTypes; namespace RobloxFiles { public enum AttributeType { Null = 1, String, Bool, Int, Float, Double, Array, Dictionary, UDim, UDim2, Ray, Faces, Axes, BrickColor, Color3, Vector2, Vector3, Vector2int16, Vector3int16, CFrame, Enum, NumberSequence = 23, NumberSequenceKeypoint, ColorSequence, ColorSequenceKeypoint, NumberRange, Rect, PhysicalProperties, Region3 = 31, Region3int16, } public class Attribute : IDisposable { public AttributeType DataType { get; private set; } public object Value { get; private set; } public override string ToString() { string type = Enum.GetName(typeof(AttributeType), DataType); string value = Value?.ToString() ?? "null"; return $"[{type}: {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 Attribute[] readArray() { int count = readInt(); var result = new Attribute[count]; for (int i = 0; i < count; i++) result[i] = new Attribute(reader); return result; } internal object readEnum() { string name = readString(); int value = readInt(); try { Type enumType = Type.GetType($"RobloxFiles.Enums.{name}"); return Enum.ToObject(enumType, value); } catch { if (RobloxFile.LogErrors) Console.Error.WriteLine($"RobloxFile - Got unknown Enum {name} in Attribute."); return null; } } private void readData() { if (reader == null) return; DataType = (AttributeType)reader.ReadByte(); switch (DataType) { ////////////////////////// case AttributeType.Null: break; case AttributeType.String: Value = readString(); break; case AttributeType.Bool: Value = readBool(); break; case AttributeType.Int: Value = readInt(); break; case AttributeType.Float: Value = readFloat(); break; case AttributeType.Double: Value = readDouble(); break; case AttributeType.Array: Value = readArray(); break; case AttributeType.Dictionary: Value = new Attributes(reader); break; case AttributeType.UDim: Value = new UDim(this); break; case AttributeType.UDim2: Value = new UDim2(this); break; case AttributeType.Ray: Value = new Ray(this); break; case AttributeType.Faces: Value = (Faces)readInt(); break; case AttributeType.Axes: Value = (Axes)readInt(); break; case AttributeType.BrickColor: Value = (BrickColor)readInt(); break; case AttributeType.Color3: Value = new Color3(this); break; case AttributeType.Vector2: Value = new Vector2(this); break; case AttributeType.Vector3: Value = new Vector3(this); break; case AttributeType.Vector2int16: Value = new Vector2int16(this); break; case AttributeType.Vector3int16: Value = new Vector3int16(this); break; case AttributeType.CFrame: Value = new CFrame(this); break; case AttributeType.Enum: Value = readEnum(); break; case AttributeType.NumberSequence: Value = new NumberSequence(this); break; case AttributeType.NumberSequenceKeypoint: Value = new NumberSequenceKeypoint(this); break; case AttributeType.ColorSequence: Value = new ColorSequence(this); break; case AttributeType.ColorSequenceKeypoint: Value = new ColorSequenceKeypoint(this); break; case AttributeType.NumberRange: Value = new NumberRange(this); break; case AttributeType.Rect: Value = new Rect(this); break; case AttributeType.PhysicalProperties: bool custom = readBool(); if (custom) Value = new PhysicalProperties(this); break; case AttributeType.Region3: Value = new Region3(this); break; case AttributeType.Region3int16: Value = new Region3int16(this); break; default: throw new InvalidDataException($"Cannot handle AttributeType {DataType}!"); ////////////////////////// } reader = null; } public void Dispose() { reader.Dispose(); } internal Attribute(BinaryReader reader) { this.reader = reader; readData(); } internal Attribute(MemoryStream stream) { reader = new BinaryReader(stream); readData(); } } public class Attributes : Dictionary { 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); } } internal byte[] Serialize() { // TODO return Array.Empty(); } } }