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

@ -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.

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;