Added read support for Instance Attributes.

This isn't 100% finished yet. I intend to add some better API for reading specific attributes, as well as write support (of course!)
This commit is contained in:
CloneTrooper1019 2019-10-31 21:40:31 -05:00
parent fd8598c1b5
commit e14b092aa7
30 changed files with 580 additions and 95 deletions

View File

@ -87,6 +87,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
// Check if this is going to be casted as a BinaryString.
// BinaryStrings should use a type of byte[] instead.
if (Name == "AttributesSerialize")
return buffer;
Property prop = props[i];
Instance instance = prop.Instance;

View File

@ -26,6 +26,9 @@ namespace RobloxFiles.DataTypes
private const string DefaultName = "Medium stone grey";
private const int DefaultNumber = 194;
public static implicit operator int(BrickColor color) => color.Number;
public static implicit operator BrickColor(int number) => FromNumber(number);
internal BrickColor(int number, uint rgb, string name)
{
uint r = (rgb / 65536) % 256;
@ -43,6 +46,7 @@ namespace RobloxFiles.DataTypes
ByNumber = BrickColors.ColorMap.ToDictionary(brickColor => brickColor.Number);
ByPalette = BrickColors.PaletteMap.Select(number => ByNumber[number]).ToList();
}
public static BrickColor FromName(string name)
{

View File

@ -1,4 +1,5 @@
using System;
using RobloxFiles.Enums;
namespace RobloxFiles.DataTypes
{
@ -121,6 +122,50 @@ namespace RobloxFiles.DataTypes
m31 = comp[9]; m32 = comp[10]; m33 = comp[11];
}
private void initFromMatrix(Vector3 pos, Vector3 vX, Vector3 vY, Vector3 vZ = null)
{
if (vZ == null)
vZ = vX.Cross(vY);
m14 = pos.X; m24 = pos.Y; m34 = pos.Z;
m11 = vX.X; m12 = vX.Y; m13 = vX.Z;
m21 = vY.X; m22 = vY.Y; m23 = vY.Z;
m31 = vZ.X; m32 = vZ.Y; m33 = vZ.Z;
}
public CFrame(Vector3 pos, Vector3 vX, Vector3 vY, Vector3 vZ = null)
{
initFromMatrix(pos, vX, vY, vZ);
}
internal CFrame(Attribute attr)
{
Vector3 pos = new Vector3(attr);
byte rawOrientId = attr.readByte();
if (rawOrientId > 0)
{
// Make sure this value is in a safe range.
int orientId = (rawOrientId - 1) % 36;
NormalId xColumn = (NormalId)(orientId / 6);
Vector3 vX = Vector3.FromNormalId(xColumn);
NormalId yColumn = (NormalId)(orientId % 6);
Vector3 vY = Vector3.FromNormalId(yColumn);
initFromMatrix(pos, vX, vY);
}
else
{
Vector3 vX = new Vector3(attr),
vY = new Vector3(attr),
vZ = new Vector3(attr);
initFromMatrix(pos, vX, vY, vZ);
}
}
public static CFrame operator +(CFrame a, Vector3 b)
{
float[] ac = a.GetComponents();

View File

@ -5,6 +5,7 @@ namespace RobloxFiles.DataTypes
public class Color3
{
public readonly float R, G, B;
public override string ToString() => $"{R}, {G}, {B}";
public Color3(float r = 0, float g = 0, float b = 0)
{
@ -13,9 +14,11 @@ namespace RobloxFiles.DataTypes
B = b;
}
public override string ToString()
internal Color3(Attribute attr)
{
return string.Join(", ", R, G, B);
R = attr.readFloat();
G = attr.readFloat();
B = attr.readFloat();
}
public static Color3 FromRGB(uint r = 0, uint g = 0, uint b = 0)

View File

@ -7,6 +7,7 @@
public class Color3uint8
{
public readonly byte R, G, B;
public override string ToString() => $"{R}, {G}, {B}";
public Color3uint8(byte r = 0, byte g = 0, byte b = 0)
{
@ -15,11 +16,6 @@
B = b;
}
public override string ToString()
{
return string.Join(", ", R, G, B);
}
public static implicit operator Color3(Color3uint8 color)
{
float r = color.R / 255f;

View File

@ -6,6 +6,11 @@ namespace RobloxFiles.DataTypes
{
public readonly ColorSequenceKeypoint[] Keypoints;
public override string ToString()
{
return string.Join<ColorSequenceKeypoint>(" ", Keypoints);
}
public ColorSequence(Color3 c) : this(c, c)
{
}
@ -42,10 +47,16 @@ namespace RobloxFiles.DataTypes
Keypoints = keypoints;
}
public override string ToString()
public ColorSequence(Attribute attr)
{
return string.Join<ColorSequenceKeypoint>(" ", Keypoints);
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

@ -6,6 +6,11 @@
public readonly Color3 Value;
public readonly int Envelope;
public override string ToString()
{
return $"{Time} {Value.R} {Value.G} {Value.B} {Envelope}";
}
public ColorSequenceKeypoint(float time, Color3 value, int envelope = 0)
{
Time = time;
@ -13,9 +18,11 @@
Envelope = envelope;
}
public override string ToString()
internal ColorSequenceKeypoint(Attribute attr)
{
return string.Join(" ", Time, Value.R, Value.G, Value.B, Envelope);
Envelope = attr.readInt();
Time = attr.readFloat();
Value = new Color3(attr);
}
}
}

View File

@ -7,11 +7,7 @@
public class Content
{
public readonly string Url;
public override string ToString()
{
return Url;
}
public override string ToString() => Url;
public Content(string url)
{

View File

@ -7,24 +7,39 @@ namespace RobloxFiles.DataTypes
public readonly float Min;
public readonly float Max;
public override string ToString() => $"{Min} {Max}";
public NumberRange(float num)
{
Min = num;
Max = num;
}
private static void checkRange(float min, float max)
{
if (max - min >= 0)
return;
throw new Exception("NumberRange: invalid range");
}
public NumberRange(float min = 0, float max = 0)
{
if (max - min < 0)
throw new Exception("NumberRange: invalid range");
checkRange(min, max);
Min = min;
Max = max;
}
public override string ToString()
internal NumberRange(Attribute attr)
{
return string.Join(" ", Min, Max);
float min = attr.readFloat();
float max = attr.readFloat();
checkRange(min, max);
Min = min;
Max = max;
}
}
}

View File

@ -6,6 +6,11 @@ namespace RobloxFiles.DataTypes
{
public readonly NumberSequenceKeypoint[] Keypoints;
public override string ToString()
{
return string.Join<NumberSequenceKeypoint>(" ", Keypoints);
}
public NumberSequence(float n)
{
NumberSequenceKeypoint a = new NumberSequenceKeypoint(0, n);
@ -47,9 +52,15 @@ namespace RobloxFiles.DataTypes
Keypoints = keypoints;
}
public override string ToString()
public NumberSequence(Attribute attr)
{
return string.Join<NumberSequenceKeypoint>(" ", Keypoints);
int numKeys = attr.readInt();
var keypoints = new NumberSequenceKeypoint[numKeys];
for (int i = 0; i < numKeys; i++)
keypoints[i] = new NumberSequenceKeypoint(attr);
Keypoints = keypoints;
}
}
}

View File

@ -6,6 +6,11 @@
public readonly float Value;
public readonly float Envelope;
public override string ToString()
{
return $"{Time} {Value} {Envelope}";
}
public NumberSequenceKeypoint(float time, float value, float envelope = 0)
{
Time = time;
@ -13,9 +18,11 @@
Envelope = envelope;
}
public override string ToString()
internal NumberSequenceKeypoint(Attribute attr)
{
return string.Join(" ", Time, Value, Envelope);
Envelope = attr.readFloat();
Time = attr.readFloat();
Value = attr.readFloat();
}
}
}

View File

@ -12,6 +12,11 @@ namespace RobloxFiles.DataTypes
public readonly float FrictionWeight = 1.0f;
public readonly float ElasticityWeight = 1.0f;
public override string ToString()
{
return $"{Density}, {Friction}, {Elasticity}, {FrictionWeight}, {ElasticityWeight}";
}
public PhysicalProperties(Material material)
{
if (MaterialInfo.FrictionWeightMap.ContainsKey(material))
@ -32,9 +37,14 @@ namespace RobloxFiles.DataTypes
ElasticityWeight = elasticityWeight;
}
public override string ToString()
internal PhysicalProperties(Attribute attr)
{
return string.Join(", ", Density, Friction, Elasticity, FrictionWeight, ElasticityWeight);
Density = attr.readFloat();
Friction = attr.readFloat();
Elasticity = attr.readFloat();
FrictionWeight = attr.readFloat();
ElasticityWeight = attr.readFloat();
}
}
}

View File

@ -7,11 +7,7 @@
public class ProtectedString
{
public readonly string ProtectedValue;
public override string ToString()
{
return ProtectedValue;
}
public override string ToString() => ProtectedValue;
public ProtectedString(string value)
{

View File

@ -9,6 +9,7 @@ namespace RobloxFiles.DataTypes
public class Quaternion
{
public readonly float X, Y, Z, W;
public override string ToString() => $"{X}, {Y}, {Z}, {W}";
public float Magnitude
{
@ -20,12 +21,7 @@ namespace RobloxFiles.DataTypes
return (float)magnitude;
}
}
public override string ToString()
{
return string.Join(", ", X, Y, Z, W);
}
public Quaternion(float x, float y, float z, float w)
{
X = x;

View File

@ -5,6 +5,8 @@
public readonly Vector3 Origin;
public readonly Vector3 Direction;
public override string ToString() => $"{{{Origin}}}, {{{Direction}}}";
public Ray Unit
{
get
@ -26,9 +28,10 @@
Direction = direction ?? new Vector3();
}
public override string ToString()
internal Ray(Attribute attr)
{
return '{' + Origin.ToString() + "}, {" + Direction.ToString() + '}';
Origin = new Vector3(attr);
Direction = new Vector3(attr);
}
public Vector3 ClosestPoint(Vector3 point)

View File

@ -8,6 +8,8 @@
public float Width => (Max - Min).X;
public float Height => (Max - Min).Y;
public override string ToString() => $"{Min}, {Max}";
public Rect(Vector2 min = null, Vector2 max = null)
{
Min = min ?? Vector2.Zero;
@ -20,9 +22,10 @@
Max = new Vector2(maxX, maxY);
}
public override string ToString()
internal Rect(Attribute attr)
{
return string.Join(", ", Min, Max);
Min = new Vector2(attr);
Max = new Vector2(attr);
}
}
}

View File

@ -4,37 +4,39 @@ namespace RobloxFiles.DataTypes
{
public class Region3
{
public readonly CFrame CFrame;
public readonly Vector3 Size;
public readonly Vector3 Min, Max;
public Region3(Vector3 a, Vector3 b)
public Vector3 Size => (Max - Min);
public CFrame CFrame => new CFrame((Min + Max) / 2);
public override string ToString() => $"{CFrame}; {Size}";
public Region3(Vector3 min, Vector3 max)
{
CFrame = new CFrame((a + b) / 2);
Size = b - a;
Min = min;
Max = max;
}
public override string ToString()
internal Region3(Attribute attr)
{
return CFrame + "; " + Size;
Min = new Vector3(attr);
Max = new Vector3(attr);
}
public Region3 ExpandToGrid(float resolution)
{
Vector3 min = (CFrame - (Size / 2)).Position / resolution;
Vector3 max = (CFrame + (Size / 2)).Position / resolution;
Vector3 emin = new Vector3
(
(float)Math.Floor(min.X) * resolution,
(float)Math.Floor(min.Y) * resolution,
(float)Math.Floor(min.Z) * resolution
(float)Math.Floor(Min.X) * resolution,
(float)Math.Floor(Min.Y) * resolution,
(float)Math.Floor(Min.Z) * resolution
);
Vector3 emax = new Vector3
(
(float)Math.Floor(max.X) * resolution,
(float)Math.Floor(max.Y) * resolution,
(float)Math.Floor(max.Z) * resolution
(float)Math.Floor(Max.X) * resolution,
(float)Math.Floor(Max.Y) * resolution,
(float)Math.Floor(Max.Z) * resolution
);
return new Region3(emin, emax);

View File

@ -1,14 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RobloxFiles.DataTypes
namespace RobloxFiles.DataTypes
{
public struct Region3int16
public class Region3int16
{
public readonly Vector3int16 Min, Max;
public override string ToString() => $"{Min}; {Max}";
public Region3int16(Vector3int16 min = null, Vector3int16 max = null)
{
@ -16,9 +11,10 @@ namespace RobloxFiles.DataTypes
Max = max ?? new Vector3int16();
}
public override string ToString()
internal Region3int16(Attribute attr)
{
return string.Join("; ", Min, Max);
Min = new Vector3int16(attr);
Max = new Vector3int16(attr);
}
}
}

View File

@ -5,12 +5,20 @@
public readonly float Scale;
public readonly int Offset;
public override string ToString() => $"{Scale}, {Offset}";
public UDim(float scale = 0, int offset = 0)
{
Scale = scale;
Offset = offset;
}
internal UDim(Attribute attr)
{
Scale = attr.readFloat();
Offset = attr.readInt();
}
public static UDim operator+(UDim a, UDim b)
{
return new UDim(a.Scale + b.Scale, a.Offset + b.Offset);
@ -20,10 +28,5 @@
{
return new UDim(a.Scale - b.Scale, a.Offset - b.Offset);
}
public override string ToString()
{
return string.Join(", ", Scale, Offset);
}
}
}

View File

@ -3,6 +3,7 @@
public class UDim2
{
public readonly UDim X, Y;
public override string ToString() => $"{{{X}}},{{{Y}}}";
public UDim Width => X;
public UDim Height => Y;
@ -19,11 +20,12 @@
Y = y;
}
public override string ToString()
internal UDim2(Attribute attr)
{
return '{' + X.ToString() + "},{" + Y.ToString() + '}';
X = new UDim(attr);
Y = new UDim(attr);
}
public UDim2 Lerp(UDim2 other, float alpha)
{
float scaleX = X.Scale + ((other.X.Scale - X.Scale) * alpha);

View File

@ -5,7 +5,8 @@ namespace RobloxFiles.DataTypes
public class Vector2
{
public readonly float X, Y;
public override string ToString() => $"{X}, {Y}";
public float Magnitude
{
get
@ -34,6 +35,12 @@ namespace RobloxFiles.DataTypes
Y = coords.Length > 1 ? coords[1] : 0;
}
internal Vector2(Attribute attr)
{
X = attr.readFloat();
Y = attr.readFloat();
}
private delegate Vector2 Operator(Vector2 a, Vector2 b);
private static Vector2 upcastFloatOp(Vector2 vec, float num, Operator upcast)
@ -76,11 +83,6 @@ namespace RobloxFiles.DataTypes
public static Vector2 Zero => new Vector2(0, 0);
public override string ToString()
{
return string.Join(", ", X, Y);
}
public float Dot(Vector2 other)
{
float dotX = X * other.X;

69
DataTypes/Vector2int16.cs Normal file
View File

@ -0,0 +1,69 @@
using System;
namespace RobloxFiles.DataTypes
{
public class Vector2int16
{
public readonly short X, Y;
public override string ToString() => $"{X}, {Y}";
public Vector2int16(short x = 0, short y = 0)
{
X = x;
Y = y;
}
public Vector2int16(int x = 0, int y = 0)
{
X = (short)x;
Y = (short)y;
}
internal Vector2int16(Attribute attr)
{
X = attr.readShort();
Y = attr.readShort();
}
private delegate Vector2int16 Operator(Vector2int16 a, Vector2int16 b);
private static Vector2int16 upcastShortOp(Vector2int16 vec, short num, Operator upcast)
{
Vector2int16 numVec = new Vector2int16(num, num);
return upcast(vec, numVec);
}
private static Vector2int16 upcastShortOp(short num, Vector2int16 vec, Operator upcast)
{
Vector2int16 numVec = new Vector2int16(num, num);
return upcast(numVec, vec);
}
private static Operator add = new Operator((a, b) => new Vector2int16(a.X + b.X, a.Y + b.Y));
private static Operator sub = new Operator((a, b) => new Vector2int16(a.X - b.X, a.Y - b.Y));
private static Operator mul = new Operator((a, b) => new Vector2int16(a.X * b.X, a.Y * b.Y));
private static Operator div = new Operator((a, b) =>
{
if (b.X == 0 || b.Y == 0)
throw new DivideByZeroException();
return new Vector2int16(a.X / b.X, a.Y / b.Y);
});
public static Vector2int16 operator +(Vector2int16 a, Vector2int16 b) => add(a, b);
public static Vector2int16 operator +(Vector2int16 v, short n) => upcastShortOp(v, n, add);
public static Vector2int16 operator +(short n, Vector2int16 v) => upcastShortOp(n, v, add);
public static Vector2int16 operator -(Vector2int16 a, Vector2int16 b) => sub(a, b);
public static Vector2int16 operator -(Vector2int16 v, short n) => upcastShortOp(v, n, sub);
public static Vector2int16 operator -(short n, Vector2int16 v) => upcastShortOp(n, v, sub);
public static Vector2int16 operator *(Vector2int16 a, Vector2int16 b) => mul(a, b);
public static Vector2int16 operator *(Vector2int16 v, short n) => upcastShortOp(v, n, mul);
public static Vector2int16 operator *(short n, Vector2int16 v) => upcastShortOp(n, v, mul);
public static Vector2int16 operator /(Vector2int16 a, Vector2int16 b) => div(a, b);
public static Vector2int16 operator /(Vector2int16 v, short n) => upcastShortOp(v, n, div);
public static Vector2int16 operator /(short n, Vector2int16 v) => upcastShortOp(n, v, div);
}
}

View File

@ -6,6 +6,7 @@ namespace RobloxFiles.DataTypes
public class Vector3
{
public readonly float X, Y, Z;
public override string ToString() => $"{X}, {Y}, {Z}";
public float Magnitude
{
@ -37,6 +38,13 @@ namespace RobloxFiles.DataTypes
Z = coords.Length > 2 ? coords[2] : 0;
}
internal Vector3(Attribute attr)
{
X = attr.readFloat();
Y = attr.readFloat();
Z = attr.readFloat();
}
public static Vector3 FromAxis(Axis axis)
{
float[] coords = new float[3] { 0f, 0f, 0f };
@ -102,10 +110,7 @@ namespace RobloxFiles.DataTypes
public static Vector3 Up => new Vector3(0, 1, 0);
public static Vector3 Back => new Vector3(0, 0, 1);
public override string ToString()
{
return string.Join(", ", X, Y, Z);
}
public float Dot(Vector3 other)
{

View File

@ -5,12 +5,10 @@ namespace RobloxFiles.DataTypes
public class Vector3int16
{
public readonly short X, Y, Z;
public override string ToString() => $"{X}, {Y}, {Z}";
public Vector3int16()
public Vector3int16() : this(0, 0, 0)
{
X = 0;
Y = 0;
Z = 0;
}
public Vector3int16(short x = 0, short y = 0, short z = 0)
@ -27,9 +25,11 @@ namespace RobloxFiles.DataTypes
Z = (short)z;
}
public override string ToString()
internal Vector3int16(Attribute attr)
{
return string.Join(", ", X, Y, Z);
X = attr.readShort();
Y = attr.readShort();
Z = attr.readShort();
}
private delegate Vector3int16 Operator(Vector3int16 a, Vector3int16 b);

View File

@ -81,10 +81,13 @@
<Compile Include="DataTypes\Color3uint8.cs" />
<Compile Include="DataTypes\ProtectedString.cs" />
<Compile Include="DataTypes\Content.cs" />
<Compile Include="DataTypes\Region3int16.cs" />
<Compile Include="DataTypes\SharedString.cs" />
<Compile Include="DataTypes\Vector2int16.cs" />
<Compile Include="Interfaces\IBinaryFileChunk.cs" />
<Compile Include="Generated\Classes.cs" />
<Compile Include="Generated\Enums.cs" />
<Compile Include="Tree\Attributes.cs" />
<Compile Include="Tree\Property.cs" />
<Compile Include="Tree\Instance.cs" />
<Compile Include="RobloxFile.cs" />

Binary file not shown.

252
Tree/Attributes.cs Normal file
View File

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

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
@ -57,9 +58,23 @@ namespace RobloxFiles
/// <summary>A list of CollectionService tags assigned to this Instance.</summary>
public List<string> Tags => RawTags;
/// <summary>The attributes defined for this Instance.</summary>
public Attributes Attributes { get; private set; }
/// <summary>The internal serialized data of this Instance's attributes</summary>
internal byte[] AttributesSerialize;
internal byte[] AttributesSerialize
{
get
{
return Attributes?.Serialize() ?? new byte[0];
}
set
{
MemoryStream data = new MemoryStream(value);
Attributes = new Attributes(data);
}
}
/// <summary>
/// Internal format of the Instance's CollectionService tags.
/// Property objects will look to this member for serializing the Tags property.
@ -538,6 +553,7 @@ namespace RobloxFiles
}
Property tags = GetProperty("Tags");
Property attributes = GetProperty("AttributesSerialize");
if (tags == null)
{
@ -545,6 +561,12 @@ namespace RobloxFiles
AddProperty(ref tags);
}
if (attributes == null)
{
attributes = new Property("AttributesSerialize", PropertyType.String);
AddProperty(ref attributes);
}
return Properties;
}
}

View File

@ -166,6 +166,11 @@ namespace RobloxFiles
byte[] data = Instance.SerializedTags;
RawValue = data;
}
else if (Name == "AttributesSerialize")
{
byte[] data = Instance.AttributesSerialize;
RawValue = data;
}
else
{
FieldInfo field = Instance.GetType()
@ -194,6 +199,11 @@ namespace RobloxFiles
byte[] data = value as byte[];
Instance.SerializedTags = data;
}
else if (Name == "AttributesSerialize" && value is byte[])
{
byte[] data = value as byte[];
Instance.AttributesSerialize = data;
}
else
{
FieldInfo field = Instance.GetType()

View File

@ -1,5 +1,7 @@
using System;
using System.IO;
using System.Globalization;
using System.Text;
internal static class Formatting
{
@ -112,4 +114,15 @@ internal static class Formatting
{
return Math.Abs(a - b) < epsilon;
}
public static string ReadString(this BinaryReader reader, bool useIntLength)
{
if (!useIntLength)
return reader.ReadString();
int len = reader.ReadInt32();
byte[] buffer = reader.ReadBytes(len);
return Encoding.UTF8.GetString(buffer);
}
}