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:
252
Tree/Attributes.cs
Normal file
252
Tree/Attributes.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
Reference in New Issue
Block a user