diff --git a/BinaryFormat/Chunks/PROP.cs b/BinaryFormat/Chunks/PROP.cs index fbcf33e..f3463cc 100644 --- a/BinaryFormat/Chunks/PROP.cs +++ b/BinaryFormat/Chunks/PROP.cs @@ -87,6 +87,9 @@ namespace RobloxFiles.BinaryFormat.Chunks // Check if this is going to be casted as a BinaryString. // BinaryStrings should use a type of byte[] instead. + if (Name == "AttributesSerialize") + return buffer; + Property prop = props[i]; Instance instance = prop.Instance; diff --git a/DataTypes/BrickColor.cs b/DataTypes/BrickColor.cs index 8bd7e21..d85af38 100644 --- a/DataTypes/BrickColor.cs +++ b/DataTypes/BrickColor.cs @@ -26,6 +26,9 @@ namespace RobloxFiles.DataTypes private const string DefaultName = "Medium stone grey"; private const int DefaultNumber = 194; + public static implicit operator int(BrickColor color) => color.Number; + public static implicit operator BrickColor(int number) => FromNumber(number); + internal BrickColor(int number, uint rgb, string name) { uint r = (rgb / 65536) % 256; @@ -43,6 +46,7 @@ namespace RobloxFiles.DataTypes ByNumber = BrickColors.ColorMap.ToDictionary(brickColor => brickColor.Number); ByPalette = BrickColors.PaletteMap.Select(number => ByNumber[number]).ToList(); } + public static BrickColor FromName(string name) { diff --git a/DataTypes/CFrame.cs b/DataTypes/CFrame.cs index 9e9eb41..0726bb3 100644 --- a/DataTypes/CFrame.cs +++ b/DataTypes/CFrame.cs @@ -1,4 +1,5 @@ using System; +using RobloxFiles.Enums; namespace RobloxFiles.DataTypes { @@ -121,6 +122,50 @@ namespace RobloxFiles.DataTypes m31 = comp[9]; m32 = comp[10]; m33 = comp[11]; } + private void initFromMatrix(Vector3 pos, Vector3 vX, Vector3 vY, Vector3 vZ = null) + { + if (vZ == null) + vZ = vX.Cross(vY); + + m14 = pos.X; m24 = pos.Y; m34 = pos.Z; + m11 = vX.X; m12 = vX.Y; m13 = vX.Z; + m21 = vY.X; m22 = vY.Y; m23 = vY.Z; + m31 = vZ.X; m32 = vZ.Y; m33 = vZ.Z; + } + + public CFrame(Vector3 pos, Vector3 vX, Vector3 vY, Vector3 vZ = null) + { + initFromMatrix(pos, vX, vY, vZ); + } + + internal CFrame(Attribute attr) + { + Vector3 pos = new Vector3(attr); + byte rawOrientId = attr.readByte(); + + if (rawOrientId > 0) + { + // Make sure this value is in a safe range. + int orientId = (rawOrientId - 1) % 36; + + NormalId xColumn = (NormalId)(orientId / 6); + Vector3 vX = Vector3.FromNormalId(xColumn); + + NormalId yColumn = (NormalId)(orientId % 6); + Vector3 vY = Vector3.FromNormalId(yColumn); + + initFromMatrix(pos, vX, vY); + } + else + { + Vector3 vX = new Vector3(attr), + vY = new Vector3(attr), + vZ = new Vector3(attr); + + initFromMatrix(pos, vX, vY, vZ); + } + } + public static CFrame operator +(CFrame a, Vector3 b) { float[] ac = a.GetComponents(); diff --git a/DataTypes/Color3.cs b/DataTypes/Color3.cs index 381d50a..90ea391 100644 --- a/DataTypes/Color3.cs +++ b/DataTypes/Color3.cs @@ -5,6 +5,7 @@ namespace RobloxFiles.DataTypes public class Color3 { public readonly float R, G, B; + public override string ToString() => $"{R}, {G}, {B}"; public Color3(float r = 0, float g = 0, float b = 0) { @@ -13,9 +14,11 @@ namespace RobloxFiles.DataTypes B = b; } - public override string ToString() + internal Color3(Attribute attr) { - return string.Join(", ", R, G, B); + R = attr.readFloat(); + G = attr.readFloat(); + B = attr.readFloat(); } public static Color3 FromRGB(uint r = 0, uint g = 0, uint b = 0) diff --git a/DataTypes/Color3uint8.cs b/DataTypes/Color3uint8.cs index a436a52..d173359 100644 --- a/DataTypes/Color3uint8.cs +++ b/DataTypes/Color3uint8.cs @@ -7,6 +7,7 @@ public class Color3uint8 { public readonly byte R, G, B; + public override string ToString() => $"{R}, {G}, {B}"; public Color3uint8(byte r = 0, byte g = 0, byte b = 0) { @@ -15,11 +16,6 @@ B = b; } - public override string ToString() - { - return string.Join(", ", R, G, B); - } - public static implicit operator Color3(Color3uint8 color) { float r = color.R / 255f; diff --git a/DataTypes/ColorSequence.cs b/DataTypes/ColorSequence.cs index 9609e64..ef2d835 100644 --- a/DataTypes/ColorSequence.cs +++ b/DataTypes/ColorSequence.cs @@ -6,6 +6,11 @@ namespace RobloxFiles.DataTypes { public readonly ColorSequenceKeypoint[] Keypoints; + public override string ToString() + { + return string.Join(" ", Keypoints); + } + public ColorSequence(Color3 c) : this(c, c) { } @@ -42,10 +47,16 @@ namespace RobloxFiles.DataTypes Keypoints = keypoints; } - - public override string ToString() + + public ColorSequence(Attribute attr) { - return string.Join(" ", Keypoints); + int numKeys = attr.readInt(); + var keypoints = new ColorSequenceKeypoint[numKeys]; + + for (int i = 0; i < numKeys; i++) + keypoints[i] = new ColorSequenceKeypoint(attr); + + Keypoints = keypoints; } } } diff --git a/DataTypes/ColorSequenceKeypoint.cs b/DataTypes/ColorSequenceKeypoint.cs index 04570d6..33652c4 100644 --- a/DataTypes/ColorSequenceKeypoint.cs +++ b/DataTypes/ColorSequenceKeypoint.cs @@ -6,6 +6,11 @@ public readonly Color3 Value; public readonly int Envelope; + public override string ToString() + { + return $"{Time} {Value.R} {Value.G} {Value.B} {Envelope}"; + } + public ColorSequenceKeypoint(float time, Color3 value, int envelope = 0) { Time = time; @@ -13,9 +18,11 @@ Envelope = envelope; } - public override string ToString() + internal ColorSequenceKeypoint(Attribute attr) { - return string.Join(" ", Time, Value.R, Value.G, Value.B, Envelope); + Envelope = attr.readInt(); + Time = attr.readFloat(); + Value = new Color3(attr); } } } diff --git a/DataTypes/Content.cs b/DataTypes/Content.cs index 51536d2..5a1c46b 100644 --- a/DataTypes/Content.cs +++ b/DataTypes/Content.cs @@ -7,11 +7,7 @@ public class Content { public readonly string Url; - - public override string ToString() - { - return Url; - } + public override string ToString() => Url; public Content(string url) { diff --git a/DataTypes/NumberRange.cs b/DataTypes/NumberRange.cs index a95b210..ee9f7ac 100644 --- a/DataTypes/NumberRange.cs +++ b/DataTypes/NumberRange.cs @@ -7,24 +7,39 @@ namespace RobloxFiles.DataTypes public readonly float Min; public readonly float Max; + public override string ToString() => $"{Min} {Max}"; + public NumberRange(float num) { Min = num; Max = num; } + private static void checkRange(float min, float max) + { + if (max - min >= 0) + return; + + throw new Exception("NumberRange: invalid range"); + } + public NumberRange(float min = 0, float max = 0) { - if (max - min < 0) - throw new Exception("NumberRange: invalid range"); + checkRange(min, max); Min = min; Max = max; } - public override string ToString() + internal NumberRange(Attribute attr) { - return string.Join(" ", Min, Max); + float min = attr.readFloat(); + float max = attr.readFloat(); + + checkRange(min, max); + + Min = min; + Max = max; } } } diff --git a/DataTypes/NumberSequence.cs b/DataTypes/NumberSequence.cs index f789c0f..bc9c0e8 100644 --- a/DataTypes/NumberSequence.cs +++ b/DataTypes/NumberSequence.cs @@ -6,6 +6,11 @@ namespace RobloxFiles.DataTypes { public readonly NumberSequenceKeypoint[] Keypoints; + public override string ToString() + { + return string.Join(" ", Keypoints); + } + public NumberSequence(float n) { NumberSequenceKeypoint a = new NumberSequenceKeypoint(0, n); @@ -47,9 +52,15 @@ namespace RobloxFiles.DataTypes Keypoints = keypoints; } - public override string ToString() + public NumberSequence(Attribute attr) { - return string.Join(" ", Keypoints); + int numKeys = attr.readInt(); + var keypoints = new NumberSequenceKeypoint[numKeys]; + + for (int i = 0; i < numKeys; i++) + keypoints[i] = new NumberSequenceKeypoint(attr); + + Keypoints = keypoints; } } } diff --git a/DataTypes/NumberSequenceKeypoint.cs b/DataTypes/NumberSequenceKeypoint.cs index 381fdba..d2e7368 100644 --- a/DataTypes/NumberSequenceKeypoint.cs +++ b/DataTypes/NumberSequenceKeypoint.cs @@ -6,6 +6,11 @@ public readonly float Value; public readonly float Envelope; + public override string ToString() + { + return $"{Time} {Value} {Envelope}"; + } + public NumberSequenceKeypoint(float time, float value, float envelope = 0) { Time = time; @@ -13,9 +18,11 @@ Envelope = envelope; } - public override string ToString() + internal NumberSequenceKeypoint(Attribute attr) { - return string.Join(" ", Time, Value, Envelope); + Envelope = attr.readFloat(); + Time = attr.readFloat(); + Value = attr.readFloat(); } } } diff --git a/DataTypes/PhysicalProperties.cs b/DataTypes/PhysicalProperties.cs index 764fe25..d9d843f 100644 --- a/DataTypes/PhysicalProperties.cs +++ b/DataTypes/PhysicalProperties.cs @@ -12,6 +12,11 @@ namespace RobloxFiles.DataTypes public readonly float FrictionWeight = 1.0f; public readonly float ElasticityWeight = 1.0f; + public override string ToString() + { + return $"{Density}, {Friction}, {Elasticity}, {FrictionWeight}, {ElasticityWeight}"; + } + public PhysicalProperties(Material material) { if (MaterialInfo.FrictionWeightMap.ContainsKey(material)) @@ -32,9 +37,14 @@ namespace RobloxFiles.DataTypes ElasticityWeight = elasticityWeight; } - public override string ToString() + internal PhysicalProperties(Attribute attr) { - return string.Join(", ", Density, Friction, Elasticity, FrictionWeight, ElasticityWeight); + Density = attr.readFloat(); + Friction = attr.readFloat(); + Elasticity = attr.readFloat(); + + FrictionWeight = attr.readFloat(); + ElasticityWeight = attr.readFloat(); } } } diff --git a/DataTypes/ProtectedString.cs b/DataTypes/ProtectedString.cs index 89b1197..3b6bc57 100644 --- a/DataTypes/ProtectedString.cs +++ b/DataTypes/ProtectedString.cs @@ -7,11 +7,7 @@ public class ProtectedString { public readonly string ProtectedValue; - - public override string ToString() - { - return ProtectedValue; - } + public override string ToString() => ProtectedValue; public ProtectedString(string value) { diff --git a/DataTypes/Quaternion.cs b/DataTypes/Quaternion.cs index 722ce35..8f37d5a 100644 --- a/DataTypes/Quaternion.cs +++ b/DataTypes/Quaternion.cs @@ -9,6 +9,7 @@ namespace RobloxFiles.DataTypes public class Quaternion { public readonly float X, Y, Z, W; + public override string ToString() => $"{X}, {Y}, {Z}, {W}"; public float Magnitude { @@ -20,12 +21,7 @@ namespace RobloxFiles.DataTypes return (float)magnitude; } } - - public override string ToString() - { - return string.Join(", ", X, Y, Z, W); - } - + public Quaternion(float x, float y, float z, float w) { X = x; diff --git a/DataTypes/Ray.cs b/DataTypes/Ray.cs index 11ae42d..47d61f8 100644 --- a/DataTypes/Ray.cs +++ b/DataTypes/Ray.cs @@ -5,6 +5,8 @@ public readonly Vector3 Origin; public readonly Vector3 Direction; + public override string ToString() => $"{{{Origin}}}, {{{Direction}}}"; + public Ray Unit { get @@ -26,9 +28,10 @@ Direction = direction ?? new Vector3(); } - public override string ToString() + internal Ray(Attribute attr) { - return '{' + Origin.ToString() + "}, {" + Direction.ToString() + '}'; + Origin = new Vector3(attr); + Direction = new Vector3(attr); } public Vector3 ClosestPoint(Vector3 point) diff --git a/DataTypes/Rect.cs b/DataTypes/Rect.cs index 3ab6ad1..4cec9b3 100644 --- a/DataTypes/Rect.cs +++ b/DataTypes/Rect.cs @@ -8,6 +8,8 @@ public float Width => (Max - Min).X; public float Height => (Max - Min).Y; + public override string ToString() => $"{Min}, {Max}"; + public Rect(Vector2 min = null, Vector2 max = null) { Min = min ?? Vector2.Zero; @@ -20,9 +22,10 @@ Max = new Vector2(maxX, maxY); } - public override string ToString() + internal Rect(Attribute attr) { - return string.Join(", ", Min, Max); + Min = new Vector2(attr); + Max = new Vector2(attr); } } } diff --git a/DataTypes/Region3.cs b/DataTypes/Region3.cs index fb8c766..7c2a00c 100644 --- a/DataTypes/Region3.cs +++ b/DataTypes/Region3.cs @@ -4,37 +4,39 @@ namespace RobloxFiles.DataTypes { public class Region3 { - public readonly CFrame CFrame; - public readonly Vector3 Size; + public readonly Vector3 Min, Max; - public Region3(Vector3 a, Vector3 b) + public Vector3 Size => (Max - Min); + public CFrame CFrame => new CFrame((Min + Max) / 2); + + public override string ToString() => $"{CFrame}; {Size}"; + + public Region3(Vector3 min, Vector3 max) { - CFrame = new CFrame((a + b) / 2); - Size = b - a; + Min = min; + Max = max; } - public override string ToString() + internal Region3(Attribute attr) { - return CFrame + "; " + Size; + Min = new Vector3(attr); + Max = new Vector3(attr); } - + public Region3 ExpandToGrid(float resolution) { - Vector3 min = (CFrame - (Size / 2)).Position / resolution; - Vector3 max = (CFrame + (Size / 2)).Position / resolution; - Vector3 emin = new Vector3 ( - (float)Math.Floor(min.X) * resolution, - (float)Math.Floor(min.Y) * resolution, - (float)Math.Floor(min.Z) * resolution + (float)Math.Floor(Min.X) * resolution, + (float)Math.Floor(Min.Y) * resolution, + (float)Math.Floor(Min.Z) * resolution ); Vector3 emax = new Vector3 ( - (float)Math.Floor(max.X) * resolution, - (float)Math.Floor(max.Y) * resolution, - (float)Math.Floor(max.Z) * resolution + (float)Math.Floor(Max.X) * resolution, + (float)Math.Floor(Max.Y) * resolution, + (float)Math.Floor(Max.Z) * resolution ); return new Region3(emin, emax); diff --git a/DataTypes/Region3int16.cs b/DataTypes/Region3int16.cs index a029831..7b47d4c 100644 --- a/DataTypes/Region3int16.cs +++ b/DataTypes/Region3int16.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace RobloxFiles.DataTypes +namespace RobloxFiles.DataTypes { - public struct Region3int16 + public class Region3int16 { public readonly Vector3int16 Min, Max; + public override string ToString() => $"{Min}; {Max}"; public Region3int16(Vector3int16 min = null, Vector3int16 max = null) { @@ -16,9 +11,10 @@ namespace RobloxFiles.DataTypes Max = max ?? new Vector3int16(); } - public override string ToString() + internal Region3int16(Attribute attr) { - return string.Join("; ", Min, Max); + Min = new Vector3int16(attr); + Max = new Vector3int16(attr); } } } diff --git a/DataTypes/UDim.cs b/DataTypes/UDim.cs index c3d6ba8..0087454 100644 --- a/DataTypes/UDim.cs +++ b/DataTypes/UDim.cs @@ -5,12 +5,20 @@ public readonly float Scale; public readonly int Offset; + public override string ToString() => $"{Scale}, {Offset}"; + public UDim(float scale = 0, int offset = 0) { Scale = scale; Offset = offset; } + internal UDim(Attribute attr) + { + Scale = attr.readFloat(); + Offset = attr.readInt(); + } + public static UDim operator+(UDim a, UDim b) { return new UDim(a.Scale + b.Scale, a.Offset + b.Offset); @@ -20,10 +28,5 @@ { return new UDim(a.Scale - b.Scale, a.Offset - b.Offset); } - - public override string ToString() - { - return string.Join(", ", Scale, Offset); - } } } \ No newline at end of file diff --git a/DataTypes/UDim2.cs b/DataTypes/UDim2.cs index 9c734ae..21b913d 100644 --- a/DataTypes/UDim2.cs +++ b/DataTypes/UDim2.cs @@ -3,6 +3,7 @@ public class UDim2 { public readonly UDim X, Y; + public override string ToString() => $"{{{X}}},{{{Y}}}"; public UDim Width => X; public UDim Height => Y; @@ -19,11 +20,12 @@ Y = y; } - public override string ToString() + internal UDim2(Attribute attr) { - return '{' + X.ToString() + "},{" + Y.ToString() + '}'; + X = new UDim(attr); + Y = new UDim(attr); } - + public UDim2 Lerp(UDim2 other, float alpha) { float scaleX = X.Scale + ((other.X.Scale - X.Scale) * alpha); diff --git a/DataTypes/Vector2.cs b/DataTypes/Vector2.cs index ddb8289..6e28a2f 100644 --- a/DataTypes/Vector2.cs +++ b/DataTypes/Vector2.cs @@ -5,7 +5,8 @@ namespace RobloxFiles.DataTypes public class Vector2 { public readonly float X, Y; - + public override string ToString() => $"{X}, {Y}"; + public float Magnitude { get @@ -34,6 +35,12 @@ namespace RobloxFiles.DataTypes Y = coords.Length > 1 ? coords[1] : 0; } + internal Vector2(Attribute attr) + { + X = attr.readFloat(); + Y = attr.readFloat(); + } + private delegate Vector2 Operator(Vector2 a, Vector2 b); private static Vector2 upcastFloatOp(Vector2 vec, float num, Operator upcast) @@ -76,11 +83,6 @@ namespace RobloxFiles.DataTypes public static Vector2 Zero => new Vector2(0, 0); - public override string ToString() - { - return string.Join(", ", X, Y); - } - public float Dot(Vector2 other) { float dotX = X * other.X; diff --git a/DataTypes/Vector2int16.cs b/DataTypes/Vector2int16.cs new file mode 100644 index 0000000..6e1535e --- /dev/null +++ b/DataTypes/Vector2int16.cs @@ -0,0 +1,69 @@ +using System; + +namespace RobloxFiles.DataTypes +{ + public class Vector2int16 + { + public readonly short X, Y; + public override string ToString() => $"{X}, {Y}"; + + public Vector2int16(short x = 0, short y = 0) + { + X = x; + Y = y; + } + + public Vector2int16(int x = 0, int y = 0) + { + X = (short)x; + Y = (short)y; + } + + internal Vector2int16(Attribute attr) + { + X = attr.readShort(); + Y = attr.readShort(); + } + + private delegate Vector2int16 Operator(Vector2int16 a, Vector2int16 b); + + private static Vector2int16 upcastShortOp(Vector2int16 vec, short num, Operator upcast) + { + Vector2int16 numVec = new Vector2int16(num, num); + return upcast(vec, numVec); + } + + private static Vector2int16 upcastShortOp(short num, Vector2int16 vec, Operator upcast) + { + Vector2int16 numVec = new Vector2int16(num, num); + return upcast(numVec, vec); + } + + private static Operator add = new Operator((a, b) => new Vector2int16(a.X + b.X, a.Y + b.Y)); + private static Operator sub = new Operator((a, b) => new Vector2int16(a.X - b.X, a.Y - b.Y)); + private static Operator mul = new Operator((a, b) => new Vector2int16(a.X * b.X, a.Y * b.Y)); + private static Operator div = new Operator((a, b) => + { + if (b.X == 0 || b.Y == 0) + throw new DivideByZeroException(); + + return new Vector2int16(a.X / b.X, a.Y / b.Y); + }); + + public static Vector2int16 operator +(Vector2int16 a, Vector2int16 b) => add(a, b); + public static Vector2int16 operator +(Vector2int16 v, short n) => upcastShortOp(v, n, add); + public static Vector2int16 operator +(short n, Vector2int16 v) => upcastShortOp(n, v, add); + + public static Vector2int16 operator -(Vector2int16 a, Vector2int16 b) => sub(a, b); + public static Vector2int16 operator -(Vector2int16 v, short n) => upcastShortOp(v, n, sub); + public static Vector2int16 operator -(short n, Vector2int16 v) => upcastShortOp(n, v, sub); + + public static Vector2int16 operator *(Vector2int16 a, Vector2int16 b) => mul(a, b); + public static Vector2int16 operator *(Vector2int16 v, short n) => upcastShortOp(v, n, mul); + public static Vector2int16 operator *(short n, Vector2int16 v) => upcastShortOp(n, v, mul); + + public static Vector2int16 operator /(Vector2int16 a, Vector2int16 b) => div(a, b); + public static Vector2int16 operator /(Vector2int16 v, short n) => upcastShortOp(v, n, div); + public static Vector2int16 operator /(short n, Vector2int16 v) => upcastShortOp(n, v, div); + } +} diff --git a/DataTypes/Vector3.cs b/DataTypes/Vector3.cs index 65fe164..8461cb2 100644 --- a/DataTypes/Vector3.cs +++ b/DataTypes/Vector3.cs @@ -6,6 +6,7 @@ namespace RobloxFiles.DataTypes public class Vector3 { public readonly float X, Y, Z; + public override string ToString() => $"{X}, {Y}, {Z}"; public float Magnitude { @@ -37,6 +38,13 @@ namespace RobloxFiles.DataTypes Z = coords.Length > 2 ? coords[2] : 0; } + internal Vector3(Attribute attr) + { + X = attr.readFloat(); + Y = attr.readFloat(); + Z = attr.readFloat(); + } + public static Vector3 FromAxis(Axis axis) { float[] coords = new float[3] { 0f, 0f, 0f }; @@ -102,10 +110,7 @@ namespace RobloxFiles.DataTypes public static Vector3 Up => new Vector3(0, 1, 0); public static Vector3 Back => new Vector3(0, 0, 1); - public override string ToString() - { - return string.Join(", ", X, Y, Z); - } + public float Dot(Vector3 other) { diff --git a/DataTypes/Vector3int16.cs b/DataTypes/Vector3int16.cs index 6ac48cb..82da093 100644 --- a/DataTypes/Vector3int16.cs +++ b/DataTypes/Vector3int16.cs @@ -5,12 +5,10 @@ namespace RobloxFiles.DataTypes public class Vector3int16 { public readonly short X, Y, Z; + public override string ToString() => $"{X}, {Y}, {Z}"; - public Vector3int16() + public Vector3int16() : this(0, 0, 0) { - X = 0; - Y = 0; - Z = 0; } public Vector3int16(short x = 0, short y = 0, short z = 0) @@ -27,9 +25,11 @@ namespace RobloxFiles.DataTypes Z = (short)z; } - public override string ToString() + internal Vector3int16(Attribute attr) { - return string.Join(", ", X, Y, Z); + X = attr.readShort(); + Y = attr.readShort(); + Z = attr.readShort(); } private delegate Vector3int16 Operator(Vector3int16 a, Vector3int16 b); diff --git a/RobloxFileFormat.csproj b/RobloxFileFormat.csproj index 35bf185..bb5b11f 100644 --- a/RobloxFileFormat.csproj +++ b/RobloxFileFormat.csproj @@ -81,10 +81,13 @@ + + + diff --git a/RobloxFileFormat.dll b/RobloxFileFormat.dll index 7ad1cc2..1eb362f 100644 Binary files a/RobloxFileFormat.dll and b/RobloxFileFormat.dll differ diff --git a/Tree/Attributes.cs b/Tree/Attributes.cs new file mode 100644 index 0000000..2ffafa9 --- /dev/null +++ b/Tree/Attributes.cs @@ -0,0 +1,252 @@ +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 + { + 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); + + private Attribute[] readArray() + { + int count = readInt(); + var result = new Attribute[count]; + + for (int i = 0; i < count; i++) + result[i] = new Attribute(reader); + + return result; + } + + private object readEnum() + { + string name = readString(); + int value = readInt(); + + try + { + Type enumType = Type.GetType($"RobloxFiles.Enums.{name}"); + return Enum.ToObject(enumType, value); + } + catch + { + Console.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; + } + + 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) + { + 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 new byte[0]; + } + } +} diff --git a/Tree/Instance.cs b/Tree/Instance.cs index 2d95dca..d22f398 100644 --- a/Tree/Instance.cs +++ b/Tree/Instance.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Text; @@ -57,9 +58,23 @@ namespace RobloxFiles /// A list of CollectionService tags assigned to this Instance. public List Tags => RawTags; + /// The attributes defined for this Instance. + public Attributes Attributes { get; private set; } + /// The internal serialized data of this Instance's attributes - internal byte[] AttributesSerialize; - + internal byte[] AttributesSerialize + { + get + { + return Attributes?.Serialize() ?? new byte[0]; + } + set + { + MemoryStream data = new MemoryStream(value); + Attributes = new Attributes(data); + } + } + /// /// Internal format of the Instance's CollectionService tags. /// Property objects will look to this member for serializing the Tags property. @@ -538,6 +553,7 @@ namespace RobloxFiles } Property tags = GetProperty("Tags"); + Property attributes = GetProperty("AttributesSerialize"); if (tags == null) { @@ -545,6 +561,12 @@ namespace RobloxFiles AddProperty(ref tags); } + if (attributes == null) + { + attributes = new Property("AttributesSerialize", PropertyType.String); + AddProperty(ref attributes); + } + return Properties; } } diff --git a/Tree/Property.cs b/Tree/Property.cs index a8f5de9..c29e270 100644 --- a/Tree/Property.cs +++ b/Tree/Property.cs @@ -166,6 +166,11 @@ namespace RobloxFiles byte[] data = Instance.SerializedTags; RawValue = data; } + else if (Name == "AttributesSerialize") + { + byte[] data = Instance.AttributesSerialize; + RawValue = data; + } else { FieldInfo field = Instance.GetType() @@ -194,6 +199,11 @@ namespace RobloxFiles byte[] data = value as byte[]; Instance.SerializedTags = data; } + else if (Name == "AttributesSerialize" && value is byte[]) + { + byte[] data = value as byte[]; + Instance.AttributesSerialize = data; + } else { FieldInfo field = Instance.GetType() diff --git a/Utility/Formatting.cs b/Utility/Formatting.cs index 4a7ddec..aea7fb9 100644 --- a/Utility/Formatting.cs +++ b/Utility/Formatting.cs @@ -1,5 +1,7 @@ using System; +using System.IO; using System.Globalization; +using System.Text; internal static class Formatting { @@ -112,4 +114,15 @@ internal static class Formatting { return Math.Abs(a - b) < epsilon; } + + public static string ReadString(this BinaryReader reader, bool useIntLength) + { + if (!useIntLength) + return reader.ReadString(); + + int len = reader.ReadInt32(); + byte[] buffer = reader.ReadBytes(len); + + return Encoding.UTF8.GetString(buffer); + } } \ No newline at end of file