diff --git a/DataTypes/CFrame.cs b/DataTypes/CFrame.cs index c93f867..fb5305a 100644 --- a/DataTypes/CFrame.cs +++ b/DataTypes/CFrame.cs @@ -6,9 +6,11 @@ namespace RobloxFiles.DataTypes { public class CFrame { - private float m11 = 1, m12, m13, m14; - private float m21, m22 = 1, m23, m24; - private float m31, m32, m33 = 1, m34; + private float m14, m24, m34; + + private readonly float m11 = 1, m12, m13; + private readonly float m21, m22 = 1, m23; + private readonly float m31, m32, m33 = 1; private const float m41 = 0, m42 = 0, m43 = 0, m44 = 1; @@ -160,49 +162,17 @@ namespace RobloxFiles.DataTypes m31 = comp[9]; m32 = comp[10]; m33 = comp[11]; } - private void InitFromMatrix(Vector3 pos, Vector3 vX, Vector3 vY, Vector3 vZ = null) + public CFrame(Vector3 pos, Vector3 vX, Vector3 vY, Vector3 vZ = null) { + Contract.Requires(pos != null && vX != null && vY != 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) - { - Contract.Requires(pos != null && vX != null && vY != 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); - } + 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 static CFrame operator +(CFrame a, Vector3 b) diff --git a/DataTypes/Color3.cs b/DataTypes/Color3.cs index 8491369..baf2b56 100644 --- a/DataTypes/Color3.cs +++ b/DataTypes/Color3.cs @@ -40,13 +40,6 @@ namespace RobloxFiles.DataTypes return true; } - internal Color3(Attribute attr) - { - R = attr.ReadFloat(); - G = attr.ReadFloat(); - B = attr.ReadFloat(); - } - public static Color3 FromRGB(uint r = 0, uint g = 0, uint b = 0) { return new Color3(r / 255f, g / 255f, b / 255f); diff --git a/DataTypes/ColorSequence.cs b/DataTypes/ColorSequence.cs index 8c0b177..c855b17 100644 --- a/DataTypes/ColorSequence.cs +++ b/DataTypes/ColorSequence.cs @@ -86,16 +86,5 @@ namespace RobloxFiles.DataTypes Keypoints = keypoints; } - - public ColorSequence(Attribute attr) - { - 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 857515f..1cf9a70 100644 --- a/DataTypes/ColorSequenceKeypoint.cs +++ b/DataTypes/ColorSequenceKeypoint.cs @@ -19,13 +19,6 @@ Envelope = envelope; } - internal ColorSequenceKeypoint(Attribute attr) - { - Envelope = attr.ReadInt(); - Time = attr.ReadFloat(); - Value = new Color3(attr); - } - public override int GetHashCode() { int hash = Time.GetHashCode() diff --git a/DataTypes/NumberRange.cs b/DataTypes/NumberRange.cs index 0375865..444d65a 100644 --- a/DataTypes/NumberRange.cs +++ b/DataTypes/NumberRange.cs @@ -25,10 +25,6 @@ namespace RobloxFiles.DataTypes Max = max; } - internal NumberRange(Attribute attr) : this(attr.ReadFloat(), attr.ReadFloat()) - { - } - public override int GetHashCode() { return Min.GetHashCode() ^ Max.GetHashCode(); diff --git a/DataTypes/NumberSequence.cs b/DataTypes/NumberSequence.cs index c6f0b7c..b7d81e5 100644 --- a/DataTypes/NumberSequence.cs +++ b/DataTypes/NumberSequence.cs @@ -52,17 +52,6 @@ namespace RobloxFiles.DataTypes Keypoints = keypoints; } - public NumberSequence(Attribute attr) - { - int numKeys = attr.ReadInt(); - var keypoints = new NumberSequenceKeypoint[numKeys]; - - for (int i = 0; i < numKeys; i++) - keypoints[i] = new NumberSequenceKeypoint(attr); - - Keypoints = keypoints; - } - public override int GetHashCode() { int hash = 0; diff --git a/DataTypes/Ray.cs b/DataTypes/Ray.cs index e16559f..087e05c 100644 --- a/DataTypes/Ray.cs +++ b/DataTypes/Ray.cs @@ -28,12 +28,6 @@ Direction = direction ?? new Vector3(); } - internal Ray(Attribute attr) - { - Origin = new Vector3(attr); - Direction = new Vector3(attr); - } - public Vector3 ClosestPoint(Vector3 point) { Vector3 result = Origin; diff --git a/DataTypes/Rect.cs b/DataTypes/Rect.cs index 884c5cd..a43bd90 100644 --- a/DataTypes/Rect.cs +++ b/DataTypes/Rect.cs @@ -22,12 +22,6 @@ Max = new Vector2(maxX, maxY); } - internal Rect(Attribute attr) - { - Min = new Vector2(attr); - Max = new Vector2(attr); - } - public override int GetHashCode() { int hash = Min.GetHashCode() diff --git a/DataTypes/Region3.cs b/DataTypes/Region3.cs index fabb333..79cf59c 100644 --- a/DataTypes/Region3.cs +++ b/DataTypes/Region3.cs @@ -17,12 +17,6 @@ namespace RobloxFiles.DataTypes Max = max; } - internal Region3(Attribute attr) - { - Min = new Vector3(attr); - Max = new Vector3(attr); - } - public Region3 ExpandToGrid(float resolution) { Vector3 emin = new Vector3 diff --git a/DataTypes/UDim.cs b/DataTypes/UDim.cs index f492243..18ad550 100644 --- a/DataTypes/UDim.cs +++ b/DataTypes/UDim.cs @@ -13,12 +13,6 @@ 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); diff --git a/DataTypes/UDim2.cs b/DataTypes/UDim2.cs index ff878f4..12d3764 100644 --- a/DataTypes/UDim2.cs +++ b/DataTypes/UDim2.cs @@ -20,12 +20,6 @@ Y = y; } - internal UDim2(Attribute attr) - { - 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 72a3c6e..e62068c 100644 --- a/DataTypes/Vector2.cs +++ b/DataTypes/Vector2.cs @@ -29,27 +29,21 @@ namespace RobloxFiles.DataTypes Y = y; } - internal Vector2(float[] coords) + public Vector2(params float[] coords) { X = coords.Length > 0 ? coords[0] : 0; 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) + private static Vector2 UpcastFloatOp(Vector2 vec, float num, Operator upcast) { Vector2 numVec = new Vector2(num, num); return upcast(vec, numVec); } - private static Vector2 upcastFloatOp(float num, Vector2 vec, Operator upcast) + private static Vector2 UpcastFloatOp(float num, Vector2 vec, Operator upcast) { Vector2 numVec = new Vector2(num, num); return upcast(numVec, vec); @@ -61,20 +55,20 @@ namespace RobloxFiles.DataTypes private static readonly Operator div = new Operator((a, b) => new Vector2(a.X / b.X, a.Y / b.Y)); public static Vector2 operator +(Vector2 a, Vector2 b) => add(a, b); - public static Vector2 operator +(Vector2 v, float n) => upcastFloatOp(v, n, add); - public static Vector2 operator +(float n, Vector2 v) => upcastFloatOp(n, v, add); + public static Vector2 operator +(Vector2 v, float n) => UpcastFloatOp(v, n, add); + public static Vector2 operator +(float n, Vector2 v) => UpcastFloatOp(n, v, add); public static Vector2 operator -(Vector2 a, Vector2 b) => sub(a, b); - public static Vector2 operator -(Vector2 v, float n) => upcastFloatOp(v, n, sub); - public static Vector2 operator -(float n, Vector2 v) => upcastFloatOp(n, v, sub); + public static Vector2 operator -(Vector2 v, float n) => UpcastFloatOp(v, n, sub); + public static Vector2 operator -(float n, Vector2 v) => UpcastFloatOp(n, v, sub); public static Vector2 operator *(Vector2 a, Vector2 b) => mul(a, b); - public static Vector2 operator *(Vector2 v, float n) => upcastFloatOp(v, n, mul); - public static Vector2 operator *(float n, Vector2 v) => upcastFloatOp(n, v, mul); + public static Vector2 operator *(Vector2 v, float n) => UpcastFloatOp(v, n, mul); + public static Vector2 operator *(float n, Vector2 v) => UpcastFloatOp(n, v, mul); public static Vector2 operator /(Vector2 a, Vector2 b) => div(a, b); - public static Vector2 operator /(Vector2 v, float n) => upcastFloatOp(v, n, div); - public static Vector2 operator /(float n, Vector2 v) => upcastFloatOp(n, v, div); + public static Vector2 operator /(Vector2 v, float n) => UpcastFloatOp(v, n, div); + public static Vector2 operator /(float n, Vector2 v) => UpcastFloatOp(n, v, div); public static Vector2 operator -(Vector2 v) { diff --git a/DataTypes/Vector3.cs b/DataTypes/Vector3.cs index ffb97ad..05cd072 100644 --- a/DataTypes/Vector3.cs +++ b/DataTypes/Vector3.cs @@ -32,20 +32,13 @@ namespace RobloxFiles.DataTypes Z = z; } - public Vector3(float[] coords) + public Vector3(params float[] coords) { X = coords.Length > 0 ? coords[0] : 0; Y = coords.Length > 1 ? coords[1] : 0; 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 }; @@ -68,13 +61,13 @@ namespace RobloxFiles.DataTypes private delegate Vector3 Operator(Vector3 a, Vector3 b); - private static Vector3 upcastFloatOp(Vector3 vec, float num, Operator upcast) + private static Vector3 UpcastFloatOp(Vector3 vec, float num, Operator upcast) { Vector3 numVec = new Vector3(num, num, num); return upcast(vec, numVec); } - private static Vector3 upcastFloatOp(float num, Vector3 vec, Operator upcast) + private static Vector3 UpcastFloatOp(float num, Vector3 vec, Operator upcast) { Vector3 numVec = new Vector3(num, num, num); return upcast(numVec, vec); @@ -86,30 +79,30 @@ namespace RobloxFiles.DataTypes private static readonly Operator div = new Operator((a, b) => new Vector3(a.X / b.X, a.Y / b.Y, a.Z / b.Z)); public static Vector3 operator +(Vector3 a, Vector3 b) => add(a, b); - public static Vector3 operator +(Vector3 v, float n) => upcastFloatOp(v, n, add); - public static Vector3 operator +(float n, Vector3 v) => upcastFloatOp(n, v, add); + public static Vector3 operator +(Vector3 v, float n) => UpcastFloatOp(v, n, add); + public static Vector3 operator +(float n, Vector3 v) => UpcastFloatOp(n, v, add); public static Vector3 operator -(Vector3 a, Vector3 b) => sub(a, b); - public static Vector3 operator -(Vector3 v, float n) => upcastFloatOp(v, n, sub); - public static Vector3 operator -(float n, Vector3 v) => upcastFloatOp(n, v, sub); + public static Vector3 operator -(Vector3 v, float n) => UpcastFloatOp(v, n, sub); + public static Vector3 operator -(float n, Vector3 v) => UpcastFloatOp(n, v, sub); public static Vector3 operator *(Vector3 a, Vector3 b) => mul(a, b); - public static Vector3 operator *(Vector3 v, float n) => upcastFloatOp(v, n, mul); - public static Vector3 operator *(float n, Vector3 v) => upcastFloatOp(n, v, mul); + public static Vector3 operator *(Vector3 v, float n) => UpcastFloatOp(v, n, mul); + public static Vector3 operator *(float n, Vector3 v) => UpcastFloatOp(n, v, mul); public static Vector3 operator /(Vector3 a, Vector3 b) => div(a, b); - public static Vector3 operator /(Vector3 v, float n) => upcastFloatOp(v, n, div); - public static Vector3 operator /(float n, Vector3 v) => upcastFloatOp(n, v, div); + public static Vector3 operator /(Vector3 v, float n) => UpcastFloatOp(v, n, div); + public static Vector3 operator /(float n, Vector3 v) => UpcastFloatOp(n, v, div); public static Vector3 operator -(Vector3 v) { return new Vector3(-v.X, -v.Y, -v.Z); } - public static Vector3 Zero => new Vector3(0, 0, 0); - public static Vector3 Right => new Vector3(1, 0, 0); - public static Vector3 Up => new Vector3(0, 1, 0); - public static Vector3 Back => new Vector3(0, 0, 1); + public static readonly Vector3 Zero = new Vector3(0, 0, 0); + public static readonly Vector3 Right = new Vector3(1, 0, 0); + public static readonly Vector3 Up = new Vector3(0, 1, 0); + public static readonly Vector3 Back = new Vector3(0, 0, 1); public float Dot(Vector3 other) { diff --git a/Interfaces/IAttributeToken.cs b/Interfaces/IAttributeToken.cs new file mode 100644 index 0000000..3956ac5 --- /dev/null +++ b/Interfaces/IAttributeToken.cs @@ -0,0 +1,10 @@ +namespace RobloxFiles +{ + public interface IAttributeToken + { + AttributeType AttributeType { get; } + + T ReadAttribute(Attribute attribute); + void WriteAttribute(Attribute attribute, T value); + } +} diff --git a/Interfaces/IXmlPropertyToken.cs b/Interfaces/IXmlPropertyToken.cs index 5a53f94..29decb1 100644 --- a/Interfaces/IXmlPropertyToken.cs +++ b/Interfaces/IXmlPropertyToken.cs @@ -1,10 +1,10 @@ using System.Xml; -namespace RobloxFiles.XmlFormat +namespace RobloxFiles.Tokens { public interface IXmlPropertyToken { - string Token { get; } + string XmlPropertyToken { get; } bool ReadProperty(Property prop, XmlNode token); void WriteProperty(Property prop, XmlDocument doc, XmlNode node); diff --git a/RobloxFileFormat.csproj b/RobloxFileFormat.csproj index 7a1eb54..153ee32 100644 --- a/RobloxFileFormat.csproj +++ b/RobloxFileFormat.csproj @@ -97,6 +97,7 @@ + @@ -130,39 +131,39 @@ - - - - + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RobloxFileFormat.dll b/RobloxFileFormat.dll index 4226d55..2c2d0ff 100644 Binary files a/RobloxFileFormat.dll and b/RobloxFileFormat.dll differ diff --git a/XmlFormat/Tokens/Axes.cs b/Tokens/Axes.cs similarity index 87% rename from XmlFormat/Tokens/Axes.cs rename to Tokens/Axes.cs index d5bc2ab..a557b7a 100644 --- a/XmlFormat/Tokens/Axes.cs +++ b/Tokens/Axes.cs @@ -1,11 +1,13 @@ using System.Xml; -using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +using RobloxFiles.DataTypes; +using RobloxFiles.XmlFormat; + +namespace RobloxFiles.Tokens { public class AxesToken : IXmlPropertyToken { - public string Token => "Axes"; + public string XmlPropertyToken => "Axes"; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/BinaryString.cs b/Tokens/BinaryString.cs similarity index 92% rename from XmlFormat/Tokens/BinaryString.cs rename to Tokens/BinaryString.cs index 10194a4..ec55759 100644 --- a/XmlFormat/Tokens/BinaryString.cs +++ b/Tokens/BinaryString.cs @@ -1,11 +1,11 @@ using System; using System.Xml; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { public class BinaryStringToken : IXmlPropertyToken { - public string Token => "BinaryString"; + public string XmlPropertyToken => "BinaryString"; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/Boolean.cs b/Tokens/Boolean.cs similarity index 53% rename from XmlFormat/Tokens/Boolean.cs rename to Tokens/Boolean.cs index f353334..36b5456 100644 --- a/XmlFormat/Tokens/Boolean.cs +++ b/Tokens/Boolean.cs @@ -1,10 +1,15 @@ using System.Xml; +using RobloxFiles.XmlFormat; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class BoolToken : IXmlPropertyToken + public class BoolToken : IXmlPropertyToken, IAttributeToken { - public string Token => "bool"; + public string XmlPropertyToken => "bool"; + public AttributeType AttributeType => AttributeType.Bool; + + public bool ReadAttribute(Attribute attr) => attr.ReadBool(); + public void WriteAttribute(Attribute attr, bool value) => attr.WriteBool(value); public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/BrickColor.cs b/Tokens/BrickColor.cs similarity index 53% rename from XmlFormat/Tokens/BrickColor.cs rename to Tokens/BrickColor.cs index 2f6ff9d..1c91428 100644 --- a/XmlFormat/Tokens/BrickColor.cs +++ b/Tokens/BrickColor.cs @@ -1,16 +1,20 @@ -using System; -using System.Xml; +using System.Xml; using RobloxFiles.DataTypes; +using RobloxFiles.XmlFormat; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class BrickColorToken : IXmlPropertyToken + public class BrickColorToken : IXmlPropertyToken, IAttributeToken { - public string Token => "BrickColor"; - // ^ This is a lie: The token is actually int, but that would cause a name collision. - // Since BrickColors are written as ints, the IntToken class will try to redirect - // to this handler if it believes that its representing a BrickColor. + // This is a lie: The token is actually int, but that would cause a name collision. + // Since BrickColors are written as ints, the IntToken class will try to redirect + // to this handler if it believes that its representing a BrickColor. + public string XmlPropertyToken => "BrickColor"; + public AttributeType AttributeType => AttributeType.BrickColor; + public BrickColor ReadAttribute(Attribute attr) => attr.ReadInt(); + public void WriteAttribute(Attribute attr, BrickColor value) => attr.WriteInt(value.Number); + public bool ReadProperty(Property prop, XmlNode token) { if (XmlPropertyTokens.ReadPropertyGeneric(token, out int value)) diff --git a/XmlFormat/Tokens/CFrame.cs b/Tokens/CFrame.cs similarity index 86% rename from XmlFormat/Tokens/CFrame.cs rename to Tokens/CFrame.cs index 416c7f4..31e0702 100644 --- a/XmlFormat/Tokens/CFrame.cs +++ b/Tokens/CFrame.cs @@ -1,12 +1,12 @@ using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { public class CFrameToken : IXmlPropertyToken { - public string Token => "CoordinateFrame; CFrame"; - private static string[] Coords = new string[12] { "X", "Y", "Z", "R00", "R01", "R02", "R10", "R11", "R12", "R20", "R21", "R22"}; + public string XmlPropertyToken => "CoordinateFrame; CFrame"; + private static readonly string[] Coords = new string[12] { "X", "Y", "Z", "R00", "R01", "R02", "R10", "R11", "R12", "R20", "R21", "R22"}; public static CFrame ReadCFrame(XmlNode token) { diff --git a/XmlFormat/Tokens/Color3.cs b/Tokens/Color3.cs similarity index 61% rename from XmlFormat/Tokens/Color3.cs rename to Tokens/Color3.cs index 64ce450..6a18b9a 100644 --- a/XmlFormat/Tokens/Color3.cs +++ b/Tokens/Color3.cs @@ -1,22 +1,42 @@ -using System; -using System.Xml; +using System.Xml; using RobloxFiles.DataTypes; +using RobloxFiles.XmlFormat; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class Color3Token : IXmlPropertyToken + public class Color3Token : IXmlPropertyToken, IAttributeToken { - public string Token => "Color3"; - private readonly string[] Fields = new string[3] { "R", "G", "B" }; + public string XmlPropertyToken => "Color3"; + private readonly string[] XmlFields = new string[3] { "R", "G", "B" }; + + public AttributeType AttributeType => AttributeType.Color3; + public Color3 ReadAttribute(Attribute attr) => ReadColor3(attr); + public void WriteAttribute(Attribute attr, Color3 value) => WriteColor3(attr, value); + + public static Color3 ReadColor3(Attribute attr) + { + float r = attr.ReadFloat(), + g = attr.ReadFloat(), + b = attr.ReadFloat(); + + return new Color3(r, g, b); + } + + public static void WriteColor3(Attribute attr, Color3 value) + { + attr.WriteFloat(value.R); + attr.WriteFloat(value.G); + attr.WriteFloat(value.B); + } public bool ReadProperty(Property prop, XmlNode token) { bool success = true; - float[] fields = new float[Fields.Length]; + float[] fields = new float[XmlFields.Length]; for (int i = 0; i < fields.Length; i++) { - string key = Fields[i]; + string key = XmlFields[i]; try { @@ -64,7 +84,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens for (int i = 0; i < 3; i++) { - string field = Fields[i]; + string field = XmlFields[i]; float value = rgb[i]; XmlElement channel = doc.CreateElement(field); diff --git a/XmlFormat/Tokens/Color3uint8.cs b/Tokens/Color3uint8.cs similarity index 90% rename from XmlFormat/Tokens/Color3uint8.cs rename to Tokens/Color3uint8.cs index 6b736aa..5d6a977 100644 --- a/XmlFormat/Tokens/Color3uint8.cs +++ b/Tokens/Color3uint8.cs @@ -1,13 +1,13 @@ using System.Xml; using RobloxFiles.DataTypes; +using RobloxFiles.XmlFormat; using System.Diagnostics.Contracts; -using System; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { public class Color3uint8Token : IXmlPropertyToken { - public string Token => "Color3uint8"; + public string XmlPropertyToken => "Color3uint8"; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/ColorSequence.cs b/Tokens/ColorSequence.cs similarity index 53% rename from XmlFormat/Tokens/ColorSequence.cs rename to Tokens/ColorSequence.cs index 424a884..3c0475c 100644 --- a/XmlFormat/Tokens/ColorSequence.cs +++ b/Tokens/ColorSequence.cs @@ -1,11 +1,12 @@ using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class ColorSequenceToken : IXmlPropertyToken + public class ColorSequenceToken : IXmlPropertyToken, IAttributeToken { - public string Token => "ColorSequence"; + public string XmlPropertyToken => "ColorSequence"; + public AttributeType AttributeType => AttributeType.ColorSequence; public bool ReadProperty(Property prop, XmlNode token) { @@ -19,7 +20,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens { try { - ColorSequenceKeypoint[] keypoints = new ColorSequenceKeypoint[length / 5]; + var keypoints = new ColorSequenceKeypoint[length / 5]; for (int i = 0; i < length; i += 5) { @@ -50,5 +51,35 @@ namespace RobloxFiles.XmlFormat.PropertyTokens ColorSequence value = prop.CastValue(); node.InnerText = value.ToString() + ' '; } + + public ColorSequence ReadAttribute(Attribute attr) + { + int numKeys = attr.ReadInt(); + var keypoints = new ColorSequenceKeypoint[numKeys]; + + for (int i = 0; i < numKeys; i++) + { + int envelope = attr.ReadInt(); + float time = attr.ReadFloat(); + + Color3 value = Color3Token.ReadColor3(attr); + keypoints[i] = new ColorSequenceKeypoint(time, value, envelope); + } + + return new ColorSequence(keypoints); + } + + public void WriteAttribute(Attribute attr, ColorSequence value) + { + attr.WriteInt(value.Keypoints.Length); + + foreach (var keypoint in value.Keypoints) + { + attr.WriteInt(keypoint.Envelope); + attr.WriteFloat(keypoint.Time); + + Color3Token.WriteColor3(attr, keypoint.Value); + } + } } } diff --git a/XmlFormat/Tokens/Content.cs b/Tokens/Content.cs similarity index 95% rename from XmlFormat/Tokens/Content.cs rename to Tokens/Content.cs index df83b82..9bcffff 100644 --- a/XmlFormat/Tokens/Content.cs +++ b/Tokens/Content.cs @@ -3,11 +3,11 @@ using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { public class ContentToken : IXmlPropertyToken { - public string Token => "Content"; + public string XmlPropertyToken => "Content"; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/Double.cs b/Tokens/Double.cs similarity index 50% rename from XmlFormat/Tokens/Double.cs rename to Tokens/Double.cs index 2eda661..c4c88e3 100644 --- a/XmlFormat/Tokens/Double.cs +++ b/Tokens/Double.cs @@ -1,10 +1,15 @@ using System.Xml; +using RobloxFiles.XmlFormat; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class DoubleToken : IXmlPropertyToken + public class DoubleToken : IXmlPropertyToken, IAttributeToken { - public string Token => "double"; + public string XmlPropertyToken => "double"; + public AttributeType AttributeType => AttributeType.Double; + + public double ReadAttribute(Attribute attr) => attr.ReadDouble(); + public void WriteAttribute(Attribute attr, double value) => attr.WriteDouble(value); public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/Enum.cs b/Tokens/Enum.cs similarity index 92% rename from XmlFormat/Tokens/Enum.cs rename to Tokens/Enum.cs index b237d68..eeaf262 100644 --- a/XmlFormat/Tokens/Enum.cs +++ b/Tokens/Enum.cs @@ -3,11 +3,13 @@ using System.Diagnostics.Contracts; using System.Reflection; using System.Xml; -namespace RobloxFiles.XmlFormat.PropertyTokens +using RobloxFiles.XmlFormat; + +namespace RobloxFiles.Tokens { public class EnumToken : IXmlPropertyToken { - public string Token => "token"; + public string XmlPropertyToken => "token"; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/Faces.cs b/Tokens/Faces.cs similarity index 89% rename from XmlFormat/Tokens/Faces.cs rename to Tokens/Faces.cs index e1bb4e0..9c3b63a 100644 --- a/XmlFormat/Tokens/Faces.cs +++ b/Tokens/Faces.cs @@ -1,12 +1,14 @@ using System.Diagnostics.Contracts; using System.Xml; -using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +using RobloxFiles.DataTypes; +using RobloxFiles.XmlFormat; + +namespace RobloxFiles.Tokens { public class FacesToken : IXmlPropertyToken { - public string Token => "Faces"; + public string XmlPropertyToken => "Faces"; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/Float.cs b/Tokens/Float.cs similarity index 50% rename from XmlFormat/Tokens/Float.cs rename to Tokens/Float.cs index 1899943..b41994c 100644 --- a/XmlFormat/Tokens/Float.cs +++ b/Tokens/Float.cs @@ -1,10 +1,15 @@ using System.Xml; +using RobloxFiles.XmlFormat; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class FloatToken : IXmlPropertyToken + public class FloatToken : IXmlPropertyToken, IAttributeToken { - public string Token => "float"; + public string XmlPropertyToken => "float"; + public AttributeType AttributeType => AttributeType.Float; + + public float ReadAttribute(Attribute attr) => attr.ReadFloat(); + public void WriteAttribute(Attribute attr, float value) => attr.WriteFloat(value); public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/Int.cs b/Tokens/Int.cs similarity index 89% rename from XmlFormat/Tokens/Int.cs rename to Tokens/Int.cs index fcf0637..8f6a760 100644 --- a/XmlFormat/Tokens/Int.cs +++ b/Tokens/Int.cs @@ -1,10 +1,11 @@ using System.Xml; +using RobloxFiles.XmlFormat; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { public class IntToken : IXmlPropertyToken { - public string Token => "int"; + public string XmlPropertyToken => "int"; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/Int64.cs b/Tokens/Int64.cs similarity index 80% rename from XmlFormat/Tokens/Int64.cs rename to Tokens/Int64.cs index 48b6f8b..5ac799b 100644 --- a/XmlFormat/Tokens/Int64.cs +++ b/Tokens/Int64.cs @@ -1,10 +1,11 @@ using System.Xml; +using RobloxFiles.XmlFormat; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { public class Int64Token : IXmlPropertyToken { - public string Token => "int64"; + public string XmlPropertyToken => "int64"; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/NumberRange.cs b/Tokens/NumberRange.cs similarity index 61% rename from XmlFormat/Tokens/NumberRange.cs rename to Tokens/NumberRange.cs index 8198a57..a7951e1 100644 --- a/XmlFormat/Tokens/NumberRange.cs +++ b/Tokens/NumberRange.cs @@ -2,11 +2,12 @@ using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class NumberRangeToken : IXmlPropertyToken + public class NumberRangeToken : IXmlPropertyToken, IAttributeToken { - public string Token => "NumberRange"; + public string XmlPropertyToken => "NumberRange"; + public AttributeType AttributeType => AttributeType.NumberRange; public bool ReadProperty(Property prop, XmlNode token) { @@ -38,5 +39,19 @@ namespace RobloxFiles.XmlFormat.PropertyTokens NumberRange value = prop.CastValue(); node.InnerText = value.ToString() + ' '; } + + public NumberRange ReadAttribute(Attribute attr) + { + float min = attr.ReadFloat(); + float max = attr.ReadFloat(); + + return new NumberRange(min, max); + } + + public void WriteAttribute(Attribute attr, NumberRange value) + { + attr.WriteFloat(value.Min); + attr.WriteFloat(value.Max); + } } } diff --git a/XmlFormat/Tokens/NumberSequence.cs b/Tokens/NumberSequence.cs similarity index 55% rename from XmlFormat/Tokens/NumberSequence.cs rename to Tokens/NumberSequence.cs index af7a1d5..3fd3eba 100644 --- a/XmlFormat/Tokens/NumberSequence.cs +++ b/Tokens/NumberSequence.cs @@ -1,11 +1,12 @@ using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class NumberSequenceToken : IXmlPropertyToken + public class NumberSequenceToken : IXmlPropertyToken, IAttributeToken { - public string Token => "NumberSequence"; + public string XmlPropertyToken => "NumberSequence"; + public AttributeType AttributeType => AttributeType.NumberSequence; public bool ReadProperty(Property prop, XmlNode token) { @@ -47,5 +48,34 @@ namespace RobloxFiles.XmlFormat.PropertyTokens NumberSequence value = prop.CastValue(); node.InnerText = value.ToString() + ' '; } + + public NumberSequence ReadAttribute(Attribute attr) + { + int numKeys = attr.ReadInt(); + var keypoints = new NumberSequenceKeypoint[numKeys]; + + for (int i = 0; i < numKeys; i++) + { + float envelope = attr.ReadInt(), + time = attr.ReadFloat(), + value = attr.ReadFloat(); + + keypoints[i] = new NumberSequenceKeypoint(time, value, envelope); + } + + return new NumberSequence(keypoints); + } + + public void WriteAttribute(Attribute attr, NumberSequence value) + { + attr.WriteInt(value.Keypoints.Length); + + foreach (var keypoint in value.Keypoints) + { + attr.WriteFloat(keypoint.Envelope); + attr.WriteFloat(keypoint.Time); + attr.WriteFloat(keypoint.Value); + } + } } } diff --git a/XmlFormat/Tokens/PhysicalProperties.cs b/Tokens/PhysicalProperties.cs similarity index 88% rename from XmlFormat/Tokens/PhysicalProperties.cs rename to Tokens/PhysicalProperties.cs index 3b6ef1e..2bc4e6e 100644 --- a/XmlFormat/Tokens/PhysicalProperties.cs +++ b/Tokens/PhysicalProperties.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { public class PhysicalPropertiesToken : IXmlPropertyToken { - public string Token => "PhysicalProperties"; + public string XmlPropertyToken => "PhysicalProperties"; - private Func createReader(Func parse, XmlNode token) where T : struct + private static Func CreateReader(Func parse, XmlNode token) where T : struct { return new Func(key => { @@ -20,8 +20,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public bool ReadProperty(Property prop, XmlNode token) { - var readBool = createReader(bool.Parse, token); - var readFloat = createReader(Formatting.ParseFloat, token); + var readBool = CreateReader(bool.Parse, token); + var readFloat = CreateReader(Formatting.ParseFloat, token); try { diff --git a/XmlFormat/Tokens/ProtectedString.cs b/Tokens/ProtectedString.cs similarity index 91% rename from XmlFormat/Tokens/ProtectedString.cs rename to Tokens/ProtectedString.cs index 662c423..5ede316 100644 --- a/XmlFormat/Tokens/ProtectedString.cs +++ b/Tokens/ProtectedString.cs @@ -1,12 +1,14 @@ using System.Text; using System.Xml; -using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +using RobloxFiles.DataTypes; +using RobloxFiles.XmlFormat; + +namespace RobloxFiles.Tokens { public class ProtectedStringToken : IXmlPropertyToken { - public string Token => "ProtectedString"; + public string XmlPropertyToken => "ProtectedString"; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/Ray.cs b/Tokens/Ray.cs similarity index 89% rename from XmlFormat/Tokens/Ray.cs rename to Tokens/Ray.cs index b861b0a..f899c10 100644 --- a/XmlFormat/Tokens/Ray.cs +++ b/Tokens/Ray.cs @@ -2,12 +2,12 @@ using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { public class RayToken : IXmlPropertyToken { - public string Token => "Ray"; - private static string[] Fields = new string[2] { "origin", "direction" }; + public string XmlPropertyToken => "Ray"; + private static readonly string[] Fields = new string[2] { "origin", "direction" }; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/Rect.cs b/Tokens/Rect.cs similarity index 58% rename from XmlFormat/Tokens/Rect.cs rename to Tokens/Rect.cs index 6b9b6f9..2af4868 100644 --- a/XmlFormat/Tokens/Rect.cs +++ b/Tokens/Rect.cs @@ -2,22 +2,23 @@ using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class RectToken : IXmlPropertyToken + public class RectToken : IXmlPropertyToken, IAttributeToken { - public string Token => "Rect2D"; - private static string[] Fields = new string[2] { "min", "max" }; - + public string XmlPropertyToken => "Rect2D"; + public AttributeType AttributeType => AttributeType.Rect; + private static readonly string[] XmlFields = new string[2] { "min", "max" }; + public bool ReadProperty(Property prop, XmlNode token) { try { - Vector2[] read = new Vector2[Fields.Length]; + Vector2[] read = new Vector2[XmlFields.Length]; for (int i = 0; i < read.Length; i++) { - string field = Fields[i]; + string field = XmlFields[i]; var fieldToken = token[field]; read[i] = Vector2Token.ReadVector2(fieldToken); } @@ -49,5 +50,19 @@ namespace RobloxFiles.XmlFormat.PropertyTokens Vector2Token.WriteVector2(doc, max, rect.Max); node.AppendChild(max); } + + public Rect ReadAttribute(Attribute attr) + { + Vector2 min = Vector2Token.ReadVector2(attr); + Vector2 max = Vector2Token.ReadVector2(attr); + + return new Rect(min, max); + } + + public void WriteAttribute(Attribute attr, Rect value) + { + Vector2Token.WriteVector2(attr, value.Min); + Vector2Token.WriteVector2(attr, value.Max); + } } } diff --git a/XmlFormat/Tokens/Ref.cs b/Tokens/Ref.cs similarity index 88% rename from XmlFormat/Tokens/Ref.cs rename to Tokens/Ref.cs index d15a68f..2fa5099 100644 --- a/XmlFormat/Tokens/Ref.cs +++ b/Tokens/Ref.cs @@ -1,10 +1,10 @@ using System.Xml; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { public class RefToken : IXmlPropertyToken { - public string Token => "Ref"; + public string XmlPropertyToken => "Ref"; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/SharedString.cs b/Tokens/SharedString.cs similarity index 81% rename from XmlFormat/Tokens/SharedString.cs rename to Tokens/SharedString.cs index 8c2415e..caa574d 100644 --- a/XmlFormat/Tokens/SharedString.cs +++ b/Tokens/SharedString.cs @@ -1,11 +1,11 @@ using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { public class SharedStringToken : IXmlPropertyToken { - public string Token => "SharedString"; + public string XmlPropertyToken => "SharedString"; public bool ReadProperty(Property prop, XmlNode token) { @@ -18,9 +18,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - var value = prop.Value as SharedString; - - if (value != null) + if (prop.Value is SharedString value) { string key = value.Key; diff --git a/XmlFormat/Tokens/String.cs b/Tokens/String.cs similarity index 64% rename from XmlFormat/Tokens/String.cs rename to Tokens/String.cs index 12ea8f2..82a8c3d 100644 --- a/XmlFormat/Tokens/String.cs +++ b/Tokens/String.cs @@ -1,10 +1,14 @@ using System.Xml; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class StringToken : IXmlPropertyToken + public class StringToken : IXmlPropertyToken, IAttributeToken { - public string Token => "string"; + public string XmlPropertyToken => "string"; + public AttributeType AttributeType => AttributeType.String; + + public string ReadAttribute(Attribute attr) => attr.ReadString(); + public void WriteAttribute(Attribute attr, string value) => attr.WriteString(value); public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/Tokens/UDim.cs b/Tokens/UDim.cs similarity index 65% rename from XmlFormat/Tokens/UDim.cs rename to Tokens/UDim.cs index 2316ad4..e56f35f 100644 --- a/XmlFormat/Tokens/UDim.cs +++ b/Tokens/UDim.cs @@ -2,12 +2,16 @@ using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class UDimToken : IXmlPropertyToken + public class UDimToken : IXmlPropertyToken, IAttributeToken { - public string Token => "UDim"; - + public string XmlPropertyToken => "UDim"; + public AttributeType AttributeType => AttributeType.UDim; + + public UDim ReadAttribute(Attribute attr) => ReadUDim(attr); + public void WriteAttribute(Attribute attr, UDim value) => WriteUDim(attr, value); + public static UDim ReadUDim(XmlNode token, string prefix = "") { try @@ -37,6 +41,23 @@ namespace RobloxFiles.XmlFormat.PropertyTokens node.AppendChild(offset); } + public static UDim ReadUDim(Attribute attr) + { + float scale = attr.ReadFloat(); + int offset = attr.ReadInt(); + + return new UDim(scale, offset); + } + + public static void WriteUDim(Attribute attr, UDim value) + { + float scale = value.Scale; + attr.WriteFloat(scale); + + int offset = value.Offset; + attr.WriteInt(offset); + } + public bool ReadProperty(Property property, XmlNode token) { UDim result = ReadUDim(token); diff --git a/XmlFormat/Tokens/UDim2.cs b/Tokens/UDim2.cs similarity index 56% rename from XmlFormat/Tokens/UDim2.cs rename to Tokens/UDim2.cs index 2fc10cc..7f350fc 100644 --- a/XmlFormat/Tokens/UDim2.cs +++ b/Tokens/UDim2.cs @@ -1,12 +1,26 @@ -using System; -using System.Xml; +using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class UDim2Token : IXmlPropertyToken + public class UDim2Token : IXmlPropertyToken, IAttributeToken { - public string Token => "UDim2"; + public string XmlPropertyToken => "UDim2"; + public AttributeType AttributeType => AttributeType.UDim2; + + public UDim2 ReadAttribute(Attribute attr) + { + UDim x = UDimToken.ReadUDim(attr); + UDim y = UDimToken.ReadUDim(attr); + + return new UDim2(x, y); + } + + public void WriteAttribute(Attribute attr, UDim2 value) + { + UDimToken.WriteUDim(attr, value.X); + UDimToken.WriteUDim(attr, value.Y); + } public bool ReadProperty(Property property, XmlNode token) { diff --git a/XmlFormat/Tokens/Vector2.cs b/Tokens/Vector2.cs similarity index 63% rename from XmlFormat/Tokens/Vector2.cs rename to Tokens/Vector2.cs index 26ed07b..572a679 100644 --- a/XmlFormat/Tokens/Vector2.cs +++ b/Tokens/Vector2.cs @@ -1,12 +1,16 @@ using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class Vector2Token : IXmlPropertyToken + public class Vector2Token : IXmlPropertyToken, IAttributeToken { - public string Token => "Vector2"; - private static string[] Coords = new string[2] { "X", "Y" }; + public string XmlPropertyToken => "Vector2"; + private static readonly string[] XmlCoords = new string[2] { "X", "Y" }; + + public AttributeType AttributeType => AttributeType.Vector2; + public Vector2 ReadAttribute(Attribute attr) => ReadVector2(attr); + public void WriteAttribute(Attribute attr, Vector2 value) => WriteVector2(attr, value); public static Vector2 ReadVector2(XmlNode token) { @@ -14,7 +18,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens for (int i = 0; i < 2; i++) { - string key = Coords[i]; + string key = XmlCoords[i]; try { @@ -42,6 +46,20 @@ namespace RobloxFiles.XmlFormat.PropertyTokens node.AppendChild(y); } + public static Vector2 ReadVector2(Attribute attr) + { + float x = attr.ReadFloat(), + y = attr.ReadFloat(); + + return new Vector2(x, y); + } + + public static void WriteVector2(Attribute attr, Vector2 value) + { + attr.WriteFloat(value.X); + attr.WriteFloat(value.Y); + } + public bool ReadProperty(Property property, XmlNode token) { Vector2 result = ReadVector2(token); diff --git a/XmlFormat/Tokens/Vector3.cs b/Tokens/Vector3.cs similarity index 61% rename from XmlFormat/Tokens/Vector3.cs rename to Tokens/Vector3.cs index fcd4b52..c3f179a 100644 --- a/XmlFormat/Tokens/Vector3.cs +++ b/Tokens/Vector3.cs @@ -1,13 +1,16 @@ -using System; -using System.Xml; +using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { - public class Vector3Token : IXmlPropertyToken + public class Vector3Token : IXmlPropertyToken, IAttributeToken { - public string Token => "Vector3"; - private static string[] Coords = new string[3] { "X", "Y", "Z" }; + public string XmlPropertyToken => "Vector3"; + private static readonly string[] XmlCoords = new string[3] { "X", "Y", "Z" }; + + public AttributeType AttributeType => AttributeType.Vector3; + public Vector3 ReadAttribute(Attribute attr) => ReadVector3(attr); + public void WriteAttribute(Attribute attr, Vector3 value) => WriteVector3(attr, value); public static Vector3 ReadVector3(XmlNode token) { @@ -15,7 +18,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens for (int i = 0; i < 3; i++) { - string key = Coords[i]; + string key = XmlCoords[i]; try { @@ -46,6 +49,22 @@ namespace RobloxFiles.XmlFormat.PropertyTokens node.AppendChild(z); } + public static Vector3 ReadVector3(Attribute attr) + { + float x = attr.ReadFloat(), + y = attr.ReadFloat(), + z = attr.ReadFloat(); + + return new Vector3(x, y, z); + } + + public static void WriteVector3(Attribute attr, Vector3 value) + { + attr.WriteFloat(value.X); + attr.WriteFloat(value.Y); + attr.WriteFloat(value.Z); + } + public bool ReadProperty(Property property, XmlNode token) { Vector3 result = ReadVector3(token); diff --git a/XmlFormat/Tokens/Vector3int16.cs b/Tokens/Vector3int16.cs similarity index 89% rename from XmlFormat/Tokens/Vector3int16.cs rename to Tokens/Vector3int16.cs index 1bf7f8a..8a877fb 100644 --- a/XmlFormat/Tokens/Vector3int16.cs +++ b/Tokens/Vector3int16.cs @@ -1,12 +1,12 @@ using System.Xml; using RobloxFiles.DataTypes; -namespace RobloxFiles.XmlFormat.PropertyTokens +namespace RobloxFiles.Tokens { public class Vector3int16Token : IXmlPropertyToken { - public string Token => "Vector3int16"; - private static string[] Coords = new string[3] { "X", "Y", "Z" }; + public string XmlPropertyToken => "Vector3int16"; + private static readonly string[] Coords = new string[3] { "X", "Y", "Z" }; public bool ReadProperty(Property property, XmlNode token) { diff --git a/Tree/Attributes.cs b/Tree/Attributes.cs index 6e926ff..dd0cda2 100644 --- a/Tree/Attributes.cs +++ b/Tree/Attributes.cs @@ -2,221 +2,221 @@ using System.Collections.Generic; using System.IO; using System.Linq; - -using RobloxFiles.DataTypes; +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, - Bool, - Int, - Float, - Double, - Array, - Dictionary, - UDim, - UDim2, - Ray, - Faces, - Axes, - BrickColor, - Color3, - Vector2, - Vector3, - Vector2int16, - Vector3int16, - CFrame, - Enum, + // 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, - ColorSequence, - ColorSequenceKeypoint, - NumberRange, - Rect, - PhysicalProperties, - Region3 = 31, - Region3int16, + // 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 type = Enum.GetName(typeof(AttributeType), DataType); string value = Value?.ToString() ?? "null"; - return $"[{type}: {value}]"; + return $"[{DataType}: {value}]"; } - internal BinaryReader reader; - // internal BinaryWriter writer; + 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 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() + 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 count = ReadInt(); - var result = new Attribute[count]; + int length = value.Length; + Writer.Write(length); - for (int i = 0; i < count; i++) - result[i] = new Attribute(reader); - - return result; + byte[] utf8 = Encoding.UTF8.GetBytes(value); + Writer.Write(utf8); } - internal object readEnum() + internal void Read() { - 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) + if (Reader == null) return; - DataType = (AttributeType)reader.ReadByte(); + var dataType = Reader.ReadByte(); + DataType = (AttributeType)dataType; - 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(); + var tokenizer = AttributeSupport[DataType]; + Value = tokenizer.ReadAttribute(this); - 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; + Reader = null; } public void Dispose() { - reader.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) { - this.reader = reader; - readData(); + Reader = reader; + Read(); } internal Attribute(MemoryStream stream) { - reader = new BinaryReader(stream); - readData(); + Reader = new BinaryReader(stream); + Read(); + } + + internal Attribute(object value) + { + Type type = value.GetType(); + + if (SupportedTypes.TryGetValue(type, out AttributeType dataType)) + { + DataType = dataType; + Value = value; + } } } @@ -248,15 +248,32 @@ namespace RobloxFiles internal Attributes(MemoryStream stream) { using (BinaryReader reader = new BinaryReader(stream)) - { Initialize(reader); - } + + stream.Dispose(); } internal byte[] Serialize() { - // TODO - return Array.Empty(); + 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(); + } } } } diff --git a/Tree/Instance.cs b/Tree/Instance.cs index 567bcb4..904f440 100644 --- a/Tree/Instance.cs +++ b/Tree/Instance.cs @@ -64,19 +64,22 @@ namespace RobloxFiles public HashSet Tags { get; } = new HashSet(); /// The attributes defined for this Instance. - public Attributes Attributes { get; private set; } + private Attributes AttributesImpl; + + /// The public readonly access point of the attributes on this Instance. + public IReadOnlyDictionary Attributes => AttributesImpl; /// The internal serialized data of this Instance's attributes internal byte[] AttributesSerialize { get { - return Attributes?.Serialize() ?? Array.Empty(); + return AttributesImpl?.Serialize() ?? Array.Empty(); } set { - MemoryStream data = new MemoryStream(value); - Attributes = new Attributes(data); + var data = new MemoryStream(value); + AttributesImpl = new Attributes(data); } } @@ -122,6 +125,55 @@ namespace RobloxFiles } } + /// + /// Attempts to get the value of an attribute whose type is T. + /// Returns false if no attribute was found with that type. + /// + /// The expected type of the attribute. + /// The name of the attribute. + /// The out value to set. + /// True if the attribute could be read and the out value was set, false otherwise. + public bool GetAttribute(string key, out T value) + { + if (AttributesImpl.TryGetValue(key, out Attribute attr)) + { + object result = attr.Value; + + if (result is T) + { + value = (T)result; + return true; + } + } + + value = default; + return false; + } + + /// + /// Attempts to set an attribute to the provided value. The provided key must be no longer than 100 characters. + /// Returns false if the key is too long or the provided type is not supported by Roblox. + /// If an attribute with the provide key already exists, it will be overwritten. + /// + /// + /// The name of the attribute. + /// The value to be assigned to the attribute. + /// True if the attribute was set, false otherwise. + public bool SetAttribute(string key, T value) + { + if (key.Length > 100) + return false; + + if (!Attribute.SupportsType()) + return false; + + var attr = new Attribute(value); + AttributesImpl[key] = attr; + + return true; + } + + /// Returns true if this Instance is an ancestor to the provided Instance. /// The instance whose descendance will be tested against this Instance. public bool IsAncestorOf(Instance descendant) @@ -432,7 +484,7 @@ namespace RobloxFiles ParentLocked = true; Tags?.Clear(); - Attributes?.Clear(); + AttributesImpl?.Clear(); while (Children.Any()) { diff --git a/UnitTest/Files/Binary.rbxl b/UnitTest/Files/Binary.rbxl index 2cdfe9a..2fa7d97 100644 Binary files a/UnitTest/Files/Binary.rbxl and b/UnitTest/Files/Binary.rbxl differ diff --git a/UnitTest/Files/Xml.rbxlx b/UnitTest/Files/Xml.rbxlx index e34f3c4..813f2c7 100644 --- a/UnitTest/Files/Xml.rbxlx +++ b/UnitTest/Files/Xml.rbxlx @@ -1,12 +1,12 @@ null nil - + false Default^0^1 - RBX13AE292CAA514434A884BD688FFBC5B1 + RBXB6729B4B8E0F4D3398108C05AFD9B65E 0 true -500 @@ -61,7 +61,7 @@ true false - + false @@ -157,7 +157,7 @@ - + false @@ -253,7 +253,7 @@ - + 0 @@ -296,7 +296,7 @@ -1 - + false @@ -392,7 +392,7 @@ - + false @@ -488,7 +488,7 @@ - + false @@ -584,7 +584,7 @@ - + false @@ -680,7 +680,7 @@ - + false @@ -777,7 +777,7 @@ - + @@ -819,7 +819,7 @@ - + 0 true @@ -1179,7 +1179,7 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A - + BoolValue @@ -1188,7 +1188,7 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A false - + BrickColorValue @@ -1197,7 +1197,7 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A 194 - + Color3Value @@ -1210,7 +1210,7 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A - + CFrameValue @@ -1232,7 +1232,7 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A - + IntValue @@ -1241,7 +1241,7 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A 1234 - + NumberValue @@ -1250,16 +1250,16 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A 9.0000999999999997669 - + ObjectValue -1 - RBXD040B9F5E34F416EA31C5CB930F42CBB + RBX473F017243BF4D4CBB814CA63559A93A - + RayValue @@ -1279,7 +1279,7 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A - + StringValue @@ -1288,7 +1288,7 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A TestingLol - + true @@ -1370,9 +1370,9 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A 1 - + - RBX6125755126B1409EB37AE2D8CA726D10 + RBX31EFCF342C8F4FEBA9CBF3E1E39463CB 0 @@ -1389,9 +1389,9 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A false - + - RBX6125755126B1409EB37AE2D8CA726D10 + RBX31EFCF342C8F4FEBA9CBF3E1E39463CB 1 @@ -1409,7 +1409,7 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A false - + 0 @@ -1444,7 +1444,7 @@ SIdI3ghI3oAbSM5I/kjxSKWAGkiISO5I/Ei/gBxIzUjWSISA/4C7RqiAHkaogP+A/4D/gP+A 1 0 - + false @@ -1469,7 +1469,7 @@ script.Parent.Color = ColorSequence.new(keyPoints)]]> - + false @@ -1495,7 +1495,7 @@ script.Parent.Size = NumberSequence.new(keyPoints)]]> - + false @@ -1523,7 +1523,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + Vector3Value @@ -1536,7 +1536,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -1590,7 +1590,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true 1 - + UDimTest @@ -1615,7 +1615,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -1658,7 +1658,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> -1 - + false @@ -1735,7 +1735,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 2 - + 2 @@ -1764,7 +1764,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -1841,7 +1841,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 5 - + 2 @@ -1870,7 +1870,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -1947,7 +1947,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + 2 @@ -1976,7 +1976,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -2053,7 +2053,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + 2 @@ -2082,7 +2082,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -2159,7 +2159,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 2 - + 2 @@ -2188,7 +2188,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -2265,7 +2265,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + 2 @@ -2294,7 +2294,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -2371,7 +2371,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 2 - + 2 @@ -2400,7 +2400,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -2477,7 +2477,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + 2 @@ -2506,7 +2506,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -2583,7 +2583,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + 2 @@ -2612,7 +2612,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -2689,7 +2689,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 2 - + 10 @@ -2710,7 +2710,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + INF @@ -2721,7 +2721,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1.3337000000000001076 - + false @@ -2817,8 +2817,23 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> + + + + Attributes + -1 + + + - + 0 @@ -2831,7 +2846,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + NonReplicatedCSGDictionaryService @@ -2839,7 +2854,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + CSGDictionaryService @@ -2847,7 +2862,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -2857,7 +2872,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + TimerService @@ -2865,7 +2880,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + true @@ -2877,7 +2892,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + ReplicatedFirst @@ -2885,7 +2900,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + TweenService @@ -2893,7 +2908,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + PermissionsService @@ -2901,7 +2916,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -2913,7 +2928,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -2928,7 +2943,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + true @@ -2972,7 +2987,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true - + StarterPlayerScripts @@ -2980,7 +2995,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + StarterCharacterScripts @@ -2989,7 +3004,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + StarterPack @@ -2997,7 +3012,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + StarterGui @@ -3008,7 +3023,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + LocalizationService @@ -3016,7 +3031,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + TeleportService @@ -3024,7 +3039,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + CollectionService @@ -3032,7 +3047,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + PhysicsService @@ -3040,7 +3055,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + Geometry @@ -3048,7 +3063,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false false @@ -3058,7 +3073,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + GamePassService @@ -3066,7 +3081,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 1000 @@ -3075,7 +3090,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + CookiesService @@ -3083,7 +3098,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + VRService @@ -3091,7 +3106,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + ContextActionService @@ -3099,7 +3114,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + ScriptService @@ -3107,7 +3122,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + AssetService @@ -3115,7 +3130,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + TouchInputService @@ -3123,7 +3138,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -3132,7 +3147,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + Selection @@ -3140,7 +3155,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -3149,7 +3164,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + ServerStorage @@ -3157,7 +3172,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + ReplicatedStorage @@ -3165,7 +3180,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + LuaWebService @@ -3173,7 +3188,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -3218,7 +3233,68 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 00:00:00 - + + + + true + false + DataStoreService + -1 + + + + + + + FlyweightService + -1 + + + + + + + true + HttpService + -1 + + + + + + + LanguageService + -1 + + + + + + + true + 16 + ProximityPromptService + -1 + + + + + + + ReplicatedScriptService + -1 + + + + + + + Teams + -1 + + + + true @@ -3235,76 +3311,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 10 - - - - true - false - DataStoreService - -1 - - - - - - - FlyweightService - -1 - - - - - - - true - HttpService - -1 - - - - - - - LanguageService - -1 - - - - - - - ReplicatedScriptService - -1 - - - - - - - Teams - -1 - - - - - - - VirtualInputManager - -1 - - - - - - - true - 16 - ProximityPromptService - -1 - - - - + {"lastSaveTime":0,"lastKnownPublishRequest":0,"users":[]} @@ -3313,14 +3320,22 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + + + + VirtualInputManager + -1 + + + + DumpFolder -1 - + @@ -3332,7 +3347,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -3354,7 +3369,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -3376,7 +3391,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -3398,7 +3413,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -3407,7 +3422,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -3429,7 +3444,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + Backpack @@ -3437,7 +3452,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -3448,7 +3463,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + true @@ -3476,7 +3491,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + true @@ -3505,7 +3520,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + null null @@ -3531,7 +3546,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 0 - + BindableEvent @@ -3539,7 +3554,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + BindableFunction @@ -3547,7 +3562,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -3566,7 +3581,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -3579,7 +3594,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -3608,7 +3623,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 1250 @@ -3628,7 +3643,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -3646,7 +3661,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -3665,7 +3680,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0.699999988 @@ -3692,7 +3707,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 3000 - + @@ -3734,7 +3749,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -3772,7 +3787,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -3784,7 +3799,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -3798,7 +3813,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -3812,7 +3827,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -3826,7 +3841,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + Skin @@ -3835,7 +3850,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0.5 @@ -3845,7 +3860,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + Configuration @@ -3853,7 +3868,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 null @@ -3873,7 +3888,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + false null @@ -3892,7 +3907,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + 0 @@ -3913,7 +3928,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + null null @@ -3934,7 +3949,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + 0 0 @@ -3959,7 +3974,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + false null @@ -3977,7 +3992,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + null null @@ -3992,7 +4007,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + null null @@ -4008,7 +4023,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + 0 0 @@ -4046,7 +4061,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + 0 null @@ -4071,7 +4086,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + null null @@ -4094,7 +4109,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + null null @@ -4111,7 +4126,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + false null @@ -4131,7 +4146,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> false - + HumanoidController @@ -4139,7 +4154,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + SkateboardController @@ -4147,7 +4162,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + VehicleController @@ -4155,7 +4170,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + CustomEvent @@ -4164,7 +4179,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + CustomEventReceiver @@ -4173,7 +4188,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -4201,7 +4216,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -4229,7 +4244,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 2 @@ -4256,7 +4271,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -4277,7 +4292,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + true @@ -4289,7 +4304,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 500000 @@ -4307,7 +4322,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true - + @@ -4323,7 +4338,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 0 - + @@ -4343,7 +4358,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 0 - + 0 @@ -4355,7 +4370,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + 0 @@ -4367,7 +4382,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + @@ -4388,7 +4403,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 5 - + Folder @@ -4396,7 +4411,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + ForceField @@ -4405,7 +4420,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true - + false @@ -4460,7 +4475,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + true @@ -4553,7 +4568,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + true @@ -4633,7 +4648,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + false @@ -4720,7 +4735,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + false @@ -4796,7 +4811,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + false @@ -4877,7 +4892,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + false @@ -4936,7 +4951,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + false @@ -5026,7 +5041,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + false null @@ -5079,7 +5094,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 0 - + true @@ -5094,7 +5109,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 0 - + true @@ -5109,7 +5124,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 0 - + true null @@ -5136,7 +5151,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 0 - + null @@ -5159,7 +5174,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true - + 0 null @@ -5202,7 +5217,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> -1 - + 0 null @@ -5242,7 +5257,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> -1 - + 0 null @@ -5284,7 +5299,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> -1 - + 0 null @@ -5327,7 +5342,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> -1 - + 0 null @@ -5367,7 +5382,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> -1 - + 0 null @@ -5406,7 +5421,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> -1 - + @@ -5432,7 +5447,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 0.0625 - + null @@ -5448,7 +5463,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true - + null @@ -5470,7 +5485,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true - + null @@ -5489,7 +5504,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true - + null @@ -5509,7 +5524,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true - + null @@ -5526,7 +5541,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true - + @@ -5543,7 +5558,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true - + @@ -5564,7 +5579,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> true - + true @@ -5599,7 +5614,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 16 - + @@ -5670,7 +5685,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + 0 @@ -5710,7 +5725,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -5750,7 +5765,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -5809,7 +5824,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -5850,7 +5865,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -5891,7 +5906,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -5932,7 +5947,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -5973,7 +5988,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -6012,7 +6027,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -6051,7 +6066,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -6094,7 +6109,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -6133,7 +6148,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + Keyframe @@ -6142,7 +6157,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 0 - + KeyframeMarker @@ -6151,7 +6166,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 2 @@ -6162,7 +6177,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 1 @@ -6179,7 +6194,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 90 @@ -6198,7 +6213,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 90 @@ -6217,17 +6232,17 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + - + [] LocalizationTable -1 en-us - + false @@ -6239,7 +6254,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -6251,7 +6266,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -6262,7 +6277,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + Message @@ -6271,7 +6286,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + Hint @@ -6280,7 +6295,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + true @@ -6291,7 +6306,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -6367,7 +6382,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -6445,7 +6460,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -6524,7 +6539,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -6603,7 +6618,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -6684,7 +6699,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false false @@ -6767,7 +6782,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -6844,7 +6859,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -6940,7 +6955,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -7033,7 +7048,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + true @@ -7126,7 +7141,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -7219,7 +7234,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + false @@ -7296,7 +7311,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 0 - + false @@ -7381,7 +7396,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -7425,7 +7440,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + @@ -7435,7 +7450,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + 0 @@ -7447,7 +7462,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + @@ -7472,7 +7487,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 1 - + true @@ -7484,7 +7499,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> 0.949999988 - + true @@ -7494,7 +7509,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + true @@ -7507,7 +7522,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> - + Interact @@ -7532,7 +7547,7 @@ script.Parent.Transparency = NumberSequence.new(keyPoints)]]> -