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
{
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)

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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()

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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()

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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)
{

View File

@ -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)
{

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;
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);

View File

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

Binary file not shown.

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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<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)
{

View File

@ -1,15 +1,19 @@
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<BrickColor>
{
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)
{

View File

@ -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)
{

View File

@ -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<Color3>
{
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);

View File

@ -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)
{

View File

@ -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<ColorSequence>
{
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<ColorSequence>();
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;
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)
{

View File

@ -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<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)
{

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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<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)
{

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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<NumberRange>
{
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<NumberRange>();
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 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)
{
@ -47,5 +48,34 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
NumberSequence value = prop.CastValue<NumberSequence>();
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 RobloxFiles.DataTypes;
namespace RobloxFiles.XmlFormat.PropertyTokens
namespace RobloxFiles.Tokens
{
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 =>
{
@ -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
{

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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<Rect>
{
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);
}
}
}

View File

@ -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)
{

View File

@ -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;

View File

@ -1,10 +1,14 @@
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)
{

View File

@ -2,11 +2,15 @@
using System.Xml;
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 = "")
{
@ -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);

View File

@ -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<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)
{

View File

@ -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<Vector2>
{
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);

View File

@ -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<Vector3>
{
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);

View File

@ -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)
{

View File

@ -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<AttributeType, Tokenizer> AttributeSupport;
private static readonly IReadOnlyDictionary<Type, AttributeType> 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<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()
{
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<byte>();
if (Count == 0)
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>();
/// <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>
internal byte[] AttributesSerialize
{
get
{
return Attributes?.Serialize() ?? Array.Empty<byte>();
return AttributesImpl?.Serialize() ?? Array.Empty<byte>();
}
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
}
}
/// <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>
/// <param name="descendant">The instance whose descendance will be tested against this Instance.</param>
public bool IsAncestorOf(Instance descendant)
@ -432,7 +484,7 @@ namespace RobloxFiles
ParentLocked = true;
Tags?.Clear();
Attributes?.Clear();
AttributesImpl?.Clear();
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 xml = RobloxFile.Open(@"Files\Xml.rbxlx");
Folder attributes = bin.FindFirstChild<Folder>("Attributes", true);
Console.WriteLine("Files opened! Pausing execution for debugger analysis...");
Debugger.Break();

View File

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

View File

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

View File

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