Correct UniqueId/FontFace implementations.

This commit is contained in:
Max 2022-10-12 21:19:43 -05:00
parent 583d69713d
commit 619b89d2a9
17 changed files with 3045 additions and 2687 deletions

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
@ -17,43 +17,46 @@ namespace RobloxFiles.BinaryFormat
File = file;
}
// Reads 'count * sizeof(T)' interleaved bytes and converts
// them into an array of T[count] where each value in the
// array has been decoded by the provided 'decode' function.
public T[] ReadInterleaved<T>(int count, Func<byte[], int, T> decode) where T : struct
// Reads 'count * sizeof(T)' interleaved bytes
public T[] ReadInterleaved<T>(int count, Func<byte[], int, T> transform) where T : struct
{
int bufferSize = Marshal.SizeOf<T>();
byte[] interleaved = ReadBytes(count * bufferSize);
int sizeof_T = Marshal.SizeOf<T>();
int blobSize = count * sizeof_T;
T[] values = new T[count];
var blob = ReadBytes(blobSize);
var work = new byte[sizeof_T];
var values = new T[count];
for (int i = 0; i < count; i++)
for (int offset = 0; offset < count; offset++)
{
long buffer = 0;
for (int column = 0; column < bufferSize; column++)
for (int i = 0; i < sizeof_T; i++)
{
long block = interleaved[(column * count) + i];
int shift = (bufferSize - column - 1) * 8;
buffer |= (block << shift);
int index = (i * count) + offset;
work[sizeof_T - i - 1] = blob[index];
}
byte[] sequence = BitConverter.GetBytes(buffer);
values[i] = decode(sequence, 0);
values[offset] = transform(work, 0);
}
return values;
}
// Decodes an int from an interleaved buffer.
private int DecodeInt(byte[] buffer, int startIndex)
// Rotates the sign bit of an int32 buffer.
public int RotateInt32(byte[] buffer, int startIndex)
{
int value = BitConverter.ToInt32(buffer, startIndex);
return (value >> 1) ^ (-(value & 1));
return (int)((uint)value >> 1) ^ (-(value & 1));
}
// Decodes a float from an interleaved buffer.
private float DecodeFloat(byte[] buffer, int startIndex)
// Rotates the sign bit of an int64 buffer.
public long RotateInt64(byte[] buffer, int startIndex)
{
long value = BitConverter.ToInt64(buffer, startIndex);
return (long)((ulong)value >> 1) ^ (-(value & 1));
}
// Rotates the sign bit of a float buffer.
public float RotateFloat(byte[] buffer, int startIndex)
{
uint u = BitConverter.ToUInt32(buffer, startIndex);
uint i = (u >> 1) | (u << 31);
@ -62,28 +65,10 @@ namespace RobloxFiles.BinaryFormat
return BitConverter.ToSingle(b, 0);
}
// Reads an interleaved buffer of integers.
public int[] ReadInts(int count)
{
return ReadInterleaved(count, DecodeInt);
}
// Reads an interleaved buffer of floats.
public float[] ReadFloats(int count)
{
return ReadInterleaved(count, DecodeFloat);
}
// Reads an interleaved buffer of unsigned integers.
public uint[] ReadUInts(int count)
{
return ReadInterleaved(count, BitConverter.ToUInt32);
}
// Reads and accumulates an interleaved buffer of integers.
// Reads and accumulates an interleaved int32 buffer.
public List<int> ReadInstanceIds(int count)
{
int[] values = ReadInts(count);
int[] values = ReadInterleaved(count, RotateInt32);
for (int i = 1; i < count; ++i)
values[i] += values[i - 1];

View File

@ -104,14 +104,20 @@ namespace RobloxFiles.BinaryFormat
Marshal.FreeHGlobal(converter);
}
// Encodes an int for an interleaved buffer.
private static int EncodeInt(int value)
// Rotates the sign bit of the provided int.
public int RotateInt(int value)
{
return (value << 1) ^ (value >> 31);
}
// Encodes a float for an interleaved buffer.
private static float EncodeFloat(float value)
// Rotates the sign bit of the provided long.
public long RotateLong(long value)
{
return (value << 1) ^ (value >> 63);
}
// Rotates the sign bit of the provided float.
public float RotateFloat(float value)
{
byte[] buffer = BitConverter.GetBytes(value);
uint bits = BitConverter.ToUInt32(buffer, 0);
@ -125,13 +131,19 @@ namespace RobloxFiles.BinaryFormat
// Writes an interleaved list of integers.
public void WriteInts(List<int> values)
{
WriteInterleaved(values, EncodeInt);
WriteInterleaved(values, RotateInt);
}
// Writes an interleaved list of longs
public void WriteLongs(List<long> values)
{
WriteInterleaved(values, RotateLong);
}
// Writes an interleaved list of floats
public void WriteFloats(List<float> values)
{
WriteInterleaved(values, EncodeFloat);
WriteInterleaved(values, RotateFloat);
}
// Accumulatively writes an interleaved array of integers.

View File

@ -78,8 +78,8 @@ namespace RobloxFiles.BinaryFormat.Chunks
}
// Setup some short-hand functions for actions used during the read procedure.
var readInts = new Func<int[]>(() => reader.ReadInts(instCount));
var readFloats = new Func<float[]>(() => reader.ReadFloats(instCount));
var readInts = new Func<int[]>(() => reader.ReadInterleaved(instCount, reader.RotateInt32));
var readFloats = new Func<float[]>(() => reader.ReadInterleaved(instCount, reader.RotateFloat));
var readProperties = new Action<Func<int, object>>(read =>
{
@ -434,7 +434,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
}
case PropertyType.Enum:
{
uint[] enums = reader.ReadUInts(instCount);
uint[] enums = reader.ReadInterleaved(instCount, BitConverter.ToUInt32);
readProperties(i =>
{
@ -619,22 +619,17 @@ namespace RobloxFiles.BinaryFormat.Chunks
}
case PropertyType.Int64:
{
long[] longs = reader.ReadInterleaved(instCount, (buffer, start) =>
{
long result = BitConverter.ToInt64(buffer, start);
return (long)((ulong)result >> 1) ^ (-(result & 1));
});
readProperties(i => longs[i]);
var values = reader.ReadInterleaved(instCount, reader.RotateInt64);
readProperties(i => values[i]);
break;
}
case PropertyType.SharedString:
{
uint[] SharedKeys = reader.ReadUInts(instCount);
var keys = reader.ReadInterleaved(instCount, BitConverter.ToUInt32);
readProperties(i =>
{
uint key = SharedKeys[i];
uint key = keys[i];
return File.SharedStrings[key];
});
@ -654,14 +649,16 @@ namespace RobloxFiles.BinaryFormat.Chunks
}
case PropertyType.UniqueId:
{
readProperties(i =>
var uniqueIds = reader.ReadInterleaved(instCount, (buffer, offset) =>
{
var index = reader.ReadUInt32();
var time = reader.ReadUInt32();
var random = reader.ReadUInt64();
return new UniqueId(index, time, random);
var random = reader.RotateInt64(buffer, 0);
var time = BitConverter.ToUInt32(buffer, 8);
var index = BitConverter.ToUInt32(buffer, 12);
return new UniqueId(random, time, index);
});
readProperties(i => uniqueIds[i]);
break;
}
case PropertyType.FontFace:
@ -670,13 +667,11 @@ namespace RobloxFiles.BinaryFormat.Chunks
{
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();
var cachedFaceId = reader.ReadString();
return new FontFace(family, weight, style);
return new FontFace(family, weight, style, cachedFaceId);
});
break;
@ -1233,12 +1228,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
longs.Add(value);
});
writer.WriteInterleaved(longs, value =>
{
// Move the sign bit to the front.
return (value << 1) ^ (value >> 63);
});
writer.WriteLongs(longs);
break;
}
case PropertyType.SharedString:
@ -1293,14 +1283,16 @@ namespace RobloxFiles.BinaryFormat.Chunks
}
case PropertyType.UniqueId:
{
var uniqueIds = new List<UniqueId>();
props.ForEach(prop =>
{
var uniqueId = prop.CastValue<UniqueId>();
writer.Write(uniqueId.Index);
writer.Write(uniqueId.Time);
writer.Write(uniqueId.Random);
var rotated = writer.RotateLong(uniqueId.Random);
uniqueIds.Add(new UniqueId(rotated, uniqueId.Time, uniqueId.Index));
});
writer.WriteInterleaved(uniqueIds);
break;
}
case PropertyType.FontFace:
@ -1310,16 +1302,16 @@ namespace RobloxFiles.BinaryFormat.Chunks
var font = prop.CastValue<FontFace>();
string family = font.Family;
writer.WriteString(font.Family);
if (family.EndsWith(".otf") || family.EndsWith(".ttf"))
return;
writer.WriteString(family);
var weight = (ushort)font.Weight;
writer.Write(weight);
var style = (byte)font.Style;
writer.Write(style);
var cachedFaceId = font.CachedFaceId;
writer.WriteString(cachedFaceId);
});
break;

View File

@ -91,20 +91,14 @@ namespace RobloxFiles.DataTypes
public CFrame(Vector3 eye, Vector3 look)
{
Vector3 zAxis = (eye - look).Unit,
xAxis = Vector3.Up.Cross(zAxis),
xAxis = Vector3.yAxis.Cross(zAxis),
yAxis = zAxis.Cross(xAxis);
if (xAxis.Magnitude == 0)
{
xAxis = Vector3.z;
yAxis = Vector3.x;
zAxis = Vector3.y;
if (zAxis.Y < 0)
{
xAxis = -xAxis;
zAxis = -zAxis;
}
xAxis = Vector3.zAxis;
yAxis = Vector3.xAxis;
zAxis = Vector3.yAxis;
}
m11 = xAxis.X; m12 = yAxis.X; m13 = zAxis.X; m14 = eye.X;
@ -299,18 +293,18 @@ namespace RobloxFiles.DataTypes
public static CFrame FromAxisAngle(Vector3 axis, float theta)
{
Vector3 r = VectorAxisAngle(axis, Vector3.x, theta),
u = VectorAxisAngle(axis, Vector3.y, theta),
b = VectorAxisAngle(axis, Vector3.z, theta);
Vector3 r = VectorAxisAngle(axis, Vector3.xAxis, theta),
u = VectorAxisAngle(axis, Vector3.yAxis, theta),
b = VectorAxisAngle(axis, Vector3.zAxis, 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);
}
public static CFrame FromEulerAnglesXYZ(float x, float y, float z)
{
CFrame cfx = FromAxisAngle(Vector3.x, x),
cfy = FromAxisAngle(Vector3.y, y),
cfz = FromAxisAngle(Vector3.z, z);
CFrame cfx = FromAxisAngle(Vector3.xAxis, x),
cfy = FromAxisAngle(Vector3.yAxis, y),
cfz = FromAxisAngle(Vector3.zAxis, z);
return cfx * cfy * cfz;
}
@ -410,9 +404,9 @@ namespace RobloxFiles.DataTypes
{
var tests = new float[3]
{
XVector.Dot(Vector3.x),
YVector.Dot(Vector3.y),
ZVector.Dot(Vector3.z)
XVector.Dot(Vector3.xAxis),
YVector.Dot(Vector3.yAxis),
ZVector.Dot(Vector3.zAxis)
};
foreach (var test in tests)

View File

@ -2,8 +2,9 @@
namespace RobloxFiles.DataTypes
{
// Implementation of Roblox's Font datatype.
// Renamed to FontFace to avoid disambiguation with System.Font and the Font enum.
// Implementation of Roblox's FontFace datatype.
// In Luau this type is named Font, but we avoid that name
// to avoid ambiguity with System.Font and Roblox's Font enum.
public class FontFace
{
@ -11,8 +12,16 @@ namespace RobloxFiles.DataTypes
public readonly FontWeight Weight = FontWeight.Regular;
public readonly FontStyle Style = FontStyle.Normal;
public FontFace(Content family, FontWeight weight = FontWeight.Regular, FontStyle style = FontStyle.Normal)
// Roblox caches the asset of the font's face to make it
// load faster. At runtime both the Family and the CachedFaceId
// are loaded in parallel. If the CachedFaceId doesn't match with
// the family file's face asset, then the correct one will be loaded late.
// Setting this is not required, it's just a throughput optimization.
public Content CachedFaceId { get; set; } = "";
public FontFace(Content family, FontWeight weight = FontWeight.Regular, FontStyle style = FontStyle.Normal, string cachedFaceId = "")
{
CachedFaceId = cachedFaceId;
Family = family;
Weight = weight;
Style = style;

View File

@ -1,16 +1,27 @@
namespace RobloxFiles.DataTypes
using System;
namespace RobloxFiles.DataTypes
{
public struct UniqueId
{
public readonly uint Time;
public readonly uint Index;
public readonly ulong Random;
public readonly long Random;
public UniqueId(uint time, uint index, ulong random)
public UniqueId(long random, uint time, uint index)
{
Time = time;
Index = index;
Random = random;
}
public override string ToString()
{
string random = Random.ToString("x2").PadLeft(16, '0');
string index = Index.ToString("x2").PadLeft(8, '0');
string time = Time.ToString("x2").PadLeft(8, '0');
return $"{random}{time}{index}";
}
}
}

View File

@ -98,13 +98,13 @@ namespace RobloxFiles.DataTypes
public static readonly Vector3 zero = new Vector3(0, 0, 0);
public static readonly Vector3 one = new Vector3(1, 1, 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 readonly Vector3 xAxis = new Vector3(1, 0, 0);
public static readonly Vector3 yAxis = new Vector3(0, 1, 0);
public static readonly Vector3 zAxis = new Vector3(0, 0, 1);
public static Vector3 Right => x;
public static Vector3 Up => y;
public static Vector3 Back => z;
public static Vector3 Right => xAxis;
public static Vector3 Up => yAxis;
public static Vector3 Back => zAxis;
public float Dot(Vector3 other)
{

View File

@ -5,10 +5,10 @@ export type FormatFunc = (any) -> string
export type Format = { [string]: FormatFunc }
export type IEnum = { GetEnumItems: (IEnum) -> {EnumItem} }
local function flags<T>(flags: T, enum: IEnum): string
local function flags(flagType: any, enum: Enum): string
local value = 0
for i, item in enum:GetEnumItems() do
for i, item: EnumItem in enum:GetEnumItems() do
if (flags :: any)[item.Name] then
value += (2 ^ item.Value)
end

View File

@ -8,49 +8,45 @@ local classes = {}
local outStream = ""
local stackLevel = 0
local singletons =
{
Speaker = Instance.new("Sound"); -- close enough
Terrain = workspace:WaitForChild("Terrain", 1000);
ParabolaAdornment = Instance.new("BoxHandleAdornment"); -- close enough
StarterPlayerScripts = StarterPlayer:WaitForChild("StarterPlayerScripts");
StarterCharacterScripts = StarterPlayer:WaitForChild("StarterCharacterScripts");
ChatWindowConfiguration = TextChatService:WaitForChild("ChatWindowConfiguration", 10);
ChatInputBarConfiguration = TextChatService:WaitForChild("ChatInputBarConfiguration", 10);
local singletons = {
Speaker = Instance.new("Sound"), -- close enough
Terrain = workspace:WaitForChild("Terrain", 1000),
ParabolaAdornment = Instance.new("BoxHandleAdornment"), -- close enough
StarterPlayerScripts = StarterPlayer:WaitForChild("StarterPlayerScripts"),
StarterCharacterScripts = StarterPlayer:WaitForChild("StarterCharacterScripts"),
ChatWindowConfiguration = TextChatService:WaitForChild("ChatWindowConfiguration", 10),
ChatInputBarConfiguration = TextChatService:WaitForChild("ChatInputBarConfiguration", 10),
}
local exceptionClasses =
{
PackageLink = true;
ScriptDebugger = true;
ChatWindowConfiguration = true;
ChatInputBarConfiguration = true;
local exceptionClasses = {
PackageLink = true,
ScriptDebugger = true,
ChatWindowConfiguration = true,
ChatInputBarConfiguration = true,
}
local numberTypes =
{
int = true;
long = true;
int64 = true;
float = true;
double = true;
local numberTypes = {
int = true,
long = true,
int64 = true,
float = true,
double = true,
}
local stringTypes =
{
string = true;
Content = true;
BinaryString = true;
ProtectedString = true;
local stringTypes = {
string = true,
Content = true,
BinaryString = true,
ProtectedString = true,
}
local isCoreScript = pcall(function ()
local isCoreScript = pcall(function()
local restricted = game:GetService("RobloxPluginGuiService")
return tostring(restricted)
end)
local function write(formatString, ...)
local tabs = string.rep(' ', stackLevel * 4)
local tabs = string.rep(" ", stackLevel * 4)
local fmt = formatString or ""
local value = tabs .. fmt:format(...)
@ -59,21 +55,21 @@ end
local function writeLine(formatString, ...)
if not formatString then
outStream = outStream .. '\n'
outStream = outStream .. "\n"
return
end
write(formatString .. '\n', ...)
write(formatString .. "\n", ...)
end
local function openStack()
writeLine('{')
writeLine("{")
stackLevel += 1
end
local function closeStack()
stackLevel -= 1
writeLine('}')
writeLine("}")
end
local function clearStream()
@ -92,7 +88,7 @@ local function exportStream(label)
export.Name = label
export.Parent = workspace
Selection:Add{export}
Selection:Add({ export })
end
if isCoreScript then
@ -110,7 +106,7 @@ local function getTags(object)
local tags = {}
if object.Tags ~= nil then
for _,tag in pairs(object.Tags) do
for _, tag in pairs(object.Tags) do
tags[tag] = true
end
end
@ -166,7 +162,7 @@ end
local function collectProperties(class)
local propMap = {}
for _,member in ipairs(class.Members) do
for _, member in ipairs(class.Members) do
if member.MemberType == "Property" then
local propName = member.Name
propMap[propName] = member
@ -177,32 +173,30 @@ local function collectProperties(class)
end
local function createProperty(propName, propType)
local category = "DataType";
local category = "DataType"
local name = propType
if propType:find(':') then
local data = string.split(propType, ':')
if propType:find(":") then
local data = string.split(propType, ":")
category = data[1]
name = data[2]
end
return
{
Name = propName;
{
Name = propName,
Serialization =
{
CanSave = true;
CanLoad = true;
};
Serialization = {
CanSave = true,
CanLoad = true,
},
ValueType =
{
Category = category;
Name = name;
};
ValueType = {
Category = category,
Name = name,
},
Security = "None";
Security = "None",
}
end
@ -214,21 +208,20 @@ local formatting: Format = require(script.Formatting)
type FormatFunc = formatting.FormatFunc
type Format = formatting.Format
local formatLinks =
{
["int"] = "Int";
["long"] = "Int";
local formatLinks = {
["int"] = "Int",
["long"] = "Int",
["float"] = "Float";
["byte[]"] = "Bytes";
["double"] = "Double";
["boolean"] = "Bool";
["float"] = "Float",
["byte[]"] = "Bytes",
["double"] = "Double",
["boolean"] = "Bool",
["string"] = "String";
["Content"] = "String";
["string"] = "String",
["Content"] = "String",
["Color3uint8"] = "Color3";
["ProtectedString"] = "String";
["Color3uint8"] = "Color3",
["ProtectedString"] = "String",
}
local function getFormatFunction(valueType: string): FormatFunc
@ -307,15 +300,14 @@ local function generateClasses()
local classNames = {}
classes = {}
local enumMap =
{
Axis = true;
FontSize = true;
FontStyle = true;
FontWeight = true;
local enumMap = {
Axis = true,
FontSize = true,
FontStyle = true,
FontWeight = true,
}
for _,class in ipairs(apiDump.Classes) do
for _, class in ipairs(apiDump.Classes) do
local className = class.Name
local superClass = classes[class.Superclass]
@ -328,13 +320,13 @@ local function generateClasses()
local classTags = getTags(class)
if classTags.Service then
pcall(function ()
pcall(function()
if not className:find("Network") then
class.Object = game:GetService(className)
end
end)
elseif not classTags.NotCreatable then
pcall(function ()
pcall(function()
local dumpFolder = game:FindFirstChild("DumpFolder")
class.Object = Instance.new(className)
@ -393,7 +385,7 @@ local function generateClasses()
registerClass = false
end
local noSecurityCheck = pcall(function ()
local noSecurityCheck = pcall(function()
if not classTags.Service then
return tostring(object)
end
@ -435,7 +427,7 @@ local function generateClasses()
local propMap = collectProperties(class)
local propNames = {}
for _,propName in pairs(classPatches.Remove) do
for _, propName in pairs(classPatches.Remove) do
propMap[propName] = nil
end
@ -472,9 +464,7 @@ local function generateClasses()
local inheritProps = ancestor.PropertyMap
local inherited = ancestor.Inherited
local baseObject = if inherited
then inherited.Object
else nil
local baseObject = if inherited then inherited.Object else nil
if inheritProps and baseObject then
for name, prop in pairs(inheritProps) do
@ -484,11 +474,11 @@ local function generateClasses()
continue
end
local gotPropValue, propValue = pcall(function ()
local gotPropValue, propValue = pcall(function()
return object[name]
end)
local gotBaseValue, baseValue = pcall(function ()
local gotBaseValue, baseValue = pcall(function()
return baseObject[name]
end)
@ -572,7 +562,7 @@ local function generateClasses()
local default = ""
if propName == className then
name = name .. '_'
name = name .. "_"
end
if valueType == "int64" then
@ -603,8 +593,8 @@ local function generateClasses()
set = "this." .. set
end
else
get = redirect.Get
set = redirect.Set
get = redirect.Get
set = redirect.Set
flag = redirect.Flag
end
@ -626,7 +616,7 @@ local function generateClasses()
openStack()
writeLine("get => %s;", get)
if set:find('\n') then
if set:find("\n") then
writeLine()
writeLine("set")
@ -654,7 +644,7 @@ local function generateClasses()
local gotValue = (value ~= nil)
if not gotValue then
gotValue, value = pcall(function ()
gotValue, value = pcall(function()
return object[propName]
end)
end
@ -675,7 +665,7 @@ local function generateClasses()
local DataType = env[valueType]
if DataType and typeof(DataType) == "table" and not rawget(env, valueType) then
pcall(function ()
pcall(function()
value = DataType.new()
gotValue = true
end)
@ -685,7 +675,7 @@ local function generateClasses()
local lowestId = math.huge
local lowest
for _,item in pairs(enum:GetEnumItems()) do
for _, item in pairs(enum:GetEnumItems()) do
local itemValue = item.Value
if itemValue < lowestId then
@ -706,7 +696,13 @@ local function generateClasses()
if gotValue then
warn(src, "Fell back to implicit value for property:", id)
else
warn(src, "!! Could not figure out default value for property:", id, "value error was:", value)
warn(
src,
"!! Could not figure out default value for property:",
id,
"value error was:",
value
)
end
end
@ -769,7 +765,7 @@ local function generateClasses()
closeStack()
if (i ~= #classNames) then
if i ~= #classNames then
writeLine()
end
end
@ -808,20 +804,20 @@ local function generateEnums(whiteList)
local lastValue = -1
local mapped = {}
table.sort(enumItems, function (a, b)
table.sort(enumItems, function(a, b)
return a.Value < b.Value
end)
for j, enumItem in ipairs(enumItems) do
local text = ""
local comma = ','
local comma = ","
local name = enumItem.Name
local value = enumItem.Value
if not mapped[value] then
if (value - lastValue) ~= 1 then
text = " = " .. value;
text = " = " .. value
end
if j == #enumItems then

1
Plugins/sourcemap.json Normal file
View File

@ -0,0 +1 @@
{"name":"GenerateApiDump","className":"Script","filePaths":["GenerateApiDump\\init.server.lua","default.project.json"],"children":[{"name":"Formatting","className":"ModuleScript","filePaths":["GenerateApiDump\\Formatting.lua"]},{"name":"PropertyPatches","className":"ModuleScript","filePaths":["GenerateApiDump\\PropertyPatches.lua"]}]}

Binary file not shown.

View File

@ -66,9 +66,9 @@ namespace RobloxFiles.Tokens
var style = (FontWeight)attribute.ReadByte();
var family = attribute.ReadString();
_ = attribute.ReadInt(); // Reserved
var cachedFaceId = attribute.ReadString();
return new FontFace(family, style, weight);
return new FontFace(family, style, weight, cachedFaceId);
}
public void WriteAttribute(RbxAttribute attribute, FontFace value)

View File

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Xml;
using RobloxFiles.DataTypes;
@ -14,13 +15,21 @@ namespace RobloxFiles.Tokens
if (Guid.TryParse(hex, out var guid))
{
var bytes = guid.ToByteArray();
var random = BitConverter.ToUInt64(bytes, 0);
var bytes = new byte[16];
var time = BitConverter.ToUInt32(bytes, 8);
var index = BitConverter.ToUInt32(bytes, 12);
for (int i = 0; i < 16; i++)
{
var hexChar = hex.Substring(i * 2, 2);
bytes[15 - i] = Convert.ToByte(hexChar, 16);
}
var rand = BitConverter.ToInt64(bytes, 8);
var time = BitConverter.ToUInt32(bytes, 4);
var index = BitConverter.ToUInt32(bytes, 0);
var uniqueId = new UniqueId(rand, time, index);
prop.Value = uniqueId;
prop.Value = new UniqueId(time, index, random);
return true;
}
@ -30,8 +39,8 @@ namespace RobloxFiles.Tokens
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
var uniqueId = prop.CastValue<UniqueId>();
var random = BitConverter.GetBytes(uniqueId.Random);
var random = BitConverter.GetBytes(uniqueId.Random);
var time = BitConverter.GetBytes(uniqueId.Time);
var index = BitConverter.GetBytes(uniqueId.Index);

View File

@ -59,7 +59,7 @@ namespace RobloxFiles
internal static BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.IgnoreCase;
// TODO: Map typeof(ProtectedString) to PropertyType.ProtectedString
// if binary files are ever publically allowed to read it.
// if binary files are ever publicly allowed to read it.
public static readonly IReadOnlyDictionary<Type, PropertyType> Types = new Dictionary<Type, PropertyType>()
{

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Collections.Generic;
using RobloxFiles.Enums;
using RobloxFiles.DataTypes;
namespace RobloxFiles.Utility
{
@ -26,6 +27,56 @@ namespace RobloxFiles.Utility
{ 96, FontSize.Size96 },
};
public static readonly IReadOnlyDictionary<Font, FontFace> FontFaces = new Dictionary<Font, FontFace>()
{
{ Font.Legacy, new FontFace("rbxasset://fonts/families/LegacyArial.json") },
{ Font.Arial, new FontFace("rbxasset://fonts/families/Arial.json") },
{ Font.ArialBold, new FontFace("rbxasset://fonts/families/Arial.json", FontWeight.Bold) },
{ Font.SourceSans, new FontFace("rbxasset://fonts/families/SourceSansPro.json") },
{ Font.SourceSansBold, new FontFace("rbxasset://fonts/families/SourceSansPro.json", FontWeight.Bold) },
{ Font.SourceSansSemibold, new FontFace("rbxasset://fonts/families/SourceSansPro.json", FontWeight.SemiBold) },
{ Font.SourceSansLight, new FontFace("rbxasset://fonts/families/SourceSansPro.json", FontWeight.Light) },
{ Font.SourceSansItalic, new FontFace("rbxasset://fonts/families/SourceSansPro.json", FontWeight.Regular, FontStyle.Italic) },
{ Font.Bodoni, new FontFace("rbxasset://fonts/families/AccanthisADFStd.json") },
{ Font.Garamond, new FontFace("rbxasset://fonts/families/Guru.json") },
{ Font.Cartoon, new FontFace("rbxasset://fonts/families/ComicNeueAngular.json") },
{ Font.Code, new FontFace("rbxasset://fonts/families/Inconsolata.json") },
{ Font.Highway, new FontFace("rbxasset://fonts/families/HighwayGothic.json") },
{ Font.SciFi, new FontFace("rbxasset://fonts/families/Zekton.json") },
{ Font.Arcade, new FontFace("rbxasset://fonts/families/PressStart2P.json") },
{ Font.Fantasy, new FontFace("rbxasset://fonts/families/Balthazar.json") },
{ Font.Antique, new FontFace("rbxasset://fonts/families/RomanAntique.json") },
{ Font.Gotham, new FontFace("rbxasset://fonts/families/GothamSSm.json") },
{ Font.GothamMedium, new FontFace("rbxasset://fonts/families/GothamSSm.json", FontWeight.Medium) },
{ Font.GothamBold, new FontFace("rbxasset://fonts/families/GothamSSm.json", FontWeight.Bold) },
{ Font.GothamBlack, new FontFace("rbxasset://fonts/families/GothamSSm.json", FontWeight.Heavy) },
{ Font.AmaticSC, new FontFace("rbxasset://fonts/families/AmaticSC.json") },
{ Font.Bangers, new FontFace("rbxasset://fonts/families/Bangers.json") },
{ Font.Creepster, new FontFace("rbxasset://fonts/families/Creepster.json") },
{ Font.DenkOne, new FontFace("rbxasset://fonts/families/DenkOne.json") },
{ Font.Fondamento, new FontFace("rbxasset://fonts/families/Fondamento.json") },
{ Font.FredokaOne, new FontFace("rbxasset://fonts/families/FredokaOne.json") },
{ Font.GrenzeGotisch, new FontFace("rbxasset://fonts/families/GrenzeGotisch.json") },
{ Font.IndieFlower, new FontFace("rbxasset://fonts/families/IndieFlower.json") },
{ Font.JosefinSans, new FontFace("rbxasset://fonts/families/JosefinSans.json") },
{ Font.Jura, new FontFace("rbxasset://fonts/families/Jura.json") },
{ Font.Kalam, new FontFace("rbxasset://fonts/families/Kalam.json") },
{ Font.LuckiestGuy, new FontFace("rbxasset://fonts/families/LuckiestGuy.json") },
{ Font.Merriweather, new FontFace("rbxasset://fonts/families/Merriweather.json") },
{ Font.Michroma, new FontFace("rbxasset://fonts/families/Michroma.json") },
{ Font.Nunito, new FontFace("rbxasset://fonts/families/Nunito.json") },
{ Font.Oswald, new FontFace("rbxasset://fonts/families/Oswald.json") },
{ Font.PatrickHand, new FontFace("rbxasset://fonts/families/PatrickHand.json") },
{ Font.PermanentMarker, new FontFace("rbxasset://fonts/families/PermanentMarker.json") },
{ Font.Roboto, new FontFace("rbxasset://fonts/families/Roboto.json") },
{ Font.RobotoCondensed, new FontFace("rbxasset://fonts/families/RobotoCondensed.json") },
{ Font.RobotoMono, new FontFace("rbxasset://fonts/families/RobotoMono.json") },
{ Font.Sarpanch, new FontFace("rbxasset://fonts/families/Sarpanch.json") },
{ Font.SpecialElite, new FontFace("rbxasset://fonts/families/SpecialElite.json") },
{ Font.TitilliumWeb, new FontFace("rbxasset://fonts/families/TitilliumWeb.json") },
{ Font.Ubuntu, new FontFace("rbxasset://fonts/families/Ubuntu.json") },
};
public static FontSize GetFontSize(int fontSize)
{
if (fontSize > 60)
@ -57,5 +108,19 @@ namespace RobloxFiles.Utility
return value;
}
public static Font GetFont(FontFace face)
{
var result = Font.Unknown;
var faceQuery = FontFaces
.Where(pair => face.Equals(pair.Value))
.Select(pair => pair.Key);
if (faceQuery.Any())
result = faceQuery.First();
return result;
}
}
}