Roblox-File-Format/Tree/Property.cs

346 lines
11 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using RobloxFiles.BinaryFormat.Chunks;
using RobloxFiles.DataTypes;
using RobloxFiles.Utility;
namespace RobloxFiles
{
public enum PropertyType
{
Unknown,
String,
Bool,
Int,
Float,
Double,
UDim,
UDim2,
Ray,
Faces,
Axes,
BrickColor,
Color3,
Vector2,
Vector3,
CFrame = 16,
Quaternion,
Enum,
Ref,
Vector3int16,
NumberSequence,
ColorSequence,
NumberRange,
Rect,
PhysicalProperties,
Color3uint8,
Int64,
SharedString,
2021-05-01 22:40:09 +00:00
ProtectedString,
OptionalCFrame,
UniqueId,
2023-11-29 03:07:30 +00:00
FontFace,
SecurityCapabilities,
}
public class Property
{
2019-07-04 23:26:53 +00:00
public string Name { get; internal set; }
2019-05-19 04:44:51 +00:00
public Instance Instance { get; internal set; }
2019-07-04 23:26:53 +00:00
public PropertyType Type { get; internal set; }
2019-07-04 23:26:53 +00:00
public string XmlToken { get; internal set; }
public byte[] RawBuffer { get; internal set; }
2019-06-11 01:27:57 +00:00
internal object RawValue;
2019-10-30 23:33:00 +00:00
internal static BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.IgnoreCase;
2021-05-01 22:40:09 +00:00
2023-11-29 03:07:30 +00:00
// FIXME: Add a proper type for SecurityCapabilities once it's purpose is better understood.
2021-05-01 22:40:09 +00:00
public static readonly IReadOnlyDictionary<Type, PropertyType> Types = new Dictionary<Type, PropertyType>()
{
{ typeof(Axes), PropertyType.Axes },
{ typeof(Faces), PropertyType.Faces },
{ typeof(int), PropertyType.Int },
{ typeof(bool), PropertyType.Bool },
{ typeof(long), PropertyType.Int64 },
{ typeof(float), PropertyType.Float },
{ typeof(double), PropertyType.Double },
{ typeof(string), PropertyType.String },
{ typeof(Ray), PropertyType.Ray },
{ typeof(Rect), PropertyType.Rect },
{ typeof(UDim), PropertyType.UDim },
{ typeof(UDim2), PropertyType.UDim2 },
{ typeof(CFrame), PropertyType.CFrame },
{ typeof(Color3), PropertyType.Color3 },
{ typeof(Content), PropertyType.String },
{ typeof(Vector2), PropertyType.Vector2 },
{ typeof(Vector3), PropertyType.Vector3 },
{ typeof(FontFace), PropertyType.FontFace },
2019-07-04 23:26:53 +00:00
{ typeof(BrickColor), PropertyType.BrickColor },
{ typeof(Quaternion), PropertyType.Quaternion },
{ typeof(Color3uint8), PropertyType.Color3uint8 },
{ typeof(NumberRange), PropertyType.NumberRange },
{ typeof(SharedString), PropertyType.SharedString },
{ typeof(Vector3int16), PropertyType.Vector3int16 },
{ typeof(ColorSequence), PropertyType.ColorSequence },
{ typeof(NumberSequence), PropertyType.NumberSequence },
{ typeof(Optional<CFrame>), PropertyType.OptionalCFrame },
{ typeof(ProtectedString), PropertyType.String },
2023-11-29 03:07:30 +00:00
{ typeof(PhysicalProperties), PropertyType.PhysicalProperties },
{ typeof(ulong), PropertyType.SecurityCapabilities },
};
2019-06-11 01:27:57 +00:00
private void ImproviseRawBuffer()
{
2020-08-18 01:12:24 +00:00
if (RawValue is byte[])
{
2020-08-18 01:12:24 +00:00
RawBuffer = RawValue as byte[];
return;
}
2021-05-01 22:40:09 +00:00
else if (RawValue is SharedString sharedString)
{
2020-08-18 01:12:24 +00:00
if (sharedString != null)
{
RawBuffer = sharedString.SharedValue;
return;
}
}
2021-05-01 22:40:09 +00:00
else if (RawValue is ProtectedString protectedString)
{
2020-08-18 01:12:24 +00:00
if (protectedString != null)
{
RawBuffer = protectedString.RawBuffer;
return;
}
}
2020-09-14 16:20:34 +00:00
if (RawValue is long)
Type = PropertyType.Int64;
2019-06-11 01:27:57 +00:00
switch (Type)
{
case PropertyType.Int:
2021-05-01 22:40:09 +00:00
{
2020-09-14 16:20:34 +00:00
if (Value is long)
{
Type = PropertyType.Int64;
goto case PropertyType.Int64;
}
2021-05-01 22:40:09 +00:00
2019-06-11 01:27:57 +00:00
RawBuffer = BitConverter.GetBytes((int)Value);
break;
2021-05-01 22:40:09 +00:00
}
2019-06-11 01:27:57 +00:00
case PropertyType.Bool:
2021-05-01 22:40:09 +00:00
{
2019-06-11 01:27:57 +00:00
RawBuffer = BitConverter.GetBytes((bool)Value);
break;
2021-05-01 22:40:09 +00:00
}
2020-08-18 01:12:24 +00:00
case PropertyType.Int64:
2021-05-01 22:40:09 +00:00
{
2020-08-18 01:12:24 +00:00
RawBuffer = BitConverter.GetBytes((long)Value);
break;
2021-05-01 22:40:09 +00:00
}
2019-06-11 01:27:57 +00:00
case PropertyType.Float:
2021-05-01 22:40:09 +00:00
{
2019-06-11 01:27:57 +00:00
RawBuffer = BitConverter.GetBytes((float)Value);
break;
2021-05-01 22:40:09 +00:00
}
2019-06-11 01:27:57 +00:00
case PropertyType.Double:
2021-05-01 22:40:09 +00:00
{
2019-06-11 01:27:57 +00:00
RawBuffer = BitConverter.GetBytes((double)Value);
break;
2021-05-01 22:40:09 +00:00
}
2019-06-11 01:27:57 +00:00
}
}
private string ImplicitName
{
get
{
if (Instance != null)
{
Type instType = Instance.GetType();
string typeName = instType.Name;
if (typeName == Name)
{
2021-05-01 22:40:09 +00:00
FieldInfo directField = instType.GetFields()
2020-08-17 05:33:59 +00:00
.Where(field => field.Name.StartsWith(Name, StringComparison.InvariantCulture))
.Where(field => field.DeclaringType == instType)
.FirstOrDefault();
2019-07-04 23:26:53 +00:00
if (directField != null)
{
var implicitName = Name + '_';
return implicitName;
}
}
}
if (Name.Contains(" "))
2019-07-04 23:26:53 +00:00
return Name.Replace(' ', '_');
return Name;
}
}
2019-06-11 01:27:57 +00:00
public object Value
{
get
{
if (Instance != null)
{
if (Name == "Tags")
{
byte[] data = Instance.SerializedTags;
RawValue = data;
}
else
{
var type = Instance.GetType();
var member = ImplicitMember.Get(type, ImplicitName);
if (member != null)
{
object value = member.GetValue(Instance);
RawValue = value;
}
else
{
2021-01-20 20:45:58 +00:00
RobloxFile.LogError($"RobloxFiles.Property - Property {Instance.ClassName}.{Name} does not exist!");
}
}
}
2019-06-11 01:27:57 +00:00
return RawValue;
}
set
{
if (Instance != null)
{
2021-02-18 19:15:08 +00:00
if (Name == "Tags" && value is byte[] data)
{
Instance.SerializedTags = data;
}
else
{
var type = Instance.GetType();
var member = ImplicitMember.Get(type, ImplicitName);
if (member != null)
{
var valueType = value?.GetType();
Type memberType = member.MemberType;
if (value == null || memberType.IsAssignableFrom(valueType))
{
try
{
member.SetValue(Instance, value);
}
catch
{
2021-01-20 20:45:58 +00:00
RobloxFile.LogError($"RobloxFiles.Property - Failed to cast value {value} into property {Instance.ClassName}.{Name}");
}
}
else if (valueType != null)
{
MethodInfo implicitCast = memberType.GetMethod("op_Implicit", new Type[] { valueType });
if (implicitCast != null)
{
try
{
object castedValue = implicitCast.Invoke(null, new object[] { value });
member.SetValue(Instance, castedValue);
}
catch
{
2021-01-20 20:45:58 +00:00
RobloxFile.LogError($"RobloxFiles.Property - Failed to implicitly cast value {value} into property {Instance.ClassName}.{Name}");
}
}
}
}
}
}
2019-06-11 01:27:57 +00:00
RawValue = value;
RawBuffer = null;
ImproviseRawBuffer();
}
}
public bool HasRawBuffer
{
get
{
// Improvise what the buffer should be if this is a primitive.
2021-05-01 22:40:09 +00:00
if (RawBuffer == null && Value != null)
2019-06-11 01:27:57 +00:00
ImproviseRawBuffer();
return (RawBuffer != null);
}
}
public Property(string name = "", PropertyType type = PropertyType.Unknown, Instance instance = null)
{
Instance = instance;
Name = name;
Type = type;
}
public Property(Instance instance, PROP property)
{
Instance = instance;
Name = property.Name;
Type = property.Type;
}
public string GetFullName()
{
string result = Name;
if (Instance != null)
2020-09-14 16:20:34 +00:00
result = Instance.GetFullName() + "->" + result;
return result;
}
public override string ToString()
{
string typeName = Enum.GetName(typeof(PropertyType), Type);
string valueLabel = (Value != null ? Value.ToString() : "null");
if (Type == PropertyType.String)
valueLabel = '"' + valueLabel + '"';
return string.Join(" ", typeName, Name, '=', valueLabel);
}
public T CastValue<T>()
{
2019-07-04 23:26:53 +00:00
object result;
2019-07-04 23:26:53 +00:00
if (typeof(T) == typeof(string))
result = Value?.ToString() ?? "";
2020-09-14 16:20:34 +00:00
else if (Value is T typedValue)
result = typedValue;
else
result = default(T);
2019-07-04 23:26:53 +00:00
return (T)result;
}
}
}