Add support for Attributes!

This commit is contained in:
Max 2021-02-25 14:09:54 -06:00
parent 81d901cbcd
commit 83dd0af8d2
54 changed files with 1053 additions and 852 deletions

View File

@ -6,9 +6,11 @@ namespace RobloxFiles.DataTypes
{ {
public class CFrame public class CFrame
{ {
private float m11 = 1, m12, m13, m14; private float m14, m24, m34;
private float m21, m22 = 1, m23, m24;
private float m31, m32, m33 = 1, 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; private const float m41 = 0, m42 = 0, m43 = 0, m44 = 1;
@ -160,8 +162,10 @@ namespace RobloxFiles.DataTypes
m31 = comp[9]; m32 = comp[10]; m33 = comp[11]; 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) if (vZ == null)
vZ = vX.Cross(vY); vZ = vX.Cross(vY);
@ -171,40 +175,6 @@ namespace RobloxFiles.DataTypes
m31 = vZ.X; m32 = vZ.Y; m33 = vZ.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);
}
}
public static CFrame operator +(CFrame a, Vector3 b) public static CFrame operator +(CFrame a, Vector3 b)
{ {
float[] ac = a.GetComponents(); float[] ac = a.GetComponents();

View File

@ -40,13 +40,6 @@ namespace RobloxFiles.DataTypes
return true; 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) public static Color3 FromRGB(uint r = 0, uint g = 0, uint b = 0)
{ {
return new Color3(r / 255f, g / 255f, b / 255f); return new Color3(r / 255f, g / 255f, b / 255f);

View File

@ -86,16 +86,5 @@ namespace RobloxFiles.DataTypes
Keypoints = keypoints; 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;
}
} }
} }

View File

@ -19,13 +19,6 @@
Envelope = envelope; Envelope = envelope;
} }
internal ColorSequenceKeypoint(Attribute attr)
{
Envelope = attr.ReadInt();
Time = attr.ReadFloat();
Value = new Color3(attr);
}
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = Time.GetHashCode() int hash = Time.GetHashCode()

View File

@ -25,10 +25,6 @@ namespace RobloxFiles.DataTypes
Max = max; Max = max;
} }
internal NumberRange(Attribute attr) : this(attr.ReadFloat(), attr.ReadFloat())
{
}
public override int GetHashCode() public override int GetHashCode()
{ {
return Min.GetHashCode() ^ Max.GetHashCode(); return Min.GetHashCode() ^ Max.GetHashCode();

View File

@ -52,17 +52,6 @@ namespace RobloxFiles.DataTypes
Keypoints = keypoints; 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() public override int GetHashCode()
{ {
int hash = 0; int hash = 0;

View File

@ -28,12 +28,6 @@
Direction = direction ?? new Vector3(); Direction = direction ?? new Vector3();
} }
internal Ray(Attribute attr)
{
Origin = new Vector3(attr);
Direction = new Vector3(attr);
}
public Vector3 ClosestPoint(Vector3 point) public Vector3 ClosestPoint(Vector3 point)
{ {
Vector3 result = Origin; Vector3 result = Origin;

View File

@ -22,12 +22,6 @@
Max = new Vector2(maxX, maxY); Max = new Vector2(maxX, maxY);
} }
internal Rect(Attribute attr)
{
Min = new Vector2(attr);
Max = new Vector2(attr);
}
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = Min.GetHashCode() int hash = Min.GetHashCode()

View File

@ -17,12 +17,6 @@ namespace RobloxFiles.DataTypes
Max = max; Max = max;
} }
internal Region3(Attribute attr)
{
Min = new Vector3(attr);
Max = new Vector3(attr);
}
public Region3 ExpandToGrid(float resolution) public Region3 ExpandToGrid(float resolution)
{ {
Vector3 emin = new Vector3 Vector3 emin = new Vector3

View File

@ -13,12 +13,6 @@
Offset = offset; Offset = offset;
} }
internal UDim(Attribute attr)
{
Scale = attr.ReadFloat();
Offset = attr.ReadInt();
}
public static UDim operator+(UDim a, UDim b) public static UDim operator+(UDim a, UDim b)
{ {
return new UDim(a.Scale + b.Scale, a.Offset + b.Offset); return new UDim(a.Scale + b.Scale, a.Offset + b.Offset);

View File

@ -20,12 +20,6 @@
Y = y; Y = y;
} }
internal UDim2(Attribute attr)
{
X = new UDim(attr);
Y = new UDim(attr);
}
public UDim2 Lerp(UDim2 other, float alpha) public UDim2 Lerp(UDim2 other, float alpha)
{ {
float scaleX = X.Scale + ((other.X.Scale - X.Scale) * alpha); float scaleX = X.Scale + ((other.X.Scale - X.Scale) * alpha);

View File

@ -29,27 +29,21 @@ namespace RobloxFiles.DataTypes
Y = y; Y = y;
} }
internal Vector2(float[] coords) public Vector2(params float[] coords)
{ {
X = coords.Length > 0 ? coords[0] : 0; X = coords.Length > 0 ? coords[0] : 0;
Y = coords.Length > 1 ? coords[1] : 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 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); Vector2 numVec = new Vector2(num, num);
return upcast(vec, numVec); 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); Vector2 numVec = new Vector2(num, num);
return upcast(numVec, vec); 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)); 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 a, Vector2 b) => add(a, b);
public static Vector2 operator +(Vector2 v, float n) => upcastFloatOp(v, n, 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 +(float n, Vector2 v) => UpcastFloatOp(n, v, add);
public static Vector2 operator -(Vector2 a, Vector2 b) => sub(a, b); 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 -(Vector2 v, float n) => UpcastFloatOp(v, n, sub);
public static Vector2 operator -(float n, Vector2 v) => upcastFloatOp(n, v, 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 a, Vector2 b) => mul(a, b);
public static Vector2 operator *(Vector2 v, float n) => upcastFloatOp(v, n, 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 *(float n, Vector2 v) => UpcastFloatOp(n, v, mul);
public static Vector2 operator /(Vector2 a, Vector2 b) => div(a, b); 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 /(Vector2 v, float n) => UpcastFloatOp(v, n, div);
public static Vector2 operator /(float n, Vector2 v) => upcastFloatOp(n, v, div); public static Vector2 operator /(float n, Vector2 v) => UpcastFloatOp(n, v, div);
public static Vector2 operator -(Vector2 v) public static Vector2 operator -(Vector2 v)
{ {

View File

@ -32,20 +32,13 @@ namespace RobloxFiles.DataTypes
Z = z; Z = z;
} }
public Vector3(float[] coords) public Vector3(params float[] coords)
{ {
X = coords.Length > 0 ? coords[0] : 0; X = coords.Length > 0 ? coords[0] : 0;
Y = coords.Length > 1 ? coords[1] : 0; Y = coords.Length > 1 ? coords[1] : 0;
Z = coords.Length > 2 ? coords[2] : 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) public static Vector3 FromAxis(Axis axis)
{ {
float[] coords = new float[3] { 0f, 0f, 0f }; float[] coords = new float[3] { 0f, 0f, 0f };
@ -68,13 +61,13 @@ namespace RobloxFiles.DataTypes
private delegate Vector3 Operator(Vector3 a, Vector3 b); 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); Vector3 numVec = new Vector3(num, num, num);
return upcast(vec, numVec); 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); Vector3 numVec = new Vector3(num, num, num);
return upcast(numVec, vec); 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)); 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 a, Vector3 b) => add(a, b);
public static Vector3 operator +(Vector3 v, float n) => upcastFloatOp(v, n, 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 +(float n, Vector3 v) => UpcastFloatOp(n, v, add);
public static Vector3 operator -(Vector3 a, Vector3 b) => sub(a, b); 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 -(Vector3 v, float n) => UpcastFloatOp(v, n, sub);
public static Vector3 operator -(float n, Vector3 v) => upcastFloatOp(n, v, 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 a, Vector3 b) => mul(a, b);
public static Vector3 operator *(Vector3 v, float n) => upcastFloatOp(v, n, 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 *(float n, Vector3 v) => UpcastFloatOp(n, v, mul);
public static Vector3 operator /(Vector3 a, Vector3 b) => div(a, b); 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 /(Vector3 v, float n) => UpcastFloatOp(v, n, div);
public static Vector3 operator /(float n, Vector3 v) => upcastFloatOp(n, v, div); public static Vector3 operator /(float n, Vector3 v) => UpcastFloatOp(n, v, div);
public static Vector3 operator -(Vector3 v) public static Vector3 operator -(Vector3 v)
{ {
return new Vector3(-v.X, -v.Y, -v.Z); return new Vector3(-v.X, -v.Y, -v.Z);
} }
public static Vector3 Zero => new Vector3(0, 0, 0); public static readonly Vector3 Zero = new Vector3(0, 0, 0);
public static Vector3 Right => new Vector3(1, 0, 0); public static readonly Vector3 Right = new Vector3(1, 0, 0);
public static Vector3 Up => new Vector3(0, 1, 0); public static readonly Vector3 Up = new Vector3(0, 1, 0);
public static Vector3 Back => new Vector3(0, 0, 1); public static readonly Vector3 Back = new Vector3(0, 0, 1);
public float Dot(Vector3 other) public float Dot(Vector3 other)
{ {

View File

@ -0,0 +1,10 @@
namespace RobloxFiles
{
public interface IAttributeToken<T>
{
AttributeType AttributeType { get; }
T ReadAttribute(Attribute attribute);
void WriteAttribute(Attribute attribute, T value);
}
}

View File

@ -1,10 +1,10 @@
using System.Xml; using System.Xml;
namespace RobloxFiles.XmlFormat namespace RobloxFiles.Tokens
{ {
public interface IXmlPropertyToken public interface IXmlPropertyToken
{ {
string Token { get; } string XmlPropertyToken { get; }
bool ReadProperty(Property prop, XmlNode token); bool ReadProperty(Property prop, XmlNode token);
void WriteProperty(Property prop, XmlDocument doc, XmlNode node); void WriteProperty(Property prop, XmlDocument doc, XmlNode node);

View File

@ -97,6 +97,7 @@
<Compile Include="Interfaces\IBinaryFileChunk.cs" /> <Compile Include="Interfaces\IBinaryFileChunk.cs" />
<Compile Include="Generated\Classes.cs" /> <Compile Include="Generated\Classes.cs" />
<Compile Include="Generated\Enums.cs" /> <Compile Include="Generated\Enums.cs" />
<Compile Include="Interfaces\IAttributeToken.cs" />
<Compile Include="Tree\Attributes.cs" /> <Compile Include="Tree\Attributes.cs" />
<Compile Include="Tree\Property.cs" /> <Compile Include="Tree\Property.cs" />
<Compile Include="Tree\Instance.cs" /> <Compile Include="Tree\Instance.cs" />
@ -130,39 +131,39 @@
<Compile Include="Utility\MaterialInfo.cs" /> <Compile Include="Utility\MaterialInfo.cs" />
<Compile Include="DataTypes\Quaternion.cs" /> <Compile Include="DataTypes\Quaternion.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="XmlFormat\Tokens\SharedString.cs" /> <Compile Include="Tokens\SharedString.cs" />
<Compile Include="XmlFormat\Tokens\ProtectedString.cs" /> <Compile Include="Tokens\ProtectedString.cs" />
<Compile Include="XmlFormat\Tokens\Vector3int16.cs" /> <Compile Include="Tokens\Vector3int16.cs" />
<Compile Include="XmlFormat\IO\XmlFileWriter.cs" /> <Compile Include="XmlFormat\XmlFileWriter.cs" />
<Compile Include="XmlFormat\XmlPropertyTokens.cs" /> <Compile Include="XmlFormat\XmlPropertyTokens.cs" />
<Compile Include="XmlFormat\IO\XmlFileReader.cs" /> <Compile Include="XmlFormat\XmlFileReader.cs" />
<Compile Include="XmlFormat\XmlRobloxFile.cs" /> <Compile Include="XmlFormat\XmlRobloxFile.cs" />
<Compile Include="XmlFormat\Tokens\Axes.cs" /> <Compile Include="Tokens\Axes.cs" />
<Compile Include="XmlFormat\Tokens\BinaryString.cs" /> <Compile Include="Tokens\BinaryString.cs" />
<Compile Include="XmlFormat\Tokens\Boolean.cs" /> <Compile Include="Tokens\Boolean.cs" />
<Compile Include="XmlFormat\Tokens\BrickColor.cs" /> <Compile Include="Tokens\BrickColor.cs" />
<Compile Include="XmlFormat\Tokens\CFrame.cs" /> <Compile Include="Tokens\CFrame.cs" />
<Compile Include="XmlFormat\Tokens\Content.cs" /> <Compile Include="Tokens\Content.cs" />
<Compile Include="XmlFormat\Tokens\Color3.cs" /> <Compile Include="Tokens\Color3.cs" />
<Compile Include="XmlFormat\Tokens\Color3uint8.cs" /> <Compile Include="Tokens\Color3uint8.cs" />
<Compile Include="XmlFormat\Tokens\ColorSequence.cs" /> <Compile Include="Tokens\ColorSequence.cs" />
<Compile Include="XmlFormat\Tokens\Double.cs" /> <Compile Include="Tokens\Double.cs" />
<Compile Include="XmlFormat\Tokens\Enum.cs" /> <Compile Include="Tokens\Enum.cs" />
<Compile Include="XmlFormat\Tokens\Faces.cs" /> <Compile Include="Tokens\Faces.cs" />
<Compile Include="XmlFormat\Tokens\Float.cs" /> <Compile Include="Tokens\Float.cs" />
<Compile Include="XmlFormat\Tokens\Int.cs" /> <Compile Include="Tokens\Int.cs" />
<Compile Include="XmlFormat\Tokens\Int64.cs" /> <Compile Include="Tokens\Int64.cs" />
<Compile Include="XmlFormat\Tokens\NumberRange.cs" /> <Compile Include="Tokens\NumberRange.cs" />
<Compile Include="XmlFormat\Tokens\NumberSequence.cs" /> <Compile Include="Tokens\NumberSequence.cs" />
<Compile Include="XmlFormat\Tokens\PhysicalProperties.cs" /> <Compile Include="Tokens\PhysicalProperties.cs" />
<Compile Include="XmlFormat\Tokens\Ray.cs" /> <Compile Include="Tokens\Ray.cs" />
<Compile Include="XmlFormat\Tokens\Rect.cs" /> <Compile Include="Tokens\Rect.cs" />
<Compile Include="XmlFormat\Tokens\Ref.cs" /> <Compile Include="Tokens\Ref.cs" />
<Compile Include="XmlFormat\Tokens\String.cs" /> <Compile Include="Tokens\String.cs" />
<Compile Include="XmlFormat\Tokens\UDim.cs" /> <Compile Include="Tokens\UDim.cs" />
<Compile Include="XmlFormat\Tokens\UDim2.cs" /> <Compile Include="Tokens\UDim2.cs" />
<Compile Include="XmlFormat\Tokens\Vector2.cs" /> <Compile Include="Tokens\Vector2.cs" />
<Compile Include="XmlFormat\Tokens\Vector3.cs" /> <Compile Include="Tokens\Vector3.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2"> <BootstrapperPackage Include=".NETFramework,Version=v4.5.2">

Binary file not shown.

View File

@ -1,11 +1,13 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens using RobloxFiles.DataTypes;
using RobloxFiles.XmlFormat;
namespace RobloxFiles.Tokens
{ {
public class AxesToken : IXmlPropertyToken public class AxesToken : IXmlPropertyToken
{ {
public string Token => "Axes"; public string XmlPropertyToken => "Axes";
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -1,11 +1,11 @@
using System; using System;
using System.Xml; using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class BinaryStringToken : IXmlPropertyToken public class BinaryStringToken : IXmlPropertyToken
{ {
public string Token => "BinaryString"; public string XmlPropertyToken => "BinaryString";
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -1,10 +1,15 @@
using System.Xml; using System.Xml;
using RobloxFiles.XmlFormat;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class BoolToken : IXmlPropertyToken public class BoolToken : IXmlPropertyToken, IAttributeToken<bool>
{ {
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) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -1,15 +1,19 @@
using System; using System.Xml;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
using RobloxFiles.XmlFormat;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class BrickColorToken : IXmlPropertyToken public class BrickColorToken : IXmlPropertyToken, IAttributeToken<BrickColor>
{ {
public string Token => "BrickColor"; // This is a lie: The token is actually int, but that would cause a name collision.
// ^ 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 // Since BrickColors are written as ints, the IntToken class will try to redirect
// to this handler if it believes that its representing a BrickColor. // 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) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -1,12 +1,12 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class CFrameToken : IXmlPropertyToken public class CFrameToken : IXmlPropertyToken
{ {
public string Token => "CoordinateFrame; CFrame"; public string XmlPropertyToken => "CoordinateFrame; CFrame";
private static string[] Coords = new string[12] { "X", "Y", "Z", "R00", "R01", "R02", "R10", "R11", "R12", "R20", "R21", "R22"}; 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) public static CFrame ReadCFrame(XmlNode token)
{ {

View File

@ -1,22 +1,42 @@
using System; using System.Xml;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
using RobloxFiles.XmlFormat;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class Color3Token : IXmlPropertyToken public class Color3Token : IXmlPropertyToken, IAttributeToken<Color3>
{ {
public string Token => "Color3"; public string XmlPropertyToken => "Color3";
private readonly string[] Fields = new string[3] { "R", "G", "B" }; 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) public bool ReadProperty(Property prop, XmlNode token)
{ {
bool success = true; bool success = true;
float[] fields = new float[Fields.Length]; float[] fields = new float[XmlFields.Length];
for (int i = 0; i < fields.Length; i++) for (int i = 0; i < fields.Length; i++)
{ {
string key = Fields[i]; string key = XmlFields[i];
try try
{ {
@ -64,7 +84,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
{ {
string field = Fields[i]; string field = XmlFields[i];
float value = rgb[i]; float value = rgb[i];
XmlElement channel = doc.CreateElement(field); XmlElement channel = doc.CreateElement(field);

View File

@ -1,13 +1,13 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
using RobloxFiles.XmlFormat;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class Color3uint8Token : IXmlPropertyToken public class Color3uint8Token : IXmlPropertyToken
{ {
public string Token => "Color3uint8"; public string XmlPropertyToken => "Color3uint8";
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -1,11 +1,12 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class ColorSequenceToken : IXmlPropertyToken public class ColorSequenceToken : IXmlPropertyToken, IAttributeToken<ColorSequence>
{ {
public string Token => "ColorSequence"; public string XmlPropertyToken => "ColorSequence";
public AttributeType AttributeType => AttributeType.ColorSequence;
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
@ -19,7 +20,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
{ {
try try
{ {
ColorSequenceKeypoint[] keypoints = new ColorSequenceKeypoint[length / 5]; var keypoints = new ColorSequenceKeypoint[length / 5];
for (int i = 0; i < length; i += 5) for (int i = 0; i < length; i += 5)
{ {
@ -50,5 +51,35 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
ColorSequence value = prop.CastValue<ColorSequence>(); ColorSequence value = prop.CastValue<ColorSequence>();
node.InnerText = value.ToString() + ' '; 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);
}
}
} }
} }

View File

@ -3,11 +3,11 @@ using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class ContentToken : IXmlPropertyToken public class ContentToken : IXmlPropertyToken
{ {
public string Token => "Content"; public string XmlPropertyToken => "Content";
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -1,10 +1,15 @@
using System.Xml; using System.Xml;
using RobloxFiles.XmlFormat;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class DoubleToken : IXmlPropertyToken public class DoubleToken : IXmlPropertyToken, IAttributeToken<double>
{ {
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) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -3,11 +3,13 @@ using System.Diagnostics.Contracts;
using System.Reflection; using System.Reflection;
using System.Xml; using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens using RobloxFiles.XmlFormat;
namespace RobloxFiles.Tokens
{ {
public class EnumToken : IXmlPropertyToken public class EnumToken : IXmlPropertyToken
{ {
public string Token => "token"; public string XmlPropertyToken => "token";
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -1,12 +1,14 @@
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens using RobloxFiles.DataTypes;
using RobloxFiles.XmlFormat;
namespace RobloxFiles.Tokens
{ {
public class FacesToken : IXmlPropertyToken public class FacesToken : IXmlPropertyToken
{ {
public string Token => "Faces"; public string XmlPropertyToken => "Faces";
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -1,10 +1,15 @@
using System.Xml; using System.Xml;
using RobloxFiles.XmlFormat;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class FloatToken : IXmlPropertyToken public class FloatToken : IXmlPropertyToken, IAttributeToken<float>
{ {
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) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -1,10 +1,11 @@
using System.Xml; using System.Xml;
using RobloxFiles.XmlFormat;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class IntToken : IXmlPropertyToken public class IntToken : IXmlPropertyToken
{ {
public string Token => "int"; public string XmlPropertyToken => "int";
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -1,10 +1,11 @@
using System.Xml; using System.Xml;
using RobloxFiles.XmlFormat;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class Int64Token : IXmlPropertyToken public class Int64Token : IXmlPropertyToken
{ {
public string Token => "int64"; public string XmlPropertyToken => "int64";
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -2,11 +2,12 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class NumberRangeToken : IXmlPropertyToken public class NumberRangeToken : IXmlPropertyToken, IAttributeToken<NumberRange>
{ {
public string Token => "NumberRange"; public string XmlPropertyToken => "NumberRange";
public AttributeType AttributeType => AttributeType.NumberRange;
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
@ -38,5 +39,19 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
NumberRange value = prop.CastValue<NumberRange>(); NumberRange value = prop.CastValue<NumberRange>();
node.InnerText = value.ToString() + ' '; 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);
}
} }
} }

View File

@ -1,11 +1,12 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class NumberSequenceToken : IXmlPropertyToken public class NumberSequenceToken : IXmlPropertyToken, IAttributeToken<NumberSequence>
{ {
public string Token => "NumberSequence"; public string XmlPropertyToken => "NumberSequence";
public AttributeType AttributeType => AttributeType.NumberSequence;
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
@ -47,5 +48,34 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
NumberSequence value = prop.CastValue<NumberSequence>(); NumberSequence value = prop.CastValue<NumberSequence>();
node.InnerText = value.ToString() + ' '; 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);
}
}
} }
} }

View File

@ -3,13 +3,13 @@ using System.Collections.Generic;
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class PhysicalPropertiesToken : IXmlPropertyToken public class PhysicalPropertiesToken : IXmlPropertyToken
{ {
public string Token => "PhysicalProperties"; public string XmlPropertyToken => "PhysicalProperties";
private Func<string, T> createReader<T>(Func<string, T> parse, XmlNode token) where T : struct private static Func<string, T> CreateReader<T>(Func<string, T> parse, XmlNode token) where T : struct
{ {
return new Func<string, T>(key => return new Func<string, T>(key =>
{ {
@ -20,8 +20,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
var readBool = createReader(bool.Parse, token); var readBool = CreateReader(bool.Parse, token);
var readFloat = createReader(Formatting.ParseFloat, token); var readFloat = CreateReader(Formatting.ParseFloat, token);
try try
{ {

View File

@ -1,12 +1,14 @@
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens using RobloxFiles.DataTypes;
using RobloxFiles.XmlFormat;
namespace RobloxFiles.Tokens
{ {
public class ProtectedStringToken : IXmlPropertyToken public class ProtectedStringToken : IXmlPropertyToken
{ {
public string Token => "ProtectedString"; public string XmlPropertyToken => "ProtectedString";
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -2,12 +2,12 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class RayToken : IXmlPropertyToken public class RayToken : IXmlPropertyToken
{ {
public string Token => "Ray"; public string XmlPropertyToken => "Ray";
private static string[] Fields = new string[2] { "origin", "direction" }; private static readonly string[] Fields = new string[2] { "origin", "direction" };
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -2,22 +2,23 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class RectToken : IXmlPropertyToken public class RectToken : IXmlPropertyToken, IAttributeToken<Rect>
{ {
public string Token => "Rect2D"; public string XmlPropertyToken => "Rect2D";
private static string[] Fields = new string[2] { "min", "max" }; public AttributeType AttributeType => AttributeType.Rect;
private static readonly string[] XmlFields = new string[2] { "min", "max" };
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
try try
{ {
Vector2[] read = new Vector2[Fields.Length]; Vector2[] read = new Vector2[XmlFields.Length];
for (int i = 0; i < read.Length; i++) for (int i = 0; i < read.Length; i++)
{ {
string field = Fields[i]; string field = XmlFields[i];
var fieldToken = token[field]; var fieldToken = token[field];
read[i] = Vector2Token.ReadVector2(fieldToken); read[i] = Vector2Token.ReadVector2(fieldToken);
} }
@ -49,5 +50,19 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
Vector2Token.WriteVector2(doc, max, rect.Max); Vector2Token.WriteVector2(doc, max, rect.Max);
node.AppendChild(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);
}
} }
} }

View File

@ -1,10 +1,10 @@
using System.Xml; using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class RefToken : IXmlPropertyToken public class RefToken : IXmlPropertyToken
{ {
public string Token => "Ref"; public string XmlPropertyToken => "Ref";
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -1,11 +1,11 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class SharedStringToken : IXmlPropertyToken public class SharedStringToken : IXmlPropertyToken
{ {
public string Token => "SharedString"; public string XmlPropertyToken => "SharedString";
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
@ -18,9 +18,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{ {
var value = prop.Value as SharedString; if (prop.Value is SharedString value)
if (value != null)
{ {
string key = value.Key; string key = value.Key;

View File

@ -1,10 +1,14 @@
using System.Xml; using System.Xml;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class StringToken : IXmlPropertyToken public class StringToken : IXmlPropertyToken, IAttributeToken<string>
{ {
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) public bool ReadProperty(Property prop, XmlNode token)
{ {

View File

@ -2,11 +2,15 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class UDimToken : IXmlPropertyToken public class UDimToken : IXmlPropertyToken, IAttributeToken<UDim>
{ {
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 = "") public static UDim ReadUDim(XmlNode token, string prefix = "")
{ {
@ -37,6 +41,23 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
node.AppendChild(offset); 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) public bool ReadProperty(Property property, XmlNode token)
{ {
UDim result = ReadUDim(token); UDim result = ReadUDim(token);

View File

@ -1,12 +1,26 @@
using System; using System.Xml;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class UDim2Token : IXmlPropertyToken public class UDim2Token : IXmlPropertyToken, IAttributeToken<UDim2>
{ {
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) public bool ReadProperty(Property property, XmlNode token)
{ {

View File

@ -1,12 +1,16 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class Vector2Token : IXmlPropertyToken public class Vector2Token : IXmlPropertyToken, IAttributeToken<Vector2>
{ {
public string Token => "Vector2"; public string XmlPropertyToken => "Vector2";
private static string[] Coords = new string[2] { "X", "Y" }; 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) public static Vector2 ReadVector2(XmlNode token)
{ {
@ -14,7 +18,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
string key = Coords[i]; string key = XmlCoords[i];
try try
{ {
@ -42,6 +46,20 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
node.AppendChild(y); 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) public bool ReadProperty(Property property, XmlNode token)
{ {
Vector2 result = ReadVector2(token); Vector2 result = ReadVector2(token);

View File

@ -1,13 +1,16 @@
using System; using System.Xml;
using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class Vector3Token : IXmlPropertyToken public class Vector3Token : IXmlPropertyToken, IAttributeToken<Vector3>
{ {
public string Token => "Vector3"; public string XmlPropertyToken => "Vector3";
private static string[] Coords = new string[3] { "X", "Y", "Z" }; 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) public static Vector3 ReadVector3(XmlNode token)
{ {
@ -15,7 +18,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
{ {
string key = Coords[i]; string key = XmlCoords[i];
try try
{ {
@ -46,6 +49,22 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
node.AppendChild(z); 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) public bool ReadProperty(Property property, XmlNode token)
{ {
Vector3 result = ReadVector3(token); Vector3 result = ReadVector3(token);

View File

@ -1,12 +1,12 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens namespace RobloxFiles.Tokens
{ {
public class Vector3int16Token : IXmlPropertyToken public class Vector3int16Token : IXmlPropertyToken
{ {
public string Token => "Vector3int16"; public string XmlPropertyToken => "Vector3int16";
private static string[] Coords = new string[3] { "X", "Y", "Z" }; private static readonly string[] Coords = new string[3] { "X", "Y", "Z" };
public bool ReadProperty(Property property, XmlNode token) public bool ReadProperty(Property property, XmlNode token)
{ {

View File

@ -2,221 +2,221 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using RobloxFiles.DataTypes; using System.Text;
namespace RobloxFiles namespace RobloxFiles
{ {
// This enum defines existing attributes
// Commented out values are known types
// which are unsupported at this time.
public enum AttributeType public enum AttributeType
{ {
Null = 1, // Null = 1,
String, String = 2,
Bool, Bool = 3,
Int, // Int = 4,
Float, Float = 5,
Double, Double = 6,
Array, // Array = 7,
Dictionary, // Dictionary = 8,
UDim, UDim = 9,
UDim2, UDim2 = 10,
Ray, // Ray = 11,
Faces, // Faces = 12,
Axes, // Axes = 13
BrickColor, BrickColor = 14,
Color3, Color3 = 15,
Vector2, Vector2 = 16,
Vector3, Vector3 = 17,
Vector2int16, // Vector2int16 = 18,
Vector3int16, // Vector3int16 = 19,
CFrame, // CFrame = 20,
Enum, // Enum = 21,
NumberSequence = 23, NumberSequence = 23,
NumberSequenceKeypoint, // NumberSequenceKeypoint = 24,
ColorSequence, ColorSequence = 25,
ColorSequenceKeypoint, // ColorSequenceKeypoint = 26,
NumberRange, NumberRange = 27,
Rect, Rect = 28,
PhysicalProperties, // PhysicalProperties = 29
Region3 = 31, // Region3 = 31,
Region3int16, // Region3int16 = 32
} }
public class Attribute : IDisposable public class Attribute : IDisposable
{ {
private static readonly IReadOnlyDictionary<AttributeType, Tokenizer> AttributeSupport;
private static readonly IReadOnlyDictionary<Type, AttributeType> SupportedTypes;
public AttributeType DataType { get; private set; } public AttributeType DataType { get; private set; }
public object Value { get; private set; } public object Value { get; private set; }
private struct Tokenizer
{
public readonly Type Support;
public readonly object Token;
public readonly MethodInfo Reader;
public readonly MethodInfo Writer;
public Tokenizer(Type tokenType, Type support)
{
Support = support;
Token = Activator.CreateInstance(tokenType);
Reader = support.GetMethod("ReadAttribute");
Writer = support.GetMethod("WriteAttribute");
}
public object ReadAttribute(Attribute attr)
{
var args = new object[1] { attr };
return Reader.Invoke(Token, args);
}
public void WriteAttribute(Attribute attr, object value)
{
var args = new object[2] { attr, value };
Writer.Invoke(Token, args);
}
}
static Attribute()
{
var attributeSupport = new Dictionary<AttributeType, Tokenizer>();
var supportedTypes = new Dictionary<Type, AttributeType>();
var assembly = Assembly.GetExecutingAssembly();
var handlerTypes =
from type in assembly.GetTypes()
let typeInfo = type.GetTypeInfo()
let support = typeInfo.GetInterface("IAttributeToken`1")
where (support != null)
select new Tokenizer(typeInfo, support);
foreach (var tokenizer in handlerTypes)
{
var token = tokenizer.Token;
var support = tokenizer.Support;
var genericType = support.GenericTypeArguments.FirstOrDefault();
var getAttributeType = support.GetMethod("get_AttributeType");
var attributeType = (AttributeType)getAttributeType.Invoke(token, null);
attributeSupport.Add(attributeType, tokenizer);
supportedTypes.Add(genericType, attributeType);
}
AttributeSupport = attributeSupport;
SupportedTypes = supportedTypes;
}
/// <summary>
/// Returns true if the provided type is supported by attributes.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool SupportsType(Type type)
{
return SupportedTypes.ContainsKey(type);
}
/// <summary>
/// Returns true if the provided type is supported by attributes.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static bool SupportsType<T>()
{
Type type = typeof(T);
return SupportsType(type);
}
public override string ToString() public override string ToString()
{ {
string type = Enum.GetName(typeof(AttributeType), DataType);
string value = Value?.ToString() ?? "null"; string value = Value?.ToString() ?? "null";
return $"[{type}: {value}]"; return $"[{DataType}: {value}]";
} }
internal BinaryReader reader; internal BinaryReader Reader;
// internal BinaryWriter writer; internal BinaryWriter Writer;
internal int ReadInt() => reader.ReadInt32(); internal int ReadInt() => Reader.ReadInt32();
internal byte ReadByte() => reader.ReadByte(); internal byte ReadByte() => Reader.ReadByte();
internal bool ReadBool() => reader.ReadBoolean(); internal bool ReadBool() => Reader.ReadBoolean();
internal short ReadShort() => reader.ReadInt16(); internal short ReadShort() => Reader.ReadInt16();
internal float ReadFloat() => reader.ReadSingle(); internal float ReadFloat() => Reader.ReadSingle();
internal double ReadDouble() => reader.ReadDouble(); internal double ReadDouble() => Reader.ReadDouble();
internal string ReadString() => reader.ReadString(true); 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(); int length = value.Length;
var result = new Attribute[count]; Writer.Write(length);
for (int i = 0; i < count; i++) byte[] utf8 = Encoding.UTF8.GetBytes(value);
result[i] = new Attribute(reader); Writer.Write(utf8);
return result;
} }
internal object readEnum() internal void Read()
{ {
string name = ReadString(); if (Reader == null)
int value = ReadInt();
try
{
Type enumType = Type.GetType($"RobloxFiles.Enums.{name}");
return Enum.ToObject(enumType, value);
}
catch
{
if (RobloxFile.LogErrors)
Console.Error.WriteLine($"RobloxFile - Got unknown Enum {name} in Attribute.");
return null;
}
}
private void readData()
{
if (reader == null)
return; return;
DataType = (AttributeType)reader.ReadByte(); var dataType = Reader.ReadByte();
DataType = (AttributeType)dataType;
switch (DataType) var tokenizer = AttributeSupport[DataType];
{ Value = tokenizer.ReadAttribute(this);
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) Reader = null;
Value = new PhysicalProperties(this);
break;
case AttributeType.Region3:
Value = new Region3(this);
break;
case AttributeType.Region3int16:
Value = new Region3int16(this);
break;
default: throw new InvalidDataException($"Cannot handle AttributeType {DataType}!");
}
reader = null;
} }
public void Dispose() 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) internal Attribute(BinaryReader reader)
{ {
this.reader = reader; Reader = reader;
readData(); Read();
} }
internal Attribute(MemoryStream stream) internal Attribute(MemoryStream stream)
{ {
reader = new BinaryReader(stream); Reader = new BinaryReader(stream);
readData(); 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) internal Attributes(MemoryStream stream)
{ {
using (BinaryReader reader = new BinaryReader(stream)) using (BinaryReader reader = new BinaryReader(stream))
{
Initialize(reader); Initialize(reader);
}
stream.Dispose();
} }
internal byte[] Serialize() internal byte[] Serialize()
{ {
// TODO if (Count == 0)
return Array.Empty<byte>(); return Array.Empty<byte>();
using (var output = new MemoryStream())
using (var writer = new BinaryWriter(output))
{
writer.Write(Count);
foreach (string key in Keys)
{
var attribute = this[key];
attribute.Writer = writer;
attribute.WriteString(key);
attribute.Write(writer);
}
return output.ToArray();
}
} }
} }
} }

View File

@ -64,19 +64,22 @@ namespace RobloxFiles
public HashSet<string> Tags { get; } = new HashSet<string>(); public HashSet<string> Tags { get; } = new HashSet<string>();
/// <summary>The attributes defined for this Instance.</summary> /// <summary>The attributes defined for this Instance.</summary>
public Attributes Attributes { get; private set; } private Attributes AttributesImpl;
/// <summary>The public readonly access point of the attributes on this Instance.</summary>
public IReadOnlyDictionary<string, Attribute> Attributes => AttributesImpl;
/// <summary>The internal serialized data of this Instance's attributes</summary> /// <summary>The internal serialized data of this Instance's attributes</summary>
internal byte[] AttributesSerialize internal byte[] AttributesSerialize
{ {
get get
{ {
return Attributes?.Serialize() ?? Array.Empty<byte>(); return AttributesImpl?.Serialize() ?? Array.Empty<byte>();
} }
set set
{ {
MemoryStream data = new MemoryStream(value); var data = new MemoryStream(value);
Attributes = new Attributes(data); AttributesImpl = new Attributes(data);
} }
} }
@ -122,6 +125,55 @@ namespace RobloxFiles
} }
} }
/// <summary>
/// Attempts to get the value of an attribute whose type is T.
/// Returns false if no attribute was found with that type.
/// </summary>
/// <typeparam name="T">The expected type of the attribute.</typeparam>
/// <param name="key">The name of the attribute.</param>
/// <param name="value">The out value to set.</param>
/// <returns>True if the attribute could be read and the out value was set, false otherwise.</returns>
public bool GetAttribute<T>(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;
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The name of the attribute.</param>
/// <param name="value">The value to be assigned to the attribute.</param>
/// <returns>True if the attribute was set, false otherwise.</returns>
public bool SetAttribute<T>(string key, T value)
{
if (key.Length > 100)
return false;
if (!Attribute.SupportsType<T>())
return false;
var attr = new Attribute(value);
AttributesImpl[key] = attr;
return true;
}
/// <summary>Returns true if this Instance is an ancestor to the provided Instance.</summary> /// <summary>Returns true if this Instance is an ancestor to the provided Instance.</summary>
/// <param name="descendant">The instance whose descendance will be tested against this Instance.</param> /// <param name="descendant">The instance whose descendance will be tested against this Instance.</param>
public bool IsAncestorOf(Instance descendant) public bool IsAncestorOf(Instance descendant)
@ -432,7 +484,7 @@ namespace RobloxFiles
ParentLocked = true; ParentLocked = true;
Tags?.Clear(); Tags?.Clear();
Attributes?.Clear(); AttributesImpl?.Clear();
while (Children.Any()) while (Children.Any())
{ {

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -120,6 +120,8 @@ namespace RobloxFiles.UnitTest
RobloxFile bin = RobloxFile.Open(@"Files\Binary.rbxl"); RobloxFile bin = RobloxFile.Open(@"Files\Binary.rbxl");
RobloxFile xml = RobloxFile.Open(@"Files\Xml.rbxlx"); RobloxFile xml = RobloxFile.Open(@"Files\Xml.rbxlx");
Folder attributes = bin.FindFirstChild<Folder>("Attributes", true);
Console.WriteLine("Files opened! Pausing execution for debugger analysis..."); Console.WriteLine("Files opened! Pausing execution for debugger analysis...");
Debugger.Break(); Debugger.Break();

View File

@ -2,6 +2,7 @@
using System.Xml; using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
using RobloxFiles.Tokens;
namespace RobloxFiles.XmlFormat namespace RobloxFiles.XmlFormat
{ {

View File

@ -7,7 +7,7 @@ using System.Xml;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
using RobloxFiles.Utility; using RobloxFiles.Utility;
using RobloxFiles.XmlFormat.PropertyTokens; using RobloxFiles.Tokens;
namespace RobloxFiles.XmlFormat namespace RobloxFiles.XmlFormat
{ {

View File

@ -5,6 +5,8 @@ using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
using RobloxFiles.Tokens;
namespace RobloxFiles.XmlFormat namespace RobloxFiles.XmlFormat
{ {
public static class XmlPropertyTokens public static class XmlPropertyTokens
@ -27,7 +29,7 @@ namespace RobloxFiles.XmlFormat
foreach (IXmlPropertyToken propToken in propTokens) foreach (IXmlPropertyToken propToken in propTokens)
{ {
var tokens = propToken.Token.Split(';') var tokens = propToken.XmlPropertyToken.Split(';')
.Select(token => token.Trim()) .Select(token => token.Trim())
.ToList(); .ToList();