0.522.0.5220281 (+ Font Type Support & Bug Fixes)

This commit is contained in:
Max 2022-04-14 20:52:05 -05:00
parent cff4b1ad5c
commit f8c914dd0b
21 changed files with 3577 additions and 2631 deletions

View File

@ -662,6 +662,23 @@ namespace RobloxFiles.BinaryFormat.Chunks
break; break;
} }
case PropertyType.FontFace:
{
readProperties(i =>
{
string family = reader.ReadString();
if (family.EndsWith(".otf") || family.EndsWith(".ttf"))
return new FontFace(family);
var weight = (FontWeight)reader.ReadUInt16();
var style = (FontStyle)reader.ReadByte();
return new FontFace(family, weight, style);
});
break;
}
default: default:
{ {
RobloxFile.LogError($"Unhandled property type: {Type} in {this}!"); RobloxFile.LogError($"Unhandled property type: {Type} in {this}!");
@ -1283,6 +1300,27 @@ namespace RobloxFiles.BinaryFormat.Chunks
break; break;
} }
case PropertyType.FontFace:
{
props.ForEach(prop =>
{
var font = prop.CastValue<FontFace>();
string family = font.Family;
writer.WriteString(font.Family);
if (family.EndsWith(".otf") || family.EndsWith(".ttf"))
return;
var weight = (ushort)font.Weight;
writer.Write(weight);
var style = (byte)font.Style;
writer.Write(style);
});
break;
}
default: default:
{ {
RobloxFile.LogError($"Unhandled property type: {Type} in {this}!"); RobloxFile.LogError($"Unhandled property type: {Type} in {this}!");

View File

@ -1,12 +1,11 @@
using System; using System;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using RobloxFiles.Enums;
namespace RobloxFiles.DataTypes namespace RobloxFiles.DataTypes
{ {
public class CFrame public class CFrame
{ {
private float m14, m24, m34; private readonly float m14, m24, m34;
private readonly float m11 = 1, m12, m13; private readonly float m11 = 1, m12, m13;
private readonly float m21, m22 = 1, m23; private readonly float m21, m22 = 1, m23;
@ -18,28 +17,23 @@ namespace RobloxFiles.DataTypes
public float Y => m24; public float Y => m24;
public float Z => m34; public float Z => m34;
public Vector3 Position public Vector3 Position => new Vector3(m41, m42, m43);
{ public CFrame Rotation => (this - Position);
get => new Vector3(X, Y, Z);
set public Vector3 XVector => new Vector3(m11, m21, m31);
{ public Vector3 YVector => new Vector3(m12, m22, m32);
Contract.Requires(value != null); public Vector3 ZVector => new Vector3(m13, m23, m33);
m14 = value.X; public Vector3 RightVector => XVector;
m24 = value.Y; public Vector3 UpVector => YVector;
m34 = value.Z; public Vector3 LookVector => -ZVector;
}
}
public Vector3 RightVector => new Vector3( m11, m21, m31);
public Vector3 UpVector => new Vector3( m12, m22, m32);
public Vector3 LookVector => new Vector3(-m13, -m23, -m33);
public Vector3 ColumnX => new Vector3(m11, m12, m13); public Vector3 ColumnX => new Vector3(m11, m12, m13);
public Vector3 ColumnY => new Vector3(m21, m22, m23); public Vector3 ColumnY => new Vector3(m21, m22, m23);
public Vector3 ColumnZ => new Vector3(m31, m32, m33); public Vector3 ColumnZ => new Vector3(m31, m32, m33);
public static readonly CFrame Identity = new CFrame();
public override int GetHashCode() public override int GetHashCode()
{ {
var components = GetComponents(); var components = GetComponents();
@ -96,23 +90,20 @@ namespace RobloxFiles.DataTypes
public CFrame(Vector3 eye, Vector3 look) public CFrame(Vector3 eye, Vector3 look)
{ {
Vector3 zAxis = (eye - look).Unit; Vector3 zAxis = (eye - look).Unit,
Vector3 xAxis = Vector3.Up.Cross(zAxis); xAxis = Vector3.Up.Cross(zAxis),
Vector3 yAxis = zAxis.Cross(xAxis); yAxis = zAxis.Cross(xAxis);
if (xAxis.Magnitude == 0) if (xAxis.Magnitude == 0)
{ {
xAxis = Vector3.z;
yAxis = Vector3.x;
zAxis = Vector3.y;
if (zAxis.Y < 0) if (zAxis.Y < 0)
{ {
xAxis = new Vector3(0, 0, -1); xAxis = -xAxis;
yAxis = new Vector3(1, 0, 0); zAxis = -zAxis;
zAxis = new Vector3(0, -1, 0);
}
else
{
xAxis = new Vector3(0, 0, 1);
yAxis = new Vector3(1, 0, 0);
zAxis = new Vector3(0, 1, 0);
} }
} }
@ -123,9 +114,9 @@ namespace RobloxFiles.DataTypes
public CFrame(float nx, float ny, float nz, float i, float j, float k, float w) public CFrame(float nx, float ny, float nz, float i, float j, float k, float w)
{ {
float ii = i * i; float ii = i * i,
float jj = j * j; jj = j * j,
float kk = k * k; kk = k * k;
m14 = nx; m14 = nx;
m24 = ny; m24 = ny;
@ -308,18 +299,18 @@ namespace RobloxFiles.DataTypes
public static CFrame FromAxisAngle(Vector3 axis, float theta) public static CFrame FromAxisAngle(Vector3 axis, float theta)
{ {
Vector3 u = VectorAxisAngle(axis, Vector3.Up, theta); Vector3 r = VectorAxisAngle(axis, Vector3.x, theta),
Vector3 b = VectorAxisAngle(axis, Vector3.Back, theta); u = VectorAxisAngle(axis, Vector3.y, theta),
Vector3 r = VectorAxisAngle(axis, Vector3.Right, theta); b = VectorAxisAngle(axis, Vector3.z, theta);
return new CFrame(0, 0, 0, r.X, u.X, b.X, r.Y, u.Y, b.Y, r.Z, u.Z, b.Z); return new CFrame(0, 0, 0, r.X, u.X, b.X, r.Y, u.Y, b.Y, r.Z, u.Z, b.Z);
} }
public static CFrame FromEulerAnglesXYZ(float x, float y, float z) public static CFrame FromEulerAnglesXYZ(float x, float y, float z)
{ {
CFrame cfx = FromAxisAngle(Vector3.Right, x), CFrame cfx = FromAxisAngle(Vector3.x, x),
cfy = FromAxisAngle(Vector3.Up, y), cfy = FromAxisAngle(Vector3.y, y),
cfz = FromAxisAngle(Vector3.Back, z); cfz = FromAxisAngle(Vector3.z, z);
return cfx * cfy * cfz; return cfx * cfy * cfz;
} }
@ -386,7 +377,13 @@ namespace RobloxFiles.DataTypes
public float[] GetComponents() public float[] GetComponents()
{ {
return new float[] { m14, m24, m34, m11, m12, m13, m21, m22, m23, m31, m32, m33 }; return new float[]
{
m14, m24, m34,
m11, m12, m13,
m21, m22, m23,
m31, m32, m33
};
} }
public EulerAngles ToEulerAngles() => new EulerAngles public EulerAngles ToEulerAngles() => new EulerAngles
@ -411,28 +408,27 @@ namespace RobloxFiles.DataTypes
public bool IsAxisAligned() public bool IsAxisAligned()
{ {
float[] matrix = GetComponents(); var tests = new float[3]
byte sum0 = 0,
sum1 = 0;
for (int i = 3; i < 12; i++)
{ {
float t = Math.Abs(matrix[i]); XVector.Dot(Vector3.x),
YVector.Dot(Vector3.y),
ZVector.Dot(Vector3.z)
};
if (t.FuzzyEquals(1, 1e-8f)) foreach (var test in tests)
{ {
// Approximately ±1 float dot = Math.Abs(test);
sum1++;
} if (dot.FuzzyEquals(1))
else if (t.FuzzyEquals(0, 1e-8f)) continue;
{
// Approximately ±0 if (dot.FuzzyEquals(0))
sum0++; continue;
}
return false;
} }
return (sum0 == 6 && sum1 == 3); return true;
} }
private static bool IsLegalOrientId(int orientId) private static bool IsLegalOrientId(int orientId)
@ -450,7 +446,6 @@ namespace RobloxFiles.DataTypes
int xNormal = ColumnX.ToNormalId(); int xNormal = ColumnX.ToNormalId();
int yNormal = ColumnY.ToNormalId(); int yNormal = ColumnY.ToNormalId();
int orientId = (6 * xNormal) + yNormal; int orientId = (6 * xNormal) + yNormal;
if (!IsLegalOrientId(orientId)) if (!IsLegalOrientId(orientId))

52
DataTypes/FontFace.cs Normal file
View File

@ -0,0 +1,52 @@
using RobloxFiles.Enums;
namespace RobloxFiles.DataTypes
{
// Implementation of Roblox's Font datatype.
// Renamed to FontFace to avoid disambiguation with System.Font and the Font enum.
public class FontFace
{
public readonly Content Family = "rbxasset://fonts/families/LegacyArial.json";
public readonly FontWeight Weight = FontWeight.Regular;
public readonly FontStyle Style = FontStyle.Normal;
public FontFace(Content family, FontWeight weight = FontWeight.Regular, FontStyle style = FontStyle.Normal)
{
Family = family;
Weight = weight;
Style = style;
}
public override string ToString()
{
return $"Font {{ Family = {Family}, Weight = {Weight}, Style = {Style}}}";
}
public override int GetHashCode()
{
int hash = Family.GetHashCode()
^ Weight.GetHashCode()
^ Style.GetHashCode();
return hash;
}
public override bool Equals(object obj)
{
if (!(obj is FontFace font))
return false;
if (Family != font.Family)
return false;
if (Weight != font.Weight)
return false;
if (Style != font.Style)
return false;
return true;
}
}
}

View File

@ -12,8 +12,8 @@
public Rect(Vector2 min = null, Vector2 max = null) public Rect(Vector2 min = null, Vector2 max = null)
{ {
Min = min ?? Vector2.Zero; Min = min ?? Vector2.zero;
Max = max ?? Vector2.Zero; Max = max ?? Vector2.zero;
} }
public Rect(float minX, float minY, float maxX, float maxY) public Rect(float minX, float minY, float maxX, float maxY)

View File

@ -1,28 +1,14 @@
using System; #pragma warning disable IDE1006 // Naming Styles
using System;
namespace RobloxFiles.DataTypes namespace RobloxFiles.DataTypes
{ {
public class Vector2 public class Vector2
{ {
public readonly float X, Y; public readonly float X, Y;
public override string ToString() => $"{X}, {Y}"; public override string ToString() => $"{X}, {Y}";
public float Magnitude
{
get
{
float product = Dot(this);
double magnitude = Math.Sqrt(product);
return (float)magnitude;
}
}
public Vector2 Unit
{
get { return this / Magnitude; }
}
public Vector2(float x = 0, float y = 0) public Vector2(float x = 0, float y = 0)
{ {
X = x; X = x;
@ -35,17 +21,20 @@ namespace RobloxFiles.DataTypes
Y = coords.Length > 1 ? coords[1] : 0; Y = coords.Length > 1 ? coords[1] : 0;
} }
public float Magnitude => (float)Math.Sqrt(X*X + Y*Y);
public Vector2 Unit => this / Magnitude;
private delegate Vector2 Operator(Vector2 a, Vector2 b); private delegate Vector2 Operator(Vector2 a, Vector2 b);
private static Vector2 UpcastFloatOp(Vector2 vec, float num, Operator upcast) private static Vector2 UpcastFloatOp(Vector2 vec, float num, Operator upcast)
{ {
Vector2 numVec = new Vector2(num, num); var numVec = new Vector2(num, num);
return upcast(vec, numVec); return upcast(vec, numVec);
} }
private static Vector2 UpcastFloatOp(float num, Vector2 vec, Operator upcast) private static Vector2 UpcastFloatOp(float num, Vector2 vec, Operator upcast)
{ {
Vector2 numVec = new Vector2(num, num); var numVec = new Vector2(num, num);
return upcast(numVec, vec); return upcast(numVec, vec);
} }
@ -70,28 +59,16 @@ namespace RobloxFiles.DataTypes
public static Vector2 operator /(Vector2 v, float n) => UpcastFloatOp(v, n, div); 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 /(float n, Vector2 v) => UpcastFloatOp(n, v, div);
public static Vector2 operator -(Vector2 v) public static Vector2 operator -(Vector2 v) => new Vector2(-v.X, -v.Y);
{
return new Vector2(-v.X, -v.Y);
}
public static Vector2 Zero => new Vector2(0, 0); public static Vector2 zero => new Vector2(0, 0);
public static Vector2 one => new Vector2(1, 1);
public float Dot(Vector2 other) public static Vector2 x => new Vector2(1, 0);
{ public static Vector2 y => new Vector2(0, 1);
float dotX = X * other.X;
float dotY = Y * other.Y;
return dotX + dotY; public float Dot(Vector2 other) => (X * other.X) + (Y * other.Y);
} public Vector2 Cross(Vector2 other) => new Vector2(X * other.Y, Y * other.X);
public Vector2 Cross(Vector2 other)
{
float crossX = X * other.Y;
float crossY = Y * other.X;
return new Vector2(crossX, crossY);
}
public Vector2 Lerp(Vector2 other, float t) public Vector2 Lerp(Vector2 other, float t)
{ {

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Text;
using RobloxFiles.Enums; using RobloxFiles.Enums;
namespace RobloxFiles.DataTypes namespace RobloxFiles.DataTypes
@ -73,10 +72,10 @@ namespace RobloxFiles.DataTypes
return upcast(numVec, vec); return upcast(numVec, vec);
} }
private static readonly Operator add = new Operator((a, b) => new Vector3(a.X + b.X, a.Y + b.Y, a.Z + b.Z)); private static readonly Operator add = (a, b) => new Vector3(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
private static readonly Operator sub = new Operator((a, b) => new Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z)); private static readonly Operator sub = (a, b) => new Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
private static readonly Operator mul = new Operator((a, b) => new Vector3(a.X * b.X, a.Y * b.Y, a.Z * b.Z)); private static readonly Operator mul = (a, b) => new Vector3(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
private static readonly Operator div = new Operator((a, b) => new Vector3(a.X / b.X, a.Y / b.Y, a.Z / b.Z)); private static readonly Operator div = (a, b) => new Vector3(a.X / b.X, a.Y / b.Y, a.Z / b.Z);
public static Vector3 operator +(Vector3 a, Vector3 b) => add(a, b); public static Vector3 operator +(Vector3 a, Vector3 b) => add(a, b);
public static Vector3 operator +(Vector3 v, float n) => UpcastFloatOp(v, n, add); public static Vector3 operator +(Vector3 v, float n) => UpcastFloatOp(v, n, add);
@ -94,15 +93,18 @@ namespace RobloxFiles.DataTypes
public static Vector3 operator /(Vector3 v, float n) => UpcastFloatOp(v, n, div); 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 /(float n, Vector3 v) => UpcastFloatOp(n, v, div);
public static Vector3 operator -(Vector3 v) public static Vector3 operator -(Vector3 v) => new Vector3(-v.X, -v.Y, -v.Z);
{
return new Vector3(-v.X, -v.Y, -v.Z);
}
public static readonly Vector3 Zero = new Vector3(0, 0, 0); public static readonly Vector3 zero = new Vector3(0, 0, 0);
public static readonly Vector3 Right = new Vector3(1, 0, 0); public static readonly Vector3 one = new Vector3(1, 1, 1);
public static readonly Vector3 Up = new Vector3(0, 1, 0);
public static readonly Vector3 Back = new Vector3(0, 0, 1); public static readonly Vector3 x = new Vector3(1, 0, 0);
public static readonly Vector3 y = new Vector3(0, 1, 0);
public static readonly Vector3 z = new Vector3(0, 0, 1);
public static Vector3 Right => x;
public static Vector3 Up => y;
public static Vector3 Back => z;
public float Dot(Vector3 other) public float Dot(Vector3 other)
{ {

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
// Auto-generated list of Roblox enums. // Auto-generated list of Roblox enums.
// Updated as of 0.514.0.5140398 // Updated as of 0.522.0.5220281
namespace RobloxFiles.Enums namespace RobloxFiles.Enums
{ {
@ -332,7 +332,8 @@ namespace RobloxFiles.Enums
Sarpanch, Sarpanch,
SpecialElite, SpecialElite,
TitilliumWeb, TitilliumWeb,
Ubuntu Ubuntu,
Unknown = 100
} }
public enum FontSize public enum FontSize
@ -354,6 +355,25 @@ namespace RobloxFiles.Enums
Size96 Size96
} }
public enum FontStyle
{
Normal,
Italic
}
public enum FontWeight
{
Thin = 100,
ExtraLight = 200,
Light = 300,
Regular = 400,
Medium = 500,
SemiBold = 600,
Bold = 700,
ExtraBold = 800,
Heavy = 900
}
public enum FormFactor public enum FormFactor
{ {
Symmetric, Symmetric,
@ -1061,7 +1081,8 @@ namespace RobloxFiles.Enums
{ {
Default, Default,
Immediate, Immediate,
Deferred Deferred,
AncestryDeferred
} }
public enum SizeConstraint public enum SizeConstraint
@ -1158,6 +1179,13 @@ namespace RobloxFiles.Enums
Other Other
} }
public enum TerrainFace
{
Top,
Side,
Bottom
}
public enum TextTruncate public enum TextTruncate
{ {
None, None,

View File

@ -6,7 +6,7 @@ namespace RobloxFiles.Tokens
{ {
string XmlPropertyToken { get; } string XmlPropertyToken { get; }
bool ReadProperty(Property prop, XmlNode token); bool ReadProperty(Property prop, XmlNode node);
void WriteProperty(Property prop, XmlDocument doc, XmlNode node); void WriteProperty(Property prop, XmlDocument doc, XmlNode node);
} }
} }

Binary file not shown.

View File

@ -1,43 +1,67 @@
--!strict
local Format = {} local Format = {}
function Format.Null(value) type IFlags = { [string]: boolean }
type IEnum = { GetEnumItems: (IEnum) -> {EnumItem} }
type IAxes =
{
X: boolean;
Y: boolean;
Z: boolean;
}
type IFaces =
{
Left: boolean;
Right: boolean;
Top: boolean;
Bottom: boolean;
Front: boolean;
Back: boolean;
}
function Format.Null(value: any): nil
return nil return nil
end end
function Format.Bytes(value) function Format.Bytes(value: string): string
if #value > 0 then if #value > 0 then
local fmt = "Convert.FromBase64String(%q)" return string.format("Convert.FromBase64String(%q)", value)
return fmt:format(value)
else
return "Array.Empty<byte>()"
end end
return "Array.Empty<byte>()"
end end
function Format.Bool(value) function Format.Bool(value: boolean?): string?
if value then if value then
return "true" return "true"
end end
return nil
end end
function Format.String(value) function Format.String(value: string): string
return string.format("%q", value) return string.format("%q", value)
end end
function Format.Int(value, default) function Format.Int(value: number): string
if value == 2147483647 then if value == 2^31-1 then
return "int.MaxValue" return "int.MaxValue"
elseif value == -2147483648 then elseif value == -2^31 then
return "int.MinValue" return "int.MinValue"
elseif value ~= 0 or default then else
return string.format("%i", value) return string.format("%i", value)
end end
end end
function Format.Number(value, default) function Format.Number(value: number): string
local int = math.floor(value) local int = math.floor(value)
if math.abs(value - int) < 0.001 then if math.abs(value - int) < 0.001 then
return Format.Int(int, default) return Format.Int(int)
end end
local result = string.format("%.5f", value) local result = string.format("%.5f", value)
@ -46,12 +70,10 @@ function Format.Number(value, default)
return result return result
end end
function Format.Double(value, default) function Format.Double(value: number): string
local result = Format.Number(value, default) local result = Format.Number(value)
if not result then if result == "inf" then
return nil
elseif result == "inf" then
return "double.MaxValue" return "double.MaxValue"
elseif result == "-inf" then elseif result == "-inf" then
return "double.MinValue" return "double.MinValue"
@ -60,12 +82,10 @@ function Format.Double(value, default)
end end
end end
function Format.Float(value, default) function Format.Float(value: number): string
local result = Format.Number(value, default) local result = Format.Number(value)
if not result then if result == "inf" then
return nil
elseif result == "inf" then
return "float.MaxValue" return "float.MaxValue"
elseif result == "-inf" then elseif result == "-inf" then
return "float.MinValue" return "float.MinValue"
@ -78,44 +98,44 @@ function Format.Float(value, default)
end end
end end
function Format.Flags(flag, enum) function Format.Flags(flags: IFlags, enum: IEnum): string
local value = 0 local value = 0
for _,item in pairs(enum:GetEnumItems()) do for _,item in pairs(enum:GetEnumItems()) do
if flag[item.Name] then if flags[item.Name] then
value += (2 ^ item.Value) value += (2 ^ item.Value)
end end
end end
return value return tostring(value)
end end
function Format.Axes(axes) function Format.Axes(axes: IAxes): string
return "(Axes)" .. Format.Flags(axes, Enum.Axis) return "(Axes)" .. Format.Flags(axes, Enum.Axis)
end end
function Format.Faces(faces) function Format.Faces(faces: IFaces): string
return "(Faces)" .. Format.Flags(faces, Enum.NormalId) return "(Faces)" .. Format.Flags(faces, Enum.NormalId)
end end
function Format.EnumItem(item) function Format.EnumItem(item: EnumItem): string
local enum = tostring(item.EnumType) local enum = tostring(item.EnumType)
return enum .. '.' .. item.Name return enum .. '.' .. item.Name
end end
function Format.BrickColor(brickColor) function Format.BrickColor(brickColor: BrickColor): string
local fmt = "BrickColor.FromNumber(%i)" local fmt = "BrickColor.FromNumber(%i)"
return fmt:format(brickColor.Number) return fmt:format(brickColor.Number)
end end
function Format.Color3(color) function Format.Color3(color: Color3): string
if color == Color3.new() then if color == Color3.new() then
return "new Color3()" return "new Color3()"
end end
local r = Format.Float(color.R, true) local r = Format.Float(color.R)
local g = Format.Float(color.G, true) local g = Format.Float(color.G)
local b = Format.Float(color.B, true) local b = Format.Float(color.B)
local fmt = "%s(%s, %s, %s)"; local fmt = "%s(%s, %s, %s)";
local constructor = "new Color3"; local constructor = "new Color3";
@ -131,159 +151,176 @@ function Format.Color3(color)
return fmt:format(constructor, r, g, b) return fmt:format(constructor, r, g, b)
end end
function Format.UDim(udim) function Format.UDim(udim: UDim): string
if udim == UDim.new() then if udim == UDim.new() then
return "new UDim()" return "new UDim()"
end end
local scale = Format.Float(udim.Scale, true) local scale = Format.Float(udim.Scale)
local offset = Format.Int(udim.Offset, true) local offset = Format.Int(udim.Offset)
local fmt = "new UDim(%s, %s)" local fmt = "new UDim(%s, %s)"
return fmt:format(scale, offset) return fmt:format(scale, offset)
end end
function Format.UDim2(udim2) function Format.UDim2(udim2: UDim2): string
if udim2 == UDim2.new() then if udim2 == UDim2.new() then
return "new UDim2()" return "new UDim2()"
end end
local xScale = Format.Float(udim2.X.Scale, true) local xScale = Format.Float(udim2.X.Scale)
local yScale = Format.Float(udim2.Y.Scale, true) local yScale = Format.Float(udim2.Y.Scale)
local xOffset = Format.Int(udim2.X.Offset, true) local xOffset = Format.Int(udim2.X.Offset)
local yOffset = Format.Int(udim2.Y.Offset, true) local yOffset = Format.Int(udim2.Y.Offset)
local fmt = "new UDim2(%s, %s, %s, %s)" local fmt = "new UDim2(%s, %s, %s, %s)"
return fmt:format(xScale, xOffset, yScale, yOffset) return fmt:format(xScale, xOffset, yScale, yOffset)
end end
function Format.Vector2(v2) function Format.Vector2(v2: Vector2): string
if v2.Magnitude < 0.001 then if v2.Magnitude < 0.001 then
return "new Vector2()" return "new Vector2()"
end end
local x = Format.Float(v2.X, true) local x = Format.Float(v2.X)
local y = Format.Float(v2.Y, true) local y = Format.Float(v2.Y)
local fmt = "new Vector2(%s, %s)" local fmt = "new Vector2(%s, %s)"
return fmt:format(x, y) return fmt:format(x, y)
end end
function Format.Vector3(v3) function Format.Vector3(v3: Vector3): string
if v3.Magnitude < 0.001 then if v3.Magnitude < 0.001 then
return "new Vector3()" return "new Vector3()"
end end
local x = Format.Float(v3.X, true) local x = Format.Float(v3.X)
local y = Format.Float(v3.Y, true) local y = Format.Float(v3.Y)
local z = Format.Float(v3.Z, true) local z = Format.Float(v3.Z)
local fmt = "new Vector3(%s, %s, %s)" local fmt = "new Vector3(%s, %s, %s)"
return fmt:format(x, y, z) return fmt:format(x, y, z)
end end
function Format.CFrame(cf) function Format.CFrame(cf: CFrame): string
local blankCF = CFrame.new() if cf == CFrame.identity then
if cf == blankCF then
return "new CFrame()" return "new CFrame()"
end end
local rot = cf - cf.p if cf.Rotation == CFrame.identity then
local x = Format.Float(cf.X)
local y = Format.Float(cf.Y)
local z = Format.Float(cf.Z)
if rot == blankCF then return string.format("new CFrame(%s, %s, %s)", x, y, z)
local fmt = "new CFrame(%s, %s, %s)"
local x = Format.Float(cf.X, true)
local y = Format.Float(cf.Y, true)
local z = Format.Float(cf.Z, true)
return fmt:format(x, y, z)
else else
local comp = { cf:GetComponents() } local comp = { cf:GetComponents() }
local matrix = ""
for i = 1,12 do for i = 1, 12 do
comp[i] = Format.Float(comp[i], true) local sep = (if i > 1 then ", " else "")
matrix ..= sep .. Format.Float(comp[i])
end end
local fmt = "new CFrame(%s)" return string.format("new CFrame(%s)", matrix)
local matrix = table.concat(comp, ", ")
return fmt:format(matrix)
end end
end end
function Format.NumberRange(nr) function Format.NumberRange(nr: NumberRange): string
local min = nr.Min local min = nr.Min
local max = nr.Max local max = nr.Max
local fmt = "new NumberRange(%s)" local fmt = "new NumberRange(%s)"
local value = Format.Float(min, true) local value = Format.Float(min)
if min ~= max then if min ~= max then
value = value .. ", " .. Format.Float(max, true) value = value .. ", " .. Format.Float(max)
end end
return fmt:format(value) return fmt:format(value)
end end
function Format.Ray(ray) function Format.Ray(ray: Ray): string
if ray == Ray.new() then if ray == Ray.new() then
return "new Ray()" return "new Ray()"
end end
local fmt = "new Ray(%s, %s)"
local origin = Format.Vector3(ray.Origin) local origin = Format.Vector3(ray.Origin)
local direction = Format.Vector3(ray.Direction) local direction = Format.Vector3(ray.Direction)
local fmt = "new Ray(%s, %s)"
return fmt:format(origin, direction) return fmt:format(origin, direction)
end end
function Format.Rect(rect) function Format.Rect(rect: Rect): string
local min: any = rect.Min
local max: any = rect.Max
if min == max and min == Vector2.zero then
return "new Rect()"
end
min = Format.Vector2(min)
max = Format.Vector2(max)
local fmt = "new Rect(%s, %s)" local fmt = "new Rect(%s, %s)"
local min = Format.Vector2(rect.Min)
local max = Format.Vector2(rect.Max)
return fmt:format(min, max) return fmt:format(min, max)
end end
function Format.ColorSequence(cs) function Format.ColorSequence(cs: ColorSequence): string
local csKey = cs.Keypoints[1] local csKey = cs.Keypoints[1]
local fmt = "new ColorSequence(%s)"
local value = tostring(csKey.Value) local value = tostring(csKey.Value)
local fmt = "new ColorSequence(%s)"
return fmt:format(value) return fmt:format(value)
end end
function Format.NumberSequence(ns) function Format.NumberSequence(ns: NumberSequence): string
local nsKey = ns.Keypoints[1] local nsKey = ns.Keypoints[1]
local fmt = "new NumberSequence(%s)" local fmt = "new NumberSequence(%s)"
local value = Format.Float(nsKey.Value, true)
local value = Format.Float(nsKey.Value)
return fmt:format(value) return fmt:format(value)
end end
function Format.Vector3int16(v3) function Format.Vector3int16(v3: Vector3int16): string
if v3 == Vector3int16.new() then if v3 == Vector3int16.new() then
return "new Vector3int16()" return "new Vector3int16()"
end end
local x = Format.Int(v3.X, true) local x = Format.Int(v3.X)
local y = Format.Int(v3.Y, true) local y = Format.Int(v3.Y)
local z = Format.Int(v3.Z, true) local z = Format.Int(v3.Z)
local fmt = "new Vector3int16(%s, %s, %s)" local fmt = "new Vector3int16(%s, %s, %s)"
return fmt:format(x, y, z) return fmt:format(x, y, z)
end end
function Format.SharedString(str) function Format.SharedString(str: string): string
local fmt = "SharedString.FromBase64(%q)" local fmt = "SharedString.FromBase64(%q)"
return fmt:format(str) return fmt:format(str)
end end
function Format.FontFace(font: Font): string
local family = string.format("%q", font.Family)
local args = { family }
local style = font.Style
local weight = font.Weight
if style ~= Enum.FontStyle.Normal then
table.insert(args, "FontStyle." .. style.Name)
end
if #args > 1 or weight ~= Enum.FontWeight.Regular then
table.insert(args, "FontWeight." .. weight.Name)
end
local fmt = "new FontFace(%s)"
local argStr = table.concat(args, ", ")
return fmt:format(argStr)
end
return Format return Format

View File

@ -63,6 +63,29 @@ local GuiTextMixIn =
return return
{ {
AnimationRigData =
{
Add =
{
label = "BinaryString";
name = "BinaryString";
parent = "BinaryString";
postTransform = "BinaryString";
preTransform = "BinaryString";
transform = "BinaryString";
};
Defaults =
{
label = "AQAAAAEAAAAAAAAA";
name = "AQAAAAEAAAAAAAAA";
parent = "AQAAAAEAAAAAAA==";
postTransform = "AQAAAAEAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAA=";
preTransform = "AQAAAAEAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAA=";
transform = "AQAAAAEAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAA=";
}
};
BallSocketConstraint = BallSocketConstraint =
{ {
-- Why does this even exist? -- Why does this even exist?
@ -73,13 +96,21 @@ return
{ {
Add = Add =
{ {
MaterialVariantSerialized = "string";
MaterialVariant = "string";
Color3uint8 = "Color3uint8"; Color3uint8 = "Color3uint8";
size = "Vector3"; size = "Vector3";
}; };
Redirect = Redirect =
{ {
Position = "CFrame.Position"; Position =
{
Get = "CFrame.Position";
Set = "CFrame = new CFrame(value) * CFrame.Rotation";
};
MaterialVariant = "MaterialVariantSerialized";
BrickColor = UseColor3("Color"); BrickColor = UseColor3("Color");
Color = "Color3uint8"; Color = "Color3uint8";
Size = "size"; Size = "size";
@ -88,6 +119,7 @@ return
Defaults = Defaults =
{ {
Color3uint8 = Color3.fromRGB(163, 162, 165); Color3uint8 = Color3.fromRGB(163, 162, 165);
MaterialVariantSerialized = "";
size = Vector3.new(4, 1.2, 2); size = Vector3.new(4, 1.2, 2);
}; };
}; };
@ -201,6 +233,12 @@ return
}; };
}; };
FloatCurve =
{
Add = { ValuesAndTimes = "BinaryString" };
Defaults = { ValuesAndTimes = "AAAAAAEAAAAKAAAAAAAAFkUAAAAA" };
};
FormFactorPart = FormFactorPart =
{ {
Add = Add =
@ -245,6 +283,15 @@ return
} }
}; };
HiddenSurfaceRemovalAsset =
{
Add =
{
HSRData = "BinaryString";
HSRMeshIdData = "BinaryString";
}
};
HttpService = HttpService =
{ {
Defaults = { HttpEnabled = false } Defaults = { HttpEnabled = false }
@ -356,6 +403,60 @@ return
}; };
}; };
MarkerCurve =
{
Add = { ValuesAndTimes = "BinaryString" };
Defaults = { ValuesAndTimes = "AAAAAAEAAAAKAAAAAAAAFkUAAAAA" };
};
MaterialService =
{
Defaults =
{
AsphaltName = "Asphalt";
BasaltName = "Basalt";
BrickName = "Brick";
CobblestoneName = "Cobblestone";
ConcreteName = "Concrete";
CorrodedMetalName = "CorrodedMetal";
CrackedLavaName = "CrackedLava";
DiamondPlateName = "DiamondPlate";
FabricName = "Fabric";
FoilName = "Foil";
GlacierName = "Glacier";
GraniteName = "Granite";
GrassName = "Grass";
GroundName = "Ground";
IceName = "Ice";
LeafyGrassName = "LeafyGrass";
LimestoneName = "Limestone";
MarbleName = "Marble";
MetalName = "Metal";
MudName = "Mud";
PavementName = "Pavement";
PebbleName = "Pebble";
PlasticName = "Plastic";
RockName = "Rock";
SaltName = "Salt";
SandName = "Sand";
SandstoneName = "Sandstone";
SlateName = "Slate";
SmoothPlasticName = "SmoothPlastic";
SnowName = "Snow";
WoodName = "Wood";
WoodPlanksName = "WoodPlanks";
}
};
MaterialVariant =
{
Add =
{
TexturePack0 = "Content";
TexturePack1 = "Content";
}
};
MeshPart = MeshPart =
{ {
Redirect = { MeshID = "MeshId" } Redirect = { MeshID = "MeshId" }
@ -458,6 +559,12 @@ return
}; };
}; };
RotationCurve =
{
Add = { ValuesAndTimes = "BinaryString" };
Defaults = { ValuesAndTimes = "AAAAAAEAAAAKAAAAAAAAFkUAAAAA" };
};
SelectionBox = SelectionBox =
{ {
Redirect = { SurfaceColor = UseColor3("SurfaceColor3") } Redirect = { SurfaceColor = UseColor3("SurfaceColor3") }
@ -605,6 +712,11 @@ return
}; };
}; };
TerrainDetail =
{
Add = { TexturePack1 = "Content"; }
};
TerrainRegion = TerrainRegion =
{ {
Add = Add =

View File

@ -2,6 +2,7 @@ local Selection = game:GetService("Selection")
local HttpService = game:GetService("HttpService") local HttpService = game:GetService("HttpService")
local StarterPlayer = game:GetService("StarterPlayer") local StarterPlayer = game:GetService("StarterPlayer")
local StudioService = game:GetService("StudioService") local StudioService = game:GetService("StudioService")
local TextChatService = game:GetService("TextChatService")
local classes = {} local classes = {}
local outStream = "" local outStream = ""
@ -14,12 +15,16 @@ local singletons =
ParabolaAdornment = Instance.new("BoxHandleAdornment"); -- close enough ParabolaAdornment = Instance.new("BoxHandleAdornment"); -- close enough
StarterPlayerScripts = StarterPlayer:WaitForChild("StarterPlayerScripts"); StarterPlayerScripts = StarterPlayer:WaitForChild("StarterPlayerScripts");
StarterCharacterScripts = StarterPlayer:WaitForChild("StarterCharacterScripts"); StarterCharacterScripts = StarterPlayer:WaitForChild("StarterCharacterScripts");
ChatWindowConfiguration = TextChatService:WaitForChild("ChatWindowConfiguration");
ChatInputBarConfiguration = TextChatService:WaitForChild("ChatInputBarConfiguration");
} }
local exceptionClasses = local exceptionClasses =
{ {
PackageLink = true; PackageLink = true;
ScriptDebugger = true; ScriptDebugger = true;
ChatWindowConfiguration = true;
ChatInputBarConfiguration = true;
} }
local numberTypes = local numberTypes =
@ -205,7 +210,10 @@ end
-- Formatting -- Formatting
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
local formatting = require(script.Formatting) type FormatFunc = (any) -> string;
type Format = { [string]: FormatFunc }
local formatting: Format = require(script.Formatting)
local formatLinks = local formatLinks =
{ {
@ -227,7 +235,7 @@ local formatLinks =
["ProtectedString"] = "String"; ["ProtectedString"] = "String";
} }
local function getFormatFunction(valueType) local function getFormatFunction(valueType: string): FormatFunc?
if not formatting[valueType] then if not formatting[valueType] then
valueType = formatLinks[valueType] valueType = formatLinks[valueType]
end end
@ -307,6 +315,8 @@ local function generateClasses()
{ {
Axis = true; Axis = true;
FontSize = true; FontSize = true;
FontStyle = true;
FontWeight = true;
} }
for _,class in ipairs(apiDump.Classes) do for _,class in ipairs(apiDump.Classes) do
@ -323,7 +333,9 @@ local function generateClasses()
if classTags.Service then if classTags.Service then
pcall(function () pcall(function ()
if not className:find("Network") then
class.Object = game:GetService(className) class.Object = game:GetService(className)
end
end) end)
elseif not classTags.NotCreatable then elseif not classTags.NotCreatable then
pcall(function () pcall(function ()
@ -364,10 +376,6 @@ local function generateClasses()
writeLine("using RobloxFiles.Utility;") writeLine("using RobloxFiles.Utility;")
writeLine() writeLine()
-- writeLine("#pragma warning disable CA1041 // Provide ObsoleteAttribute message")
-- writeLine("#pragma warning disable CA1051 // Do not declare visible instance fields")
-- writeLine("#pragma warning disable CA1707 // Identifiers should not contain underscores")
-- writeLine("#pragma warning disable CA1716 // Identifiers should not match keywords")
writeLine("#pragma warning disable IDE1006 // Naming Styles") writeLine("#pragma warning disable IDE1006 // Naming Styles")
writeLine() writeLine()
@ -468,7 +476,10 @@ local function generateClasses()
local propTags = getTags(prop) local propTags = getTags(prop)
local serial = prop.Serialization local serial = prop.Serialization
local valueType = prop.ValueType.Name local typeData = prop.ValueType
local category = typeData.Category
local valueType = typeData.Name
local redirect = redirectProps[propName] local redirect = redirectProps[propName]
local couldSave = (serial.CanSave or propTags.Deprecated or redirect) local couldSave = (serial.CanSave or propTags.Deprecated or redirect)
@ -489,6 +500,8 @@ local function generateClasses()
valueType = "long" valueType = "long"
elseif valueType == "BinaryString" then elseif valueType == "BinaryString" then
valueType = "byte[]" valueType = "byte[]"
elseif valueType == "Font" and category ~= "Enum" then
valueType = "FontFace"
end end
local first = name:sub(1, 1) local first = name:sub(1, 1)
@ -567,33 +580,29 @@ local function generateClasses()
end) end)
end end
local typeData = prop.ValueType
local category = typeData.Category
if not gotValue and category ~= "Class" then if not gotValue and category ~= "Class" then
-- Fallback to implicit defaults -- Fallback to implicit defaults
local typeName = typeData.Name
if numberTypes[typeName] then if numberTypes[valueType] then
value = 0 value = 0
gotValue = true gotValue = true
elseif stringTypes[typeName] then elseif stringTypes[valueType] then
value = "" value = ""
gotValue = true gotValue = true
elseif typeName == "SharedString" then elseif valueType == "SharedString" then
value = "yuZpQdnvvUBOTYh1jqZ2cA==" value = "yuZpQdnvvUBOTYh1jqZ2cA=="
gotValue = true gotValue = true
elseif category == "DataType" then elseif category == "DataType" then
local DataType = env[typeName] local DataType = env[valueType]
if DataType and typeof(DataType) == "table" and not rawget(env, typeName) then if DataType and typeof(DataType) == "table" and not rawget(env, valueType) then
pcall(function () pcall(function ()
value = DataType.new() value = DataType.new()
gotValue = true gotValue = true
end) end)
end end
elseif category == "Enum" then elseif category == "Enum" then
local enum = Enum[typeName] local enum = Enum[valueType]
local lowestId = math.huge local lowestId = math.huge
local lowest local lowest
@ -623,7 +632,8 @@ local function generateClasses()
end end
if gotValue then if gotValue then
local formatFunc = getFormatFunction(valueType) local formatKey = if category == "Enum" then "Enum" else valueType
local formatFunc = getFormatFunction(formatKey)
if not formatFunc then if not formatFunc then
local literal = typeof(value) local literal = typeof(value)
@ -636,11 +646,13 @@ local function generateClasses()
local result local result
if formatFunc then
if typeof(formatFunc) == "string" then if typeof(formatFunc) == "string" then
result = formatFunc result = formatFunc
else else
result = formatFunc(value) result = formatFunc(value)
end end
end
if result ~= nil then if result ~= nil then
default = " = " .. result default = " = " .. result

View File

@ -77,6 +77,7 @@
<Compile Include="DataTypes\Content.cs" /> <Compile Include="DataTypes\Content.cs" />
<Compile Include="DataTypes\EulerAngles.cs" /> <Compile Include="DataTypes\EulerAngles.cs" />
<Compile Include="DataTypes\Faces.cs" /> <Compile Include="DataTypes\Faces.cs" />
<Compile Include="DataTypes\FontFace.cs" />
<Compile Include="DataTypes\NumberRange.cs" /> <Compile Include="DataTypes\NumberRange.cs" />
<Compile Include="DataTypes\NumberSequence.cs" /> <Compile Include="DataTypes\NumberSequence.cs" />
<Compile Include="DataTypes\NumberSequenceKeypoint.cs" /> <Compile Include="DataTypes\NumberSequenceKeypoint.cs" />
@ -115,6 +116,7 @@
<Compile Include="Tokens\Enum.cs" /> <Compile Include="Tokens\Enum.cs" />
<Compile Include="Tokens\Faces.cs" /> <Compile Include="Tokens\Faces.cs" />
<Compile Include="Tokens\Float.cs" /> <Compile Include="Tokens\Float.cs" />
<Compile Include="Tokens\Font.cs" />
<Compile Include="Tokens\Int.cs" /> <Compile Include="Tokens\Int.cs" />
<Compile Include="Tokens\Int64.cs" /> <Compile Include="Tokens\Int64.cs" />
<Compile Include="Tokens\NumberRange.cs" /> <Compile Include="Tokens\NumberRange.cs" />

89
Tokens/Font.cs Normal file
View File

@ -0,0 +1,89 @@
using System;
using System.Xml;
using RobloxFiles.Enums;
using RobloxFiles.DataTypes;
namespace RobloxFiles.Tokens
{
public class FontToken : IXmlPropertyToken, IAttributeToken<FontFace>
{
public string XmlPropertyToken => "Font";
public AttributeType AttributeType => AttributeType.FontFace;
public bool ReadProperty(Property prop, XmlNode node)
{
try
{
var familyNode = node["Family"];
var family = familyNode.InnerText;
var weightNode = node["Weight"];
var weight = (FontWeight)uint.Parse(weightNode.InnerText);
var styleNode = node["Style"];
Enum.TryParse(styleNode.InnerText, out FontStyle style);
prop.Value = new FontFace(family, weight, style);
return true;
}
catch
{
return false;
}
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
FontFace font = prop.CastValue<FontFace>();
var weight = (uint)font.Weight;
string family = font.Family;
string contentType = "null";
if (family.Length > 0)
contentType = "url";
var contentNode = doc.CreateElement(contentType);
contentNode.InnerText = family;
var familyNode = doc.CreateElement("Family");
familyNode.AppendChild(contentNode);
node.AppendChild(familyNode);
var weightNode = doc.CreateElement("Weight");
weightNode.InnerText = $"{weight}";
node.AppendChild(weightNode);
var styleNode = doc.CreateElement("Style");
styleNode.InnerText = $"{font.Style}";
node.AppendChild(styleNode);
}
public FontFace ReadAttribute(RbxAttribute attribute)
{
var weight = (FontStyle)attribute.ReadShort();
var style = (FontWeight)attribute.ReadByte();
var family = attribute.ReadString();
_ = attribute.ReadInt(); // Reserved
return new FontFace(family, style, weight);
}
public void WriteAttribute(RbxAttribute attribute, FontFace value)
{
var weight = (short)value.Weight;
var style = (byte)value.Style;
var family = value.Family;
var writer = attribute.Writer;
writer.Write(weight);
writer.Write(style);
attribute.WriteString(family);
attribute.WriteInt(0); // Reserved
}
}
}

View File

@ -43,7 +43,8 @@ namespace RobloxFiles
Rect = 28, Rect = 28,
// PhysicalProperties = 29 // PhysicalProperties = 29
// Region3 = 31, // Region3 = 31,
// Region3int16 = 32 // Region3int16 = 32,
FontFace = 33
} }
public class RbxAttribute : IDisposable public class RbxAttribute : IDisposable

View File

@ -688,6 +688,8 @@ namespace RobloxFiles
if (fieldType.IsEnum) if (fieldType.IsEnum)
xmlToken = "token"; xmlToken = "token";
else if (propType == PropertyType.Ref)
xmlToken = "Ref";
switch (xmlToken) switch (xmlToken)
{ {
@ -723,6 +725,11 @@ namespace RobloxFiles
xmlToken = "CoordinateFrame"; xmlToken = "CoordinateFrame";
break; break;
} }
case "FontFace":
{
xmlToken = "Font";
break;
}
case "Optional`1": case "Optional`1":
{ {
// TODO: If more optional types are added, // TODO: If more optional types are added,

View File

@ -41,7 +41,8 @@ namespace RobloxFiles
SharedString, SharedString,
ProtectedString, ProtectedString,
OptionalCFrame, OptionalCFrame,
UniqueId UniqueId,
FontFace
} }
public class Property public class Property
@ -81,6 +82,7 @@ namespace RobloxFiles
{ typeof(Content), PropertyType.String }, { typeof(Content), PropertyType.String },
{ typeof(Vector2), PropertyType.Vector2 }, { typeof(Vector2), PropertyType.Vector2 },
{ typeof(Vector3), PropertyType.Vector3 }, { typeof(Vector3), PropertyType.Vector3 },
{ typeof(FontFace), PropertyType.FontFace },
{ typeof(BrickColor), PropertyType.BrickColor }, { typeof(BrickColor), PropertyType.BrickColor },
{ typeof(Quaternion), PropertyType.Quaternion }, { typeof(Quaternion), PropertyType.Quaternion },

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -66,6 +66,11 @@ namespace RobloxFiles.XmlFormat
switch (prop.Type) switch (prop.Type)
{ {
case PropertyType.Ref:
{
propType = "Ref";
break;
}
case PropertyType.CFrame: case PropertyType.CFrame:
case PropertyType.Quaternion: case PropertyType.Quaternion:
{ {
@ -107,6 +112,9 @@ namespace RobloxFiles.XmlFormat
} }
} }
if (prop.Type == PropertyType.Ref)
propType = "Ref";
IXmlPropertyToken handler = XmlPropertyTokens.GetHandler(propType); IXmlPropertyToken handler = XmlPropertyTokens.GetHandler(propType);
if (handler == null) if (handler == null)