Correct UniqueId/FontFace implementations.
This commit is contained in:
parent
583d69713d
commit
619b89d2a9
@ -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];
|
||||
|
@ -103,15 +103,21 @@ 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.
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -8,72 +8,68 @@ 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(...)
|
||||
outStream = outStream .. value
|
||||
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()
|
||||
@ -84,7 +80,7 @@ end
|
||||
local function exportStream(label)
|
||||
local results = outStream:gsub("\n\n\n", "\n\n")
|
||||
local export
|
||||
|
||||
|
||||
if plugin then
|
||||
export = Instance.new("Script")
|
||||
export.Archivable = false
|
||||
@ -92,9 +88,9 @@ local function exportStream(label)
|
||||
export.Name = label
|
||||
export.Parent = workspace
|
||||
|
||||
Selection:Add{export}
|
||||
Selection:Add({ export })
|
||||
end
|
||||
|
||||
|
||||
if isCoreScript then
|
||||
StudioService:CopyToClipboard(results)
|
||||
elseif not plugin then
|
||||
@ -108,101 +104,99 @@ end
|
||||
|
||||
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
|
||||
|
||||
|
||||
if object.Name == "Terrain" then
|
||||
tags.NotCreatable = nil
|
||||
end
|
||||
|
||||
|
||||
return tags
|
||||
end
|
||||
|
||||
local function upcastInheritance(class, root)
|
||||
local superClass = classes[class.Superclass]
|
||||
|
||||
|
||||
if not superClass then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
if not root then
|
||||
root = class
|
||||
end
|
||||
|
||||
|
||||
if not superClass.Inherited then
|
||||
superClass.Inherited = root
|
||||
end
|
||||
|
||||
|
||||
upcastInheritance(superClass, root)
|
||||
end
|
||||
|
||||
local function canCreateClass(class)
|
||||
local tags = getTags(class)
|
||||
local canCreate = true
|
||||
|
||||
|
||||
if tags.NotCreatable then
|
||||
canCreate = false
|
||||
end
|
||||
|
||||
|
||||
if tags.Service then
|
||||
canCreate = true
|
||||
end
|
||||
|
||||
|
||||
if tags.Settings then
|
||||
canCreate = false
|
||||
end
|
||||
|
||||
|
||||
if singletons[class.Name] then
|
||||
canCreate = true
|
||||
end
|
||||
|
||||
|
||||
return canCreate
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return propMap
|
||||
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;
|
||||
|
||||
Serialization =
|
||||
{
|
||||
CanSave = true;
|
||||
CanLoad = true;
|
||||
};
|
||||
|
||||
ValueType =
|
||||
{
|
||||
Category = category;
|
||||
Name = name;
|
||||
};
|
||||
|
||||
Security = "None";
|
||||
{
|
||||
Name = propName,
|
||||
|
||||
Serialization = {
|
||||
CanSave = true,
|
||||
CanLoad = true,
|
||||
},
|
||||
|
||||
ValueType = {
|
||||
Category = category,
|
||||
Name = name,
|
||||
},
|
||||
|
||||
Security = "None",
|
||||
}
|
||||
end
|
||||
|
||||
@ -214,28 +208,27 @@ local formatting: Format = require(script.Formatting)
|
||||
type FormatFunc = formatting.FormatFunc
|
||||
type Format = formatting.Format
|
||||
|
||||
local formatLinks =
|
||||
{
|
||||
["int"] = "Int";
|
||||
["long"] = "Int";
|
||||
|
||||
["float"] = "Float";
|
||||
["byte[]"] = "Bytes";
|
||||
["double"] = "Double";
|
||||
["boolean"] = "Bool";
|
||||
|
||||
["string"] = "String";
|
||||
["Content"] = "String";
|
||||
|
||||
["Color3uint8"] = "Color3";
|
||||
["ProtectedString"] = "String";
|
||||
local formatLinks = {
|
||||
["int"] = "Int",
|
||||
["long"] = "Int",
|
||||
|
||||
["float"] = "Float",
|
||||
["byte[]"] = "Bytes",
|
||||
["double"] = "Double",
|
||||
["boolean"] = "Bool",
|
||||
|
||||
["string"] = "String",
|
||||
["Content"] = "String",
|
||||
|
||||
["Color3uint8"] = "Color3",
|
||||
["ProtectedString"] = "String",
|
||||
}
|
||||
|
||||
local function getFormatFunction(valueType: string): FormatFunc
|
||||
if not formatting[valueType] then
|
||||
valueType = formatLinks[valueType]
|
||||
end
|
||||
|
||||
|
||||
return formatting[valueType] or formatting.Null
|
||||
end
|
||||
|
||||
@ -250,7 +243,7 @@ function patchIndex:__index(key)
|
||||
if not rawget(self, key) then
|
||||
rawset(self, key, {})
|
||||
end
|
||||
|
||||
|
||||
return self[key]
|
||||
end
|
||||
|
||||
@ -273,71 +266,70 @@ if plugin then
|
||||
|
||||
button = toolbar:CreateButton(
|
||||
"Dump API",
|
||||
"Generates a C# dump of Roblox's Class/Enum API.",
|
||||
"Generates a C# dump of Roblox's Class/Enum API.",
|
||||
"rbxasset://textures/Icon_Stream_Off@2x.png"
|
||||
)
|
||||
|
||||
|
||||
button.ClickableWhenViewportHidden = true
|
||||
end
|
||||
|
||||
local function getAsync(url)
|
||||
local enabled
|
||||
|
||||
|
||||
if isCoreScript then
|
||||
enabled = HttpService:GetHttpEnabled()
|
||||
HttpService:SetHttpEnabled(true)
|
||||
end
|
||||
|
||||
|
||||
local result = HttpService:GetAsync(url)
|
||||
|
||||
|
||||
if isCoreScript then
|
||||
HttpService:SetHttpEnabled(enabled)
|
||||
end
|
||||
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local function generateClasses()
|
||||
local env = getfenv()
|
||||
local version = getAsync(baseUrl .. "version.txt")
|
||||
|
||||
|
||||
local apiDump = getAsync(baseUrl .. "API-Dump.json")
|
||||
apiDump = HttpService:JSONDecode(apiDump)
|
||||
|
||||
|
||||
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]
|
||||
|
||||
|
||||
if singletons[className] then
|
||||
class.Singleton = true
|
||||
class.Object = singletons[className]
|
||||
end
|
||||
|
||||
|
||||
if superClass and canCreateClass(class) then
|
||||
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)
|
||||
|
||||
|
||||
if dumpFolder then
|
||||
local old = dumpFolder:FindFirstChildOfClass(className)
|
||||
|
||||
@ -350,50 +342,50 @@ local function generateClasses()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
upcastInheritance(class)
|
||||
end
|
||||
|
||||
|
||||
classes[className] = class
|
||||
table.insert(classNames, className)
|
||||
end
|
||||
|
||||
|
||||
outStream = ""
|
||||
|
||||
|
||||
writeLine("// Auto-generated list of creatable Roblox classes.")
|
||||
writeLine("// Updated as of %s", version)
|
||||
writeLine()
|
||||
|
||||
|
||||
writeLine("using System;")
|
||||
writeLine()
|
||||
|
||||
|
||||
writeLine("using RobloxFiles.DataTypes;")
|
||||
writeLine("using RobloxFiles.Enums;")
|
||||
writeLine("using RobloxFiles.Utility;")
|
||||
writeLine()
|
||||
|
||||
|
||||
writeLine("#pragma warning disable IDE1006 // Naming Styles")
|
||||
writeLine()
|
||||
|
||||
|
||||
writeLine("namespace RobloxFiles")
|
||||
openStack()
|
||||
|
||||
|
||||
for i, className in ipairs(classNames) do
|
||||
local class = classes[className]
|
||||
local classTags = getTags(class)
|
||||
|
||||
|
||||
local registerClass = canCreateClass(class)
|
||||
local object = class.Object
|
||||
|
||||
|
||||
if class.Inherited then
|
||||
registerClass = true
|
||||
end
|
||||
|
||||
|
||||
if class.Name == "Instance" or class.Name == "Studio" then
|
||||
registerClass = false
|
||||
end
|
||||
|
||||
local noSecurityCheck = pcall(function ()
|
||||
local noSecurityCheck = pcall(function()
|
||||
if not classTags.Service then
|
||||
return tostring(object)
|
||||
end
|
||||
@ -402,7 +394,7 @@ local function generateClasses()
|
||||
if not noSecurityCheck then
|
||||
object = nil
|
||||
end
|
||||
|
||||
|
||||
if not object then
|
||||
if class.Inherited then
|
||||
object = class.Inherited.Object
|
||||
@ -412,40 +404,40 @@ local function generateClasses()
|
||||
registerClass = false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if exceptionClasses[class.Name] then
|
||||
registerClass = true
|
||||
end
|
||||
|
||||
|
||||
if registerClass then
|
||||
local objectType
|
||||
|
||||
|
||||
if classTags.NotCreatable and class.Inherited and not class.Singleton then
|
||||
objectType = "abstract class"
|
||||
else
|
||||
objectType = "class"
|
||||
end
|
||||
|
||||
|
||||
writeLine("public %s %s : %s", objectType, className, class.Superclass)
|
||||
openStack()
|
||||
|
||||
|
||||
local classPatches = getPatches(className)
|
||||
local redirectProps = classPatches.Redirect
|
||||
|
||||
|
||||
local propMap = collectProperties(class)
|
||||
local propNames = {}
|
||||
|
||||
for _,propName in pairs(classPatches.Remove) do
|
||||
|
||||
for _, propName in pairs(classPatches.Remove) do
|
||||
propMap[propName] = nil
|
||||
end
|
||||
|
||||
|
||||
for propName in pairs(propMap) do
|
||||
table.insert(propNames, propName)
|
||||
end
|
||||
|
||||
|
||||
for propName, propType in pairs(classPatches.Add) do
|
||||
local prop = propMap[propName]
|
||||
|
||||
|
||||
if prop then
|
||||
local serial = prop.Serialization
|
||||
serial.CanSave = true
|
||||
@ -455,13 +447,13 @@ local function generateClasses()
|
||||
table.insert(propNames, propName)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local firstLine = true
|
||||
class.PropertyMap = propMap
|
||||
|
||||
|
||||
local ancestor = class
|
||||
local diffProps = {}
|
||||
|
||||
|
||||
while object do
|
||||
ancestor = classes[ancestor.Superclass]
|
||||
|
||||
@ -471,27 +463,25 @@ 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
|
||||
local tags = getTags(prop)
|
||||
|
||||
|
||||
if tags.ReadOnly then
|
||||
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)
|
||||
|
||||
|
||||
if gotBaseValue and gotPropValue then
|
||||
if propValue ~= baseValue then
|
||||
diffProps[name] = propValue
|
||||
@ -500,7 +490,7 @@ local function generateClasses()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if classTags.Service or next(diffProps) then
|
||||
local headerFormat = "public %s()"
|
||||
|
||||
@ -510,10 +500,10 @@ local function generateClasses()
|
||||
|
||||
writeLine(headerFormat, className)
|
||||
openStack()
|
||||
|
||||
|
||||
if classTags.Service then
|
||||
writeLine("IsService = true;")
|
||||
|
||||
|
||||
if next(diffProps) then
|
||||
writeLine()
|
||||
end
|
||||
@ -532,7 +522,7 @@ local function generateClasses()
|
||||
local value = diffProps[name]
|
||||
local valueType = typeof(value)
|
||||
local formatFunc = getFormatFunction(valueType)
|
||||
|
||||
|
||||
if formatFunc ~= formatting.Null then
|
||||
local result = formatFunc(value)
|
||||
|
||||
@ -544,37 +534,37 @@ local function generateClasses()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
closeStack()
|
||||
end
|
||||
|
||||
|
||||
table.sort(propNames)
|
||||
|
||||
|
||||
for j, propName in ipairs(propNames) do
|
||||
local prop = propMap[propName]
|
||||
local propTags = getTags(prop)
|
||||
|
||||
|
||||
local serial = prop.Serialization
|
||||
local typeData = prop.ValueType
|
||||
|
||||
local category = typeData.Category
|
||||
local valueType = typeData.Name
|
||||
|
||||
|
||||
local redirect = redirectProps[propName]
|
||||
local couldSave = (serial.CanSave or propTags.Deprecated or redirect)
|
||||
|
||||
|
||||
if serial.CanLoad and couldSave then
|
||||
if firstLine and (classTags.Service or next(diffProps)) then
|
||||
writeLine()
|
||||
end
|
||||
|
||||
|
||||
local name = propName
|
||||
local default = ""
|
||||
|
||||
|
||||
if propName == className then
|
||||
name = name .. '_'
|
||||
name = name .. "_"
|
||||
end
|
||||
|
||||
|
||||
if valueType == "int64" then
|
||||
valueType = "long"
|
||||
elseif valueType == "BinaryString" then
|
||||
@ -582,40 +572,40 @@ local function generateClasses()
|
||||
elseif valueType == "Font" and category ~= "Enum" then
|
||||
valueType = "FontFace"
|
||||
end
|
||||
|
||||
|
||||
local first = name:sub(1, 1)
|
||||
|
||||
|
||||
if first == first:lower() then
|
||||
local pascal = first:upper() .. name:sub(2)
|
||||
if propMap[pascal] ~= nil and propTags.Deprecated then
|
||||
redirect = pascal
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if redirect then
|
||||
local get, set, flag
|
||||
|
||||
|
||||
if typeof(redirect) == "string" then
|
||||
get = redirect
|
||||
set = redirect .. " = value"
|
||||
|
||||
|
||||
if redirect == "value" then
|
||||
set = "this." .. set
|
||||
end
|
||||
else
|
||||
get = redirect.Get
|
||||
set = redirect.Set
|
||||
get = redirect.Get
|
||||
set = redirect.Set
|
||||
flag = redirect.Flag
|
||||
end
|
||||
|
||||
|
||||
if not firstLine and set then
|
||||
writeLine()
|
||||
end
|
||||
|
||||
|
||||
if propTags.Deprecated then
|
||||
writeLine("[Obsolete]")
|
||||
end
|
||||
|
||||
|
||||
if set then
|
||||
if flag then
|
||||
writeLine("public %s %s %s", flag, valueType, name)
|
||||
@ -626,9 +616,9 @@ local function generateClasses()
|
||||
openStack()
|
||||
writeLine("get => %s;", get)
|
||||
|
||||
if set:find('\n') then
|
||||
if set:find("\n") then
|
||||
writeLine()
|
||||
|
||||
|
||||
writeLine("set")
|
||||
openStack()
|
||||
|
||||
@ -645,23 +635,23 @@ local function generateClasses()
|
||||
else
|
||||
writeLine("public %s %s => %s;", valueType, name, get)
|
||||
end
|
||||
|
||||
|
||||
if j ~= #propNames and set then
|
||||
writeLine()
|
||||
end
|
||||
else
|
||||
local value = classPatches.Defaults[propName]
|
||||
local gotValue = (value ~= nil)
|
||||
|
||||
|
||||
if not gotValue then
|
||||
gotValue, value = pcall(function ()
|
||||
gotValue, value = pcall(function()
|
||||
return object[propName]
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
if not gotValue and category ~= "Class" then
|
||||
-- Fallback to implicit defaults
|
||||
|
||||
|
||||
if numberTypes[valueType] then
|
||||
value = 0
|
||||
gotValue = true
|
||||
@ -673,9 +663,9 @@ local function generateClasses()
|
||||
gotValue = true
|
||||
elseif category == "DataType" then
|
||||
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,21 +696,27 @@ 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
|
||||
|
||||
|
||||
if gotValue then
|
||||
local formatKey = if category == "Enum" then "Enum" else valueType
|
||||
local formatFunc = getFormatFunction(formatKey)
|
||||
|
||||
|
||||
if formatFunc == formatting.Null then
|
||||
local literal = typeof(value)
|
||||
formatFunc = getFormatFunction(literal)
|
||||
end
|
||||
|
||||
|
||||
local result
|
||||
|
||||
|
||||
if formatFunc then
|
||||
if typeof(formatFunc) == "string" then
|
||||
result = formatFunc
|
||||
@ -732,7 +728,7 @@ local function generateClasses()
|
||||
if result == "" then
|
||||
result = nil
|
||||
end
|
||||
|
||||
|
||||
if result ~= nil then
|
||||
default = " = " .. result
|
||||
end
|
||||
@ -747,34 +743,34 @@ local function generateClasses()
|
||||
-- .____.
|
||||
propTags.Deprecated = false
|
||||
end
|
||||
|
||||
|
||||
if propTags.Deprecated then
|
||||
if not firstLine then
|
||||
writeLine()
|
||||
end
|
||||
|
||||
|
||||
writeLine("[Obsolete]")
|
||||
end
|
||||
|
||||
|
||||
writeLine("public %s %s%s;", valueType, name, default)
|
||||
|
||||
|
||||
if propTags.Deprecated and j ~= #propNames then
|
||||
writeLine()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
firstLine = false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
closeStack()
|
||||
|
||||
if (i ~= #classNames) then
|
||||
|
||||
if i ~= #classNames then
|
||||
writeLine()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
closeStack()
|
||||
exportStream("Classes")
|
||||
|
||||
@ -784,64 +780,64 @@ end
|
||||
local function generateEnums(whiteList)
|
||||
local version = getfenv().version():gsub("%. ", ".")
|
||||
clearStream()
|
||||
|
||||
|
||||
writeLine("// Auto-generated list of Roblox enums.")
|
||||
writeLine("// Updated as of %s", version)
|
||||
writeLine()
|
||||
|
||||
writeLine("namespace RobloxFiles.Enums")
|
||||
openStack()
|
||||
|
||||
|
||||
local enums = Enum:GetEnums()
|
||||
|
||||
|
||||
for i, enum in ipairs(enums) do
|
||||
local enumName = tostring(enum)
|
||||
|
||||
if whiteList and not whiteList[enumName] then
|
||||
continue
|
||||
end
|
||||
|
||||
|
||||
writeLine("public enum %s", enumName)
|
||||
openStack()
|
||||
|
||||
|
||||
local enumItems = enum:GetEnumItems()
|
||||
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
|
||||
comma = ""
|
||||
end
|
||||
|
||||
|
||||
lastValue = value
|
||||
mapped[value] = true
|
||||
|
||||
writeLine("%s%s%s", name, text, comma)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
closeStack()
|
||||
|
||||
|
||||
if i ~= #enums then
|
||||
writeLine()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
closeStack()
|
||||
exportStream("Enums")
|
||||
end
|
||||
@ -859,4 +855,4 @@ end
|
||||
|
||||
if game.Name:sub(1, 9) == "Null.rbxl" then
|
||||
generateAll()
|
||||
end
|
||||
end
|
||||
|
1
Plugins/sourcemap.json
Normal file
1
Plugins/sourcemap.json
Normal 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.
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user