2019-01-26 00:39:37 +00:00
|
|
|
|
using System;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
using System.Collections.Generic;
|
2020-06-22 01:02:36 +00:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Linq;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
using System.Reflection;
|
2019-06-08 03:43:28 +00:00
|
|
|
|
|
|
|
|
|
using RobloxFiles.BinaryFormat;
|
2019-05-17 06:14:04 +00:00
|
|
|
|
using RobloxFiles.BinaryFormat.Chunks;
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
using RobloxFiles.DataTypes;
|
2020-06-22 01:02:36 +00:00
|
|
|
|
using RobloxFiles.Utility;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2019-02-01 17:19:20 +00:00
|
|
|
|
namespace RobloxFiles
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
|
|
|
|
public enum PropertyType
|
|
|
|
|
{
|
|
|
|
|
Unknown,
|
|
|
|
|
String,
|
|
|
|
|
Bool,
|
|
|
|
|
Int,
|
|
|
|
|
Float,
|
|
|
|
|
Double,
|
|
|
|
|
UDim,
|
|
|
|
|
UDim2,
|
|
|
|
|
Ray,
|
|
|
|
|
Faces,
|
|
|
|
|
Axes,
|
|
|
|
|
BrickColor,
|
|
|
|
|
Color3,
|
|
|
|
|
Vector2,
|
|
|
|
|
Vector3,
|
2019-06-30 22:01:19 +00:00
|
|
|
|
CFrame = 16,
|
2019-01-26 00:39:37 +00:00
|
|
|
|
Quaternion,
|
|
|
|
|
Enum,
|
|
|
|
|
Ref,
|
|
|
|
|
Vector3int16,
|
|
|
|
|
NumberSequence,
|
|
|
|
|
ColorSequence,
|
|
|
|
|
NumberRange,
|
|
|
|
|
Rect,
|
|
|
|
|
PhysicalProperties,
|
|
|
|
|
Color3uint8,
|
2019-05-17 06:14:04 +00:00
|
|
|
|
Int64,
|
2019-05-17 12:08:06 +00:00
|
|
|
|
SharedString,
|
2019-01-26 00:39:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-29 09:50:55 +00:00
|
|
|
|
public class Property
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
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-02-04 19:30:33 +00:00
|
|
|
|
|
2019-07-04 23:26:53 +00:00
|
|
|
|
public PropertyType Type { get; internal set; }
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2019-07-04 23:26:53 +00:00
|
|
|
|
public string XmlToken { get; internal set; }
|
|
|
|
|
public byte[] RawBuffer { get; internal set; }
|
2019-05-17 12:08:06 +00:00
|
|
|
|
|
2019-06-11 01:27:57 +00:00
|
|
|
|
internal object RawValue;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
internal BinaryRobloxFileWriter CurrentWriter;
|
|
|
|
|
|
2019-10-30 23:33:00 +00:00
|
|
|
|
internal static BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.IgnoreCase;
|
2020-06-22 01:02:36 +00:00
|
|
|
|
internal static MemberTypes FieldOrProperty = MemberTypes.Field | MemberTypes.Property;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2020-06-22 01:02:36 +00:00
|
|
|
|
public static readonly IReadOnlyDictionary<Type, PropertyType> Types = new Dictionary<Type, PropertyType>()
|
2019-06-30 22:01:19 +00:00
|
|
|
|
{
|
|
|
|
|
{ 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 },
|
2019-07-04 23:26:53 +00:00
|
|
|
|
{ typeof(Content), PropertyType.String },
|
2019-06-30 22:01:19 +00:00
|
|
|
|
{ typeof(Vector2), PropertyType.Vector2 },
|
|
|
|
|
{ typeof(Vector3), PropertyType.Vector3 },
|
2019-07-04 23:26:53 +00:00
|
|
|
|
|
2020-06-22 01:02:36 +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(ProtectedString), PropertyType.String },
|
|
|
|
|
{ typeof(PhysicalProperties), PropertyType.PhysicalProperties },
|
2019-06-30 22:01:19 +00:00
|
|
|
|
};
|
2019-06-11 01:27:57 +00:00
|
|
|
|
|
|
|
|
|
private void ImproviseRawBuffer()
|
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (RawValue is byte[])
|
|
|
|
|
{
|
|
|
|
|
RawBuffer = RawValue as byte[];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (RawValue is SharedString)
|
|
|
|
|
{
|
|
|
|
|
var sharedString = CastValue<SharedString>();
|
2019-07-04 23:26:53 +00:00
|
|
|
|
RawBuffer = sharedString.SharedValue;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-11 01:27:57 +00:00
|
|
|
|
switch (Type)
|
|
|
|
|
{
|
|
|
|
|
case PropertyType.Int:
|
|
|
|
|
RawBuffer = BitConverter.GetBytes((int)Value);
|
|
|
|
|
break;
|
|
|
|
|
case PropertyType.Int64:
|
|
|
|
|
RawBuffer = BitConverter.GetBytes((long)Value);
|
|
|
|
|
break;
|
|
|
|
|
case PropertyType.Bool:
|
|
|
|
|
RawBuffer = BitConverter.GetBytes((bool)Value);
|
|
|
|
|
break;
|
|
|
|
|
case PropertyType.Float:
|
|
|
|
|
RawBuffer = BitConverter.GetBytes((float)Value);
|
|
|
|
|
break;
|
|
|
|
|
case PropertyType.Double:
|
|
|
|
|
RawBuffer = BitConverter.GetBytes((double)Value);
|
|
|
|
|
break;
|
|
|
|
|
//
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
private string ImplicitName
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (Instance != null)
|
|
|
|
|
{
|
|
|
|
|
Type instType = Instance.GetType();
|
|
|
|
|
string typeName = instType.Name;
|
|
|
|
|
|
|
|
|
|
if (typeName == Name)
|
|
|
|
|
{
|
2019-07-04 23:26:53 +00:00
|
|
|
|
FieldInfo directField = instType.GetField(typeName, BindingFlags.DeclaredOnly);
|
|
|
|
|
|
|
|
|
|
if (directField != null)
|
|
|
|
|
{
|
|
|
|
|
var implicitName = Name + '_';
|
|
|
|
|
return implicitName;
|
|
|
|
|
}
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-03 14:48:05 +00:00
|
|
|
|
if (Name.Contains(" "))
|
2019-07-04 23:26:53 +00:00
|
|
|
|
return Name.Replace(' ', '_');
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
return Name;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-11 01:27:57 +00:00
|
|
|
|
public object Value
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (Instance != null)
|
|
|
|
|
{
|
|
|
|
|
if (Name == "Tags")
|
|
|
|
|
{
|
|
|
|
|
byte[] data = Instance.SerializedTags;
|
|
|
|
|
RawValue = data;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-06-22 01:02:36 +00:00
|
|
|
|
var type = Instance.GetType();
|
|
|
|
|
var member = ImplicitMember.Get(type, ImplicitName);
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2020-06-22 01:02:36 +00:00
|
|
|
|
if (member != null)
|
2019-06-30 22:01:19 +00:00
|
|
|
|
{
|
2020-06-22 01:02:36 +00:00
|
|
|
|
object value = member.GetValue(Instance);
|
2019-06-30 22:01:19 +00:00
|
|
|
|
RawValue = value;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-06-22 01:02:36 +00:00
|
|
|
|
Console.Error.WriteLine($"RobloxFiles.Property - No defined member for {Instance.ClassName}.{Name}");
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-11 01:27:57 +00:00
|
|
|
|
return RawValue;
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (Instance != null)
|
|
|
|
|
{
|
|
|
|
|
if (Name == "Tags" && value is byte[])
|
|
|
|
|
{
|
|
|
|
|
byte[] data = value as byte[];
|
|
|
|
|
Instance.SerializedTags = data;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-06-22 01:02:36 +00:00
|
|
|
|
var type = Instance.GetType();
|
|
|
|
|
var member = ImplicitMember.Get(type, ImplicitName);
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2020-06-22 01:02:36 +00:00
|
|
|
|
if (member != null)
|
2019-06-30 22:01:19 +00:00
|
|
|
|
{
|
2020-06-22 01:02:36 +00:00
|
|
|
|
var valueType = value?.GetType();
|
|
|
|
|
Type memberType = member.MemberType;
|
|
|
|
|
|
|
|
|
|
if (memberType == valueType || value == null)
|
2019-07-03 14:48:05 +00:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2020-06-22 01:02:36 +00:00
|
|
|
|
member.SetValue(Instance, value);
|
2019-07-03 14:48:05 +00:00
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
2020-06-22 01:02:36 +00:00
|
|
|
|
Console.Error.WriteLine($"RobloxFiles.Property - Failed to cast value {value} into property {Instance.ClassName}.{Name}");
|
2019-07-03 14:48:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (valueType != null)
|
|
|
|
|
{
|
2020-06-22 01:02:36 +00:00
|
|
|
|
MethodInfo implicitCast = memberType.GetMethod("op_Implicit", new Type[] { valueType });
|
2019-07-03 14:48:05 +00:00
|
|
|
|
|
|
|
|
|
if (implicitCast != null)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2020-06-22 01:02:36 +00:00
|
|
|
|
object castedValue = implicitCast.Invoke(null, new object[] { value });
|
|
|
|
|
member.SetValue(Instance, castedValue);
|
2019-07-03 14:48:05 +00:00
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
2020-06-22 01:02:36 +00:00
|
|
|
|
Console.Error.WriteLine($"RobloxFiles.Property - Failed to implicitly cast value {value} into property {Instance.ClassName}.{Name}");
|
2019-07-03 14:48:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-11 01:27:57 +00:00
|
|
|
|
RawValue = value;
|
|
|
|
|
RawBuffer = null;
|
|
|
|
|
|
|
|
|
|
ImproviseRawBuffer();
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-08 03:43:28 +00:00
|
|
|
|
|
2019-01-29 09:50:55 +00:00
|
|
|
|
public bool HasRawBuffer
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2019-01-29 09:50:55 +00:00
|
|
|
|
get
|
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
// Improvise what the buffer should be if this is a primitive.
|
2019-01-29 09:50:55 +00:00
|
|
|
|
if (RawBuffer == null && Value != null)
|
2019-06-11 01:27:57 +00:00
|
|
|
|
ImproviseRawBuffer();
|
2019-01-29 09:50:55 +00:00
|
|
|
|
|
|
|
|
|
return (RawBuffer != null);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2019-05-17 06:14:04 +00:00
|
|
|
|
public Property(string name = "", PropertyType type = PropertyType.Unknown, Instance instance = null)
|
|
|
|
|
{
|
2019-06-08 03:43:28 +00:00
|
|
|
|
Instance = instance;
|
2019-05-17 06:14:04 +00:00
|
|
|
|
Name = name;
|
|
|
|
|
Type = type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Property(Instance instance, PROP property)
|
|
|
|
|
{
|
|
|
|
|
Instance = instance;
|
|
|
|
|
Name = property.Name;
|
|
|
|
|
Type = property.Type;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-30 06:36:56 +00:00
|
|
|
|
public string GetFullName()
|
|
|
|
|
{
|
|
|
|
|
string result = Name;
|
|
|
|
|
|
|
|
|
|
if (Instance != null)
|
|
|
|
|
result = Instance.GetFullName() + '.' + result;
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-29 09:50:55 +00:00
|
|
|
|
public override string ToString()
|
|
|
|
|
{
|
|
|
|
|
string typeName = Enum.GetName(typeof(PropertyType), Type);
|
|
|
|
|
string valueLabel = (Value != null ? Value.ToString() : "null");
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2019-01-29 09:50:55 +00:00
|
|
|
|
if (Type == PropertyType.String)
|
|
|
|
|
valueLabel = '"' + valueLabel + '"';
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
|
|
|
|
return string.Join(" ", typeName, Name, '=', valueLabel);
|
|
|
|
|
}
|
2019-06-08 03:43:28 +00:00
|
|
|
|
|
|
|
|
|
public T CastValue<T>()
|
|
|
|
|
{
|
2019-07-04 23:26:53 +00:00
|
|
|
|
object result;
|
2019-06-08 03:43:28 +00:00
|
|
|
|
|
2019-07-04 23:26:53 +00:00
|
|
|
|
if (typeof(T) == typeof(string))
|
|
|
|
|
result = Value?.ToString() ?? "";
|
|
|
|
|
else if (Value is T)
|
2019-06-08 03:43:28 +00:00
|
|
|
|
result = (T)Value;
|
|
|
|
|
else
|
|
|
|
|
result = default(T);
|
2019-07-04 23:26:53 +00:00
|
|
|
|
|
|
|
|
|
return (T)result;
|
2019-06-08 03:43:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void WriteValue<T>() where T : struct
|
|
|
|
|
{
|
|
|
|
|
if (CurrentWriter == null)
|
|
|
|
|
throw new Exception("Property.CurrentWriter must be set to use WriteValue<T>");
|
|
|
|
|
|
|
|
|
|
T value = CastValue<T>();
|
|
|
|
|
byte[] bytes = BinaryRobloxFileWriter.GetBytes(value);
|
2019-06-11 01:27:57 +00:00
|
|
|
|
|
2019-06-08 03:43:28 +00:00
|
|
|
|
CurrentWriter.Write(bytes);
|
|
|
|
|
}
|
2019-01-26 00:39:37 +00:00
|
|
|
|
}
|
2019-01-30 06:36:56 +00:00
|
|
|
|
}
|