Large scale refactor to add class support!

Instance classes are now strongly typed with real property fields that
are derived from the JSON API Dump! This required a lot of reworking
across the board:

- Classes and Enums are auto-generated in the 'Generated' folder now.
This is done using a custom built-in plugin, which can be found in
the Plugins folder of this project.
- Property objects are now tied to .NET's reflection system. Reading
and writing from them will try to redirect into a field of the
Instance they are bound to.
- Property types that were loosely defined now have proper data types
(such as Color3uint8, Content, ProtectedString, SharedString, etc)
- Fixed an error with the CFrame directional vectors.
- The binary PRNT chunk now writes instances in child->parent order.
- Enums are now generated correctly, with up-to-date values.
- INST chunks are now referred to as 'Classes' instead of 'Types'.
- Unary operator added to Vector2 and Vector3.
- CollectionService tags can now be manipulated per-instance using
the Instance.Tags member.
- The Instance.Archivable property now works correctly.
- XML files now save/load metadata correctly.
- Cleaned up the property tokens directory.

I probably missed a few things, but that's a general overview of
everything that changed.
This commit is contained in:
CloneTrooper1019
2019-06-30 17:01:19 -05:00
parent 8e01f33d6b
commit de8df15d3f
67 changed files with 6297 additions and 667 deletions

View File

@ -14,11 +14,23 @@ namespace RobloxFiles.DataTypes
public float Y => m24;
public float Z => m34;
public Vector3 Position => new Vector3(X, Y, Z);
public Vector3 Position
{
get
{
return new Vector3(X, Y, Z);
}
set
{
m14 = value.X;
m24 = value.Y;
m34 = value.Z;
}
}
public Vector3 LookVector => new Vector3(-m13, -m23, -m33);
public Vector3 RightVector => new Vector3( m11, m21, m31);
public Vector3 UpVector => new Vector3( m12, m22, m32);
public Vector3 RightVector => new Vector3( m11, m12, m13);
public Vector3 UpVector => new Vector3( m21, m22, m23);
public Vector3 LookVector => new Vector3(-m31, -m32, -m33);
public CFrame()
{
@ -41,7 +53,6 @@ namespace RobloxFiles.DataTypes
m34 = nz;
}
public CFrame(Vector3 eye, Vector3 look)
{
Vector3 zAxis = (eye - look).Unit;
@ -127,9 +138,9 @@ namespace RobloxFiles.DataTypes
{
float[] ac = a.GetComponents();
float x = ac[0], y = ac[1], z = ac[2],
m11 = ac[3], m12 = ac[4], m13 = ac[5],
m21 = ac[6], m22 = ac[7], m23 = ac[8],
float x = ac[0], y = ac[1], z = ac[2],
m11 = ac[3], m12 = ac[4], m13 = ac[5],
m21 = ac[6], m22 = ac[7], m23 = ac[8],
m31 = ac[9], m32 = ac[10], m33 = ac[11];
return new CFrame(x - b.X, y - b.Y, z - b.Z, m11, m12, m13, m21, m22, m23, m31, m32, m33);
@ -138,9 +149,10 @@ namespace RobloxFiles.DataTypes
public static Vector3 operator *(CFrame a, Vector3 b)
{
float[] ac = a.GetComponents();
float x = ac[0], y = ac[1], z = ac[2],
m11 = ac[3], m12 = ac[4], m13 = ac[5],
m21 = ac[6], m22 = ac[7], m23 = ac[8],
float x = ac[0], y = ac[1], z = ac[2],
m11 = ac[3], m12 = ac[4], m13 = ac[5],
m21 = ac[6], m22 = ac[7], m23 = ac[8],
m31 = ac[9], m32 = ac[10], m33 = ac[11];
Vector3 right = new Vector3(m11, m21, m31);
@ -154,14 +166,14 @@ namespace RobloxFiles.DataTypes
float[] ac = a.GetComponents();
float[] bc = b.GetComponents();
float a14 = ac[0], a24 = ac[1], a34 = ac[2],
a11 = ac[3], a12 = ac[4], a13 = ac[5],
a21 = ac[6], a22 = ac[7], a23 = ac[8],
float a14 = ac[0], a24 = ac[1], a34 = ac[2],
a11 = ac[3], a12 = ac[4], a13 = ac[5],
a21 = ac[6], a22 = ac[7], a23 = ac[8],
a31 = ac[9], a32 = ac[10], a33 = ac[11];
float b14 = bc[0], b24 = bc[1], b34 = bc[2],
b11 = bc[3], b12 = bc[4], b13 = bc[5],
b21 = bc[6], b22 = bc[7], b23 = bc[8],
float b14 = bc[0], b24 = bc[1], b34 = bc[2],
b11 = bc[3], b12 = bc[4], b13 = bc[5],
b21 = bc[6], b22 = bc[7], b23 = bc[8],
b31 = bc[9], b32 = bc[10], b33 = bc[11];
float n11 = a11 * b11 + a12 * b21 + a13 * b31 + a14 * m41;
@ -345,14 +357,14 @@ namespace RobloxFiles.DataTypes
for (int i = 3; i < 12; i++)
{
float t = matrix[i];
float t = Math.Abs(matrix[i]);
if (Math.Abs(t - 1f) < 10e-5f)
if (t.FuzzyEquals(1))
{
// Approximately ±1
sum1++;
}
else if (Math.Abs(t) < 10e-5f)
else if (t.FuzzyEquals(0))
{
// Approximately ±0
sum0++;
@ -364,10 +376,10 @@ namespace RobloxFiles.DataTypes
private static bool IsLegalOrientId(int orientId)
{
int xNormalAbs = (orientId / 6) % 3;
int yNormalAbs = orientId % 3;
int xOrientId = (orientId / 6) % 3;
int yOrientId = orientId % 3;
return (xNormalAbs != yNormalAbs);
return (xOrientId != yOrientId);
}
public int GetOrientId()

41
DataTypes/Color3uint8.cs Normal file
View File

@ -0,0 +1,41 @@
namespace RobloxFiles.DataTypes
{
/// <summary>
/// Color3uint8 functions as an interconvertible storage medium for Color3 types.
/// It is used by property types that want their Color3 value encoded with bytes instead of floats.
/// </summary>
public class Color3uint8
{
public readonly byte R, G, B;
public Color3uint8(byte r = 0, byte g = 0, byte b = 0)
{
R = r;
G = g;
B = b;
}
public override string ToString()
{
return string.Join(", ", R, G, B);
}
public static implicit operator Color3(Color3uint8 color)
{
float r = color.R / 255f;
float g = color.G / 255f;
float b = color.B / 255f;
return new Color3(r, g, b);
}
public static implicit operator Color3uint8(Color3 color)
{
byte r = (byte)(color.R * 255);
byte g = (byte)(color.G * 255);
byte b = (byte)(color.B * 255);
return new Color3uint8(r, g, b);
}
}
}

View File

@ -6,12 +6,8 @@ namespace RobloxFiles.DataTypes
{
public readonly ColorSequenceKeypoint[] Keypoints;
public ColorSequence(Color3 c)
public ColorSequence(Color3 c) : this(c, c)
{
ColorSequenceKeypoint a = new ColorSequenceKeypoint(0, c);
ColorSequenceKeypoint b = new ColorSequenceKeypoint(1, c);
Keypoints = new ColorSequenceKeypoint[2] { a, b };
}
public ColorSequence(Color3 c0, Color3 c1)
@ -35,10 +31,13 @@ namespace RobloxFiles.DataTypes
if (keypoints[key - 1].Time > keypoints[key].Time)
throw new Exception("ColorSequence: all keypoints must be ordered by time");
if (Math.Abs(keypoints[0].Time) >= 10e-5f)
var first = keypoints[0];
var last = keypoints[numKeys - 1];
if (!first.Time.FuzzyEquals(0))
throw new Exception("ColorSequence must start at time=0.0");
if (Math.Abs(keypoints[numKeys - 1].Time - 1f) >= 10e-5f)
if (!last.Time.FuzzyEquals(1))
throw new Exception("ColorSequence must end at time=1.0");
Keypoints = keypoints;

View File

@ -4,18 +4,18 @@
{
public readonly float Time;
public readonly Color3 Value;
public readonly byte[] Reserved;
public readonly int Envelope;
public ColorSequenceKeypoint(float time, Color3 value, byte[] reserved = null)
public ColorSequenceKeypoint(float time, Color3 value, int envelope = 0)
{
Time = time;
Value = value;
Reserved = reserved;
Envelope = envelope;
}
public override string ToString()
{
return string.Join(" ", Time, Value.R, Value.G, Value.B, 0);
return string.Join(" ", Time, Value.R, Value.G, Value.B, Envelope);
}
}
}

32
DataTypes/Content.cs Normal file
View File

@ -0,0 +1,32 @@
namespace RobloxFiles.DataTypes
{
/// <summary>
/// Content is a type used by most url-based XML properties.
/// Here, it only exists as a wrapper class for strings.
/// </summary>
public class Content
{
// TODO: Maybe introduce constraints to the value?
public readonly string Data;
public override string ToString()
{
return Data;
}
public Content(string data)
{
Data = data;
}
public static implicit operator string(Content content)
{
return content.Data;
}
public static implicit operator Content(string data)
{
return new Content(data);
}
}
}

View File

@ -7,6 +7,12 @@ namespace RobloxFiles.DataTypes
public readonly float Min;
public readonly float Max;
public NumberRange(float num)
{
Min = num;
Max = num;
}
public NumberRange(float min = 0, float max = 0)
{
if (max - min < 0)

View File

@ -35,10 +35,13 @@ namespace RobloxFiles.DataTypes
if (keypoints[key - 1].Time > keypoints[key].Time)
throw new Exception("NumberSequence: all keypoints must be ordered by time");
if (Math.Abs(keypoints[0].Time) >= 10e-5f)
var first = keypoints[0];
var last = keypoints[numKeys - 1];
if (!first.Time.FuzzyEquals(0))
throw new Exception("NumberSequence must start at time=0.0");
if (Math.Abs(keypoints[numKeys - 1].Time - 1f) >= 10e-5f)
if (!last.Time.FuzzyEquals(1))
throw new Exception("NumberSequence must end at time=1.0");
Keypoints = keypoints;

View File

@ -0,0 +1,31 @@
namespace RobloxFiles.DataTypes
{
/// <summary>
/// ProtectedString is a type used by some of the XML properties.
/// Here, it only exists as a wrapper class for strings.
/// </summary>
public class ProtectedString
{
public readonly string Value;
public override string ToString()
{
return Value;
}
public ProtectedString(string value)
{
Value = value;
}
public static implicit operator string(ProtectedString protectedString)
{
return protectedString.Value;
}
public static implicit operator ProtectedString(string value)
{
return new ProtectedString(value);
}
}
}

208
DataTypes/Quaternion.cs Normal file
View File

@ -0,0 +1,208 @@
using System;
using RobloxFiles.DataTypes;
namespace RobloxFiles.Utility
{
/// <summary>
/// Quaternion is a utility used by the CFrame DataType to handle rotation interpolation.
/// It can be used as an independent Quaternion implementation if you so please!
/// </summary>
public class Quaternion
{
public readonly float X, Y, Z, W;
public float Magnitude
{
get
{
float squared = Dot(this);
double magnitude = Math.Sqrt(squared);
return (float)magnitude;
}
}
public Quaternion(float x, float y, float z, float w)
{
X = x;
Y = y;
Z = z;
W = w;
}
public Quaternion(Vector3 qv, float qw)
{
X = qv.X;
Y = qv.Y;
Z = qv.Z;
W = qw;
}
public Quaternion(CFrame cf)
{
CFrame matrix = (cf - cf.Position);
float[] ac = cf.GetComponents();
float m11 = ac[3], m12 = ac[4], m13 = ac[5],
m21 = ac[6], m22 = ac[7], m23 = ac[8],
m31 = ac[9], m32 = ac[10], m33 = ac[11];
float trace = m11 + m22 + m33;
if (trace > 0)
{
float s = (float)Math.Sqrt(1 + trace);
float r = 0.5f / s;
W = s * 0.5f;
X = (m32 - m23) * r;
Y = (m13 - m31) * r;
Z = (m21 - m12) * r;
}
else
{
float big = Math.Max(Math.Max(m11, m22), m33);
if (big == m11)
{
float s = (float)Math.Sqrt(1 + m11 - m22 - m33);
float r = 0.5f / s;
W = (m32 - m23) * r;
X = 0.5f * s;
Y = (m21 + m12) * r;
Z = (m13 + m31) * r;
}
else if (big == m22)
{
float s = (float)Math.Sqrt(1 - m11 + m22 - m33);
float r = 0.5f / s;
W = (m13 - m31) * r;
X = (m21 + m12) * r;
Y = 0.5f * s;
Z = (m32 + m23) * r;
}
else if (big == m33)
{
float s = (float)Math.Sqrt(1 - m11 - m22 + m33);
float r = 0.5f / s;
W = (m21 - m12) * r;
X = (m13 + m31) * r;
Y = (m32 + m23) * r;
Z = 0.5f * s;
}
}
}
public float Dot(Quaternion other)
{
return (X * other.X) + (Y * other.Y) + (Z * other.Z) + (W * other.W);
}
public Quaternion Lerp(Quaternion other, float alpha)
{
Quaternion result = this * (1.0f - alpha) + other * alpha;
return result / result.Magnitude;
}
public Quaternion Slerp(Quaternion other, float alpha)
{
float cosAng = Dot(other);
if (cosAng < 0)
{
other = -other;
cosAng = -cosAng;
}
double ang = Math.Acos(cosAng);
if (ang >= 0.05f)
{
float scale0 = (float)Math.Sin((1.0f - alpha) * ang);
float scale1 = (float)Math.Sin(alpha * ang);
float denom = (float)Math.Sin(ang);
return ((this * scale0) + (other * scale1)) / denom;
}
else
{
return Lerp(other, alpha);
}
}
public CFrame ToCFrame()
{
float xc = X * 2f;
float yc = Y * 2f;
float zc = Z * 2f;
float xx = X * xc;
float xy = X * yc;
float xz = X * zc;
float wx = W * xc;
float wy = W * yc;
float wz = W * zc;
float yy = Y * yc;
float yz = Y * zc;
float zz = Z * zc;
return new CFrame
(
0, 0, 0,
1f - (yy + zz),
xy - wz,
xz + wy,
xy + wz,
1f - (xx + zz),
yz - wx,
xz - wy,
yz + wx,
1f - (xx + yy)
);
}
public static Quaternion operator +(Quaternion a, Quaternion b)
{
return new Quaternion(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W);
}
public static Quaternion operator -(Quaternion a, Quaternion b)
{
return new Quaternion(a.X - b.X, a.Y - b.Y, a.Z - b.Z, a.W - b.W);
}
public static Quaternion operator *(Quaternion a, float f)
{
return new Quaternion(a.X * f, a.Y * f, a.Z * f, a.W * f);
}
public static Quaternion operator /(Quaternion a, float f)
{
return new Quaternion(a.X / f, a.Y / f, a.Z / f, a.W / f);
}
public static Quaternion operator -(Quaternion a)
{
return new Quaternion(-a.X, -a.Y, -a.Z, -a.W);
}
public static Quaternion operator *(Quaternion a, Quaternion b)
{
Vector3 v1 = new Vector3(a.X, a.Y, a.Z);
float s1 = a.W;
Vector3 v2 = new Vector3(b.X, b.Y, b.Z);
float s2 = b.W;
return new Quaternion(s1 * v2 + s2 * v1 + v1.Cross(v2), s1 * s2 - v1.Dot(v2));
}
}
}

View File

@ -20,10 +20,10 @@
}
}
public Ray(Vector3 origin, Vector3 direction)
public Ray(Vector3 origin = null, Vector3 direction = null)
{
Origin = origin;
Direction = direction;
Origin = origin ?? new Vector3();
Direction = direction ?? new Vector3();
}
public override string ToString()

62
DataTypes/SharedString.cs Normal file
View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
namespace RobloxFiles.DataTypes
{
public class SharedString
{
private static Dictionary<string, byte[]> Records = new Dictionary<string, byte[]>();
public readonly string MD5_Key;
public byte[] SharedValue => FindRecord(MD5_Key);
public override string ToString() => $"MD5 Key: {MD5_Key}";
internal SharedString(string md5)
{
MD5_Key = md5;
}
private SharedString(byte[] buffer)
{
using (MD5 md5 = MD5.Create())
{
byte[] hash = md5.ComputeHash(buffer);
MD5_Key = Convert.ToBase64String(hash);
}
if (Records.ContainsKey(MD5_Key))
return;
Records.Add(MD5_Key, buffer);
}
public static byte[] FindRecord(string key)
{
byte[] result = null;
if (Records.ContainsKey(key))
result = Records[key];
return result;
}
public static SharedString FromBuffer(byte[] buffer)
{
return new SharedString(buffer);
}
public static SharedString FromString(string value)
{
byte[] buffer = Encoding.UTF8.GetBytes(value);
return new SharedString(buffer);
}
public static SharedString FromBase64(string base64)
{
byte[] buffer = Convert.FromBase64String(base64);
return new SharedString(buffer);
}
}
}

View File

@ -28,7 +28,7 @@ namespace RobloxFiles.DataTypes
Y = y;
}
public Vector2(float[] coords)
internal Vector2(float[] coords)
{
X = coords.Length > 0 ? coords[0] : 0;
Y = coords.Length > 1 ? coords[1] : 0;
@ -69,6 +69,11 @@ namespace RobloxFiles.DataTypes
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)
{
return new Vector2(-v.X, -v.Y);
}
public static Vector2 Zero => new Vector2(0, 0);
public override string ToString()

View File

@ -30,7 +30,7 @@ namespace RobloxFiles.DataTypes
Z = z;
}
public Vector3(float[] coords)
internal Vector3(float[] coords)
{
X = coords.Length > 0 ? coords[0] : 0;
Y = coords.Length > 1 ? coords[1] : 0;
@ -92,6 +92,11 @@ namespace RobloxFiles.DataTypes
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);
@ -141,7 +146,7 @@ namespace RobloxFiles.DataTypes
float dotProd = normal.Dot(this);
if (Math.Abs(dotProd - 1f) < 10e-5f)
if (dotProd.FuzzyEquals(1))
{
result = i;
break;