Initial commit.
There's a lot of code at play here, so I haven't tested it yet. A good chunk of the components are available though.
This commit is contained in:
.gitignore
BinaryFormat
Core
DataTypes
Axes.csBrickColor.csCFrame.csColor3.csColorSequence.csColorSequenceKeypoint.csFaces.csNumberRange.csNumberSequence.csNumberSequenceKeypoint.csPathWaypoint.csPhysicalProperties.csRay.csRect.csRegion3.csRegion3int16.csUDim.csUDim2.cs
Utility
Vector2.csVector3.csVector3int16.csProperties
RobloxFileFormat.csprojRobloxFileFormat.slnpackages.config
124
BinaryFormat/BinaryFile.cs
Normal file
124
BinaryFormat/BinaryFile.cs
Normal file
@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Roblox.BinaryFormat.Chunks;
|
||||
|
||||
namespace Roblox.BinaryFormat
|
||||
{
|
||||
public class RobloxBinaryFile : RobloxFile
|
||||
{
|
||||
public const string FileSignature = "<roblox!\x89\xff\x0d\x0a\x1a\x0a";
|
||||
public readonly List<RobloxBinaryChunk> BinaryChunks = new List<RobloxBinaryChunk>();
|
||||
|
||||
public readonly PRNT ParentIds;
|
||||
public readonly META Metadata;
|
||||
|
||||
public readonly Dictionary<int, INST> INSTs = new Dictionary<int, INST>();
|
||||
public readonly List<PROP> PROPs = new List<PROP>();
|
||||
|
||||
public readonly RobloxInstance[] Instances;
|
||||
|
||||
public readonly ushort Version;
|
||||
public readonly uint NumTypes;
|
||||
public readonly uint NumInstances;
|
||||
public readonly long Reserved;
|
||||
|
||||
public RobloxBinaryFile(byte[] contents)
|
||||
{
|
||||
using (MemoryStream file = new MemoryStream(contents))
|
||||
using (RobloxBinaryReader reader = new RobloxBinaryReader(file))
|
||||
{
|
||||
byte[] binSignature = reader.ReadBytes(14);
|
||||
string signature = Encoding.UTF7.GetString(binSignature);
|
||||
|
||||
if (signature != FileSignature)
|
||||
throw new InvalidDataException("Signature does not match RobloxBinaryFile.FileSignature!");
|
||||
|
||||
Version = reader.ReadUInt16();
|
||||
NumTypes = reader.ReadUInt32();
|
||||
NumInstances = reader.ReadUInt32();
|
||||
Reserved = reader.ReadInt64();
|
||||
|
||||
// Begin reading the file chunks.
|
||||
bool reading = true;
|
||||
Instances = new RobloxInstance[NumInstances];
|
||||
BinaryChunks = new List<RobloxBinaryChunk>();
|
||||
|
||||
while (reading)
|
||||
{
|
||||
try
|
||||
{
|
||||
RobloxBinaryChunk chunk = new RobloxBinaryChunk(reader);
|
||||
BinaryChunks.Add(chunk);
|
||||
|
||||
switch (chunk.ChunkType)
|
||||
{
|
||||
case "INST":
|
||||
INST inst = new INST(chunk);
|
||||
INSTs.Add(inst.TypeIndex, inst);
|
||||
break;
|
||||
case "PROP":
|
||||
PROP prop = new PROP(chunk);
|
||||
PROPs.Add(prop);
|
||||
break;
|
||||
case "PRNT":
|
||||
PRNT prnt = new PRNT(chunk);
|
||||
ParentIds = prnt;
|
||||
break;
|
||||
case "META":
|
||||
META meta = new META(chunk);
|
||||
Metadata = meta;
|
||||
break;
|
||||
case "END\0":
|
||||
reading = false;
|
||||
break;
|
||||
default:
|
||||
BinaryChunks.Remove(chunk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
throw new Exception("Unexpected end of file!");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (INST chunk in INSTs.Values)
|
||||
{
|
||||
foreach (int id in chunk.InstanceIds)
|
||||
{
|
||||
RobloxInstance inst = new RobloxInstance();
|
||||
inst.ClassName = chunk.TypeName;
|
||||
Instances[id] = inst;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (PROP prop in PROPs)
|
||||
{
|
||||
INST chunk = INSTs[prop.Index];
|
||||
prop.ReadPropertyValues(chunk, Instances);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ParentIds.NumRelations; i++)
|
||||
{
|
||||
int childId = ParentIds.ChildrenIds[i];
|
||||
int parentId = ParentIds.ParentIds[i];
|
||||
|
||||
RobloxInstance child = Instances[childId];
|
||||
|
||||
if (parentId >= 0)
|
||||
{
|
||||
var parent = Instances[parentId];
|
||||
child.Parent = parent;
|
||||
}
|
||||
else
|
||||
{
|
||||
Trunk.Add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
57
BinaryFormat/Chunk.cs
Normal file
57
BinaryFormat/Chunk.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using LZ4;
|
||||
|
||||
namespace Roblox.BinaryFormat
|
||||
{
|
||||
public class RobloxBinaryChunk
|
||||
{
|
||||
public readonly string ChunkType;
|
||||
|
||||
public readonly int CompressedSize;
|
||||
public readonly byte[] CompressedData;
|
||||
|
||||
public readonly int Size;
|
||||
public readonly int Reserved;
|
||||
public readonly byte[] Data;
|
||||
|
||||
public bool HasCompressedData => (CompressedSize > 0);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ChunkType + " Chunk [" + Size + ']';
|
||||
}
|
||||
|
||||
public RobloxBinaryReader GetReader(string chunkType)
|
||||
{
|
||||
if (ChunkType == chunkType)
|
||||
{
|
||||
MemoryStream buffer = new MemoryStream(Data);
|
||||
return new RobloxBinaryReader(buffer);
|
||||
}
|
||||
|
||||
throw new Exception("Expected " + chunkType + " ChunkType from the input RobloxBinaryChunk");
|
||||
}
|
||||
|
||||
public RobloxBinaryChunk(RobloxBinaryReader reader)
|
||||
{
|
||||
byte[] bChunkType = reader.ReadBytes(4);
|
||||
ChunkType = Encoding.ASCII.GetString(bChunkType);
|
||||
|
||||
CompressedSize = reader.ReadInt32();
|
||||
Size = reader.ReadInt32();
|
||||
Reserved = reader.ReadInt32();
|
||||
|
||||
if (HasCompressedData)
|
||||
{
|
||||
CompressedData = reader.ReadBytes(CompressedSize);
|
||||
Data = LZ4Codec.Decode(CompressedData, 0, CompressedSize, Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Data = reader.ReadBytes(Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
BinaryFormat/ChunkTypes/INST.cs
Normal file
36
BinaryFormat/ChunkTypes/INST.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Roblox.BinaryFormat.Chunks
|
||||
{
|
||||
public class INST
|
||||
{
|
||||
public readonly int TypeIndex;
|
||||
public readonly string TypeName;
|
||||
public readonly bool IsService;
|
||||
public readonly int NumInstances;
|
||||
public readonly int[] InstanceIds;
|
||||
|
||||
public Dictionary<string, PROP> Properties;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return TypeName;
|
||||
}
|
||||
|
||||
public INST(RobloxBinaryChunk chunk)
|
||||
{
|
||||
using (RobloxBinaryReader reader = chunk.GetReader("INST"))
|
||||
{
|
||||
TypeIndex = reader.ReadInt32();
|
||||
TypeName = reader.ReadString();
|
||||
IsService = reader.ReadBoolean();
|
||||
|
||||
NumInstances = reader.ReadInt32();
|
||||
InstanceIds = reader.ReadInstanceIds(NumInstances);
|
||||
}
|
||||
|
||||
Properties = new Dictionary<string, PROP>();
|
||||
}
|
||||
}
|
||||
}
|
27
BinaryFormat/ChunkTypes/META.cs
Normal file
27
BinaryFormat/ChunkTypes/META.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Roblox.BinaryFormat.Chunks
|
||||
{
|
||||
public class META
|
||||
{
|
||||
public int NumEntries;
|
||||
public Dictionary<string, string> Entries;
|
||||
|
||||
public META(RobloxBinaryChunk chunk)
|
||||
{
|
||||
using (RobloxBinaryReader reader = chunk.GetReader("META"))
|
||||
{
|
||||
NumEntries = reader.ReadInt32();
|
||||
Entries = new Dictionary<string, string>(NumEntries);
|
||||
|
||||
for (int i = 0; i < NumEntries; i++)
|
||||
{
|
||||
string key = reader.ReadString();
|
||||
string value = reader.ReadString();
|
||||
Entries.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
BinaryFormat/ChunkTypes/PRNT.cs
Normal file
23
BinaryFormat/ChunkTypes/PRNT.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace Roblox.BinaryFormat.Chunks
|
||||
{
|
||||
public class PRNT
|
||||
{
|
||||
public readonly byte Format;
|
||||
public readonly int NumRelations;
|
||||
|
||||
public readonly int[] ChildrenIds;
|
||||
public readonly int[] ParentIds;
|
||||
|
||||
public PRNT(RobloxBinaryChunk chunk)
|
||||
{
|
||||
using (RobloxBinaryReader reader = chunk.GetReader("PRNT"))
|
||||
{
|
||||
Format = reader.ReadByte();
|
||||
NumRelations = reader.ReadInt32();
|
||||
|
||||
ChildrenIds = reader.ReadInstanceIds(NumRelations);
|
||||
ParentIds = reader.ReadInstanceIds(NumRelations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
422
BinaryFormat/ChunkTypes/PROP.cs
Normal file
422
BinaryFormat/ChunkTypes/PROP.cs
Normal file
@ -0,0 +1,422 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using Roblox.Enums;
|
||||
using Roblox.DataTypes;
|
||||
using Roblox.DataTypes.Utility;
|
||||
|
||||
namespace Roblox.BinaryFormat.Chunks
|
||||
{
|
||||
public class PROP
|
||||
{
|
||||
public int Index { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
|
||||
public readonly PropertyType Type;
|
||||
public RobloxProperty[] Properties => props;
|
||||
|
||||
private RobloxBinaryReader reader;
|
||||
private RobloxProperty[] props;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
Type PropertyType = typeof(PropertyType);
|
||||
return '[' + Enum.GetName(PropertyType, Type) + "] " + Name;
|
||||
}
|
||||
|
||||
public PROP(RobloxBinaryChunk chunk)
|
||||
{
|
||||
reader = chunk.GetReader("PROP");
|
||||
|
||||
Index = reader.ReadInt32();
|
||||
Name = reader.ReadString();
|
||||
|
||||
try
|
||||
{
|
||||
byte propType = reader.ReadByte();
|
||||
Type = (PropertyType)propType;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Type = PropertyType.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadPropertyValues(INST instChunk, RobloxInstance[] instMap)
|
||||
{
|
||||
int[] ids = instChunk.InstanceIds;
|
||||
int instCount = ids.Length;
|
||||
|
||||
props = new RobloxProperty[instCount];
|
||||
|
||||
for (int i = 0; i < instCount; i++)
|
||||
{
|
||||
RobloxProperty prop = new RobloxProperty();
|
||||
prop.Name = Name;
|
||||
prop.Type = Type;
|
||||
|
||||
Properties[i] = prop;
|
||||
instMap[ids[i]].Properties.Add(prop);
|
||||
}
|
||||
|
||||
// Setup some short-hand functions for frequently used actions.
|
||||
var readInstanceInts = new Func<int[]>(() => reader.ReadInts(instCount));
|
||||
var readInstanceFloats = new Func<float[]>(() => reader.ReadFloats(instCount));
|
||||
|
||||
var loadProperties = new Action<Func<int, object>>(read =>
|
||||
{
|
||||
for (int i = 0; i < instCount; i++)
|
||||
{
|
||||
object result = read(i);
|
||||
props[i].Value = result;
|
||||
}
|
||||
});
|
||||
|
||||
// Process the property data based on the property type.
|
||||
switch (Type)
|
||||
{
|
||||
case PropertyType.String:
|
||||
loadProperties(i => reader.ReadString());
|
||||
break;
|
||||
case PropertyType.Bool:
|
||||
loadProperties(i => reader.ReadBoolean());
|
||||
break;
|
||||
case PropertyType.Int:
|
||||
int[] ints = readInstanceInts();
|
||||
loadProperties(i => ints[i]);
|
||||
break;
|
||||
case PropertyType.Float:
|
||||
float[] floats = readInstanceFloats();
|
||||
loadProperties(i => floats[i]);
|
||||
break;
|
||||
case PropertyType.Double:
|
||||
loadProperties(i => reader.ReadDouble());
|
||||
break;
|
||||
case PropertyType.UDim:
|
||||
float[] scales = readInstanceFloats();
|
||||
int[] offsets = readInstanceInts();
|
||||
|
||||
loadProperties(i =>
|
||||
{
|
||||
float scale = scales[i];
|
||||
int offset = offsets[i];
|
||||
return new UDim(scale, offset);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.UDim2:
|
||||
float[] scalesX = readInstanceFloats(), scalesY = readInstanceFloats();
|
||||
int[] offsetsX = readInstanceInts(), offsetsY = readInstanceInts();
|
||||
|
||||
loadProperties(i =>
|
||||
{
|
||||
float scaleX = scalesX[i], scaleY = scalesY[i];
|
||||
int offsetX = offsetsX[i], offsetY = offsetsY[i];
|
||||
return new UDim2(scaleX, offsetX, scaleY, offsetY);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.Ray:
|
||||
loadProperties(i =>
|
||||
{
|
||||
float[] rawOrigin = reader.ReadFloats(3);
|
||||
Vector3 origin = new Vector3(rawOrigin);
|
||||
|
||||
float[] rawDirection = reader.ReadFloats(3);
|
||||
Vector3 direction = new Vector3(rawDirection);
|
||||
|
||||
return new Ray(origin, direction);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.Faces:
|
||||
loadProperties(i =>
|
||||
{
|
||||
byte faces = reader.ReadByte();
|
||||
return (Faces)faces;
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.Axes:
|
||||
loadProperties(i =>
|
||||
{
|
||||
byte axes = reader.ReadByte();
|
||||
return (Axes)axes;
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.BrickColor:
|
||||
int[] brickColors = readInstanceInts();
|
||||
|
||||
loadProperties(i =>
|
||||
{
|
||||
int number = brickColors[i];
|
||||
return BrickColor.New(number);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.Color3:
|
||||
float[] color3_R = readInstanceFloats(),
|
||||
color3_G = readInstanceFloats(),
|
||||
color3_B = readInstanceFloats();
|
||||
|
||||
loadProperties(i =>
|
||||
{
|
||||
float r = color3_R[i],
|
||||
g = color3_G[i],
|
||||
b = color3_B[i];
|
||||
|
||||
return new Color3(r, g, b);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.Vector2:
|
||||
float[] vector2_X = readInstanceFloats(),
|
||||
vector2_Y = readInstanceFloats();
|
||||
|
||||
loadProperties(i =>
|
||||
{
|
||||
float x = vector2_X[i],
|
||||
y = vector2_Y[i];
|
||||
|
||||
return new Vector2(x, y);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.Vector3:
|
||||
float[] vector3_X = readInstanceFloats(),
|
||||
vector3_Y = readInstanceFloats(),
|
||||
vector3_Z = readInstanceFloats();
|
||||
|
||||
loadProperties(i =>
|
||||
{
|
||||
float x = vector3_X[i],
|
||||
y = vector3_Y[i],
|
||||
z = vector3_Z[i];
|
||||
|
||||
return new Vector3(x, y, z);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.CFrame:
|
||||
case PropertyType.Quaternion:
|
||||
// Temporarily load the rotation matrices into their properties.
|
||||
// We'll update them to CFrames once we iterate over the position data.
|
||||
|
||||
loadProperties(i =>
|
||||
{
|
||||
byte orientId = reader.ReadByte();
|
||||
|
||||
if (orientId > 0)
|
||||
{
|
||||
NormalId normX = (NormalId)((orientId - 1) / 6);
|
||||
Vector3 R0 = Vector3.FromNormalId(normX);
|
||||
|
||||
NormalId normY = (NormalId)((orientId - 1) % 6);
|
||||
Vector3 R1 = Vector3.FromNormalId(normY);
|
||||
|
||||
// Compute R2 using the cross product of R0 and R1.
|
||||
Vector3 R2 = R0.Cross(R1);
|
||||
|
||||
// Generate the rotation matrix and return it.
|
||||
return new float[9]
|
||||
{
|
||||
R0.X, R0.Y, R0.Z,
|
||||
R1.X, R1.Y, R1.Z,
|
||||
R2.X, R2.Y, R2.Z,
|
||||
};
|
||||
}
|
||||
else if (Type == PropertyType.Quaternion)
|
||||
{
|
||||
float qx = reader.ReadSingle(),
|
||||
qy = reader.ReadSingle(),
|
||||
qz = reader.ReadSingle(),
|
||||
qw = reader.ReadSingle();
|
||||
|
||||
Quaternion quat = new Quaternion(qx, qy, qz, qw);
|
||||
var rotation = quat.ToCFrame();
|
||||
|
||||
return rotation.GetComponents();
|
||||
}
|
||||
else
|
||||
{
|
||||
float[] matrix = new float[9];
|
||||
|
||||
for (int m = 0; m < 9; m++)
|
||||
{
|
||||
float value = reader.ReadSingle();
|
||||
matrix[m] = value;
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
});
|
||||
|
||||
float[] cframe_X = readInstanceFloats(),
|
||||
cframe_Y = readInstanceFloats(),
|
||||
cframe_Z = readInstanceFloats();
|
||||
|
||||
loadProperties(i =>
|
||||
{
|
||||
float[] matrix = props[i].Value as float[];
|
||||
|
||||
float x = cframe_X[i],
|
||||
y = cframe_Y[i],
|
||||
z = cframe_Z[i];
|
||||
|
||||
float[] position = new float[3] { x, y, z };
|
||||
float[] components = position.Concat(matrix).ToArray();
|
||||
|
||||
return new CFrame(components);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.Enum:
|
||||
uint[] enums = reader.ReadInterwovenValues(instCount, BitConverter.ToUInt32);
|
||||
loadProperties(i => enums[i]);
|
||||
break;
|
||||
case PropertyType.Ref:
|
||||
int[] instIds = reader.ReadInstanceIds(instCount);
|
||||
|
||||
loadProperties(i =>
|
||||
{
|
||||
int instId = instIds[i];
|
||||
return instId >= 0 ? instMap[instId] : null;
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.Vector3int16:
|
||||
loadProperties(i =>
|
||||
{
|
||||
short x = reader.ReadInt16(),
|
||||
y = reader.ReadInt16(),
|
||||
z = reader.ReadInt16();
|
||||
|
||||
return new Vector3int16(x, y, z);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.NumberSequence:
|
||||
loadProperties(i =>
|
||||
{
|
||||
int keys = reader.ReadInt32();
|
||||
var keypoints = new NumberSequenceKeypoint[keys];
|
||||
|
||||
for (int key = 0; key < keys; key++)
|
||||
{
|
||||
float time = reader.ReadSingle(),
|
||||
value = reader.ReadSingle(),
|
||||
envelope = reader.ReadSingle();
|
||||
|
||||
keypoints[key] = new NumberSequenceKeypoint(time, value, envelope);
|
||||
}
|
||||
|
||||
return new NumberSequence(keypoints);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.ColorSequence:
|
||||
loadProperties(i =>
|
||||
{
|
||||
int keys = reader.ReadInt32();
|
||||
var keypoints = new ColorSequenceKeypoint[keys];
|
||||
|
||||
for (int key = 0; key < keys; key++)
|
||||
{
|
||||
float time = reader.ReadSingle(),
|
||||
R = reader.ReadSingle(),
|
||||
G = reader.ReadSingle(),
|
||||
B = reader.ReadSingle(),
|
||||
envelope = reader.ReadSingle(); // unused, but still written
|
||||
|
||||
keypoints[key] = new ColorSequenceKeypoint(time, new Color3(R, G, B));
|
||||
}
|
||||
|
||||
return new ColorSequence(keypoints);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.NumberRange:
|
||||
loadProperties(i =>
|
||||
{
|
||||
float min = reader.ReadSingle();
|
||||
float max = reader.ReadSingle();
|
||||
|
||||
return new NumberRange(min, max);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.Rect:
|
||||
float[] Rect_X0 = readInstanceFloats(),
|
||||
Rect_Y0 = readInstanceFloats(),
|
||||
Rect_X1 = readInstanceFloats(),
|
||||
Rect_Y1 = readInstanceFloats();
|
||||
|
||||
loadProperties(i =>
|
||||
{
|
||||
float x0 = Rect_X0[i],
|
||||
y0 = Rect_Y0[i],
|
||||
x1 = Rect_X1[i],
|
||||
y1 = Rect_Y1[i];
|
||||
|
||||
return new Rect(x0, y0, x1, y1);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.PhysicalProperties:
|
||||
loadProperties(i =>
|
||||
{
|
||||
bool custom = reader.ReadBoolean();
|
||||
|
||||
if (custom)
|
||||
{
|
||||
float density = reader.ReadSingle(),
|
||||
friction = reader.ReadSingle(),
|
||||
elasticity = reader.ReadSingle(),
|
||||
frictionWeight = reader.ReadSingle(),
|
||||
elasticityWeight = reader.ReadSingle();
|
||||
|
||||
return new PhysicalProperties
|
||||
(
|
||||
density,
|
||||
friction,
|
||||
elasticity,
|
||||
frictionWeight,
|
||||
elasticityWeight
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.Color3uint8:
|
||||
byte[] color3uint8_R = reader.ReadBytes(instCount),
|
||||
color3uint8_G = reader.ReadBytes(instCount),
|
||||
color3uint8_B = reader.ReadBytes(instCount);
|
||||
|
||||
loadProperties(i =>
|
||||
{
|
||||
byte r = color3uint8_R[i],
|
||||
g = color3uint8_G[i],
|
||||
b = color3uint8_B[i];
|
||||
|
||||
return Color3.fromRGB(r, g, b);
|
||||
});
|
||||
|
||||
break;
|
||||
case PropertyType.Int64:
|
||||
long[] int64s = reader.ReadInterwovenValues(instCount, (buffer, start) =>
|
||||
{
|
||||
long result = BitConverter.ToInt64(buffer, start);
|
||||
return (long)((ulong)result >> 1) ^ (-(result & 1));
|
||||
});
|
||||
|
||||
loadProperties(i => int64s[i]);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
BinaryFormat/Reader.cs
Normal file
77
BinaryFormat/Reader.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Roblox.DataTypes;
|
||||
|
||||
namespace Roblox.BinaryFormat
|
||||
{
|
||||
public class RobloxBinaryReader : BinaryReader
|
||||
{
|
||||
public RobloxBinaryReader(Stream stream) : base(stream) { }
|
||||
|
||||
public T[] ReadInterwovenValues<T>(int count, Func<byte[], int, T> decode) where T : struct
|
||||
{
|
||||
int bufferSize = Marshal.SizeOf<T>();
|
||||
|
||||
byte[] interwoven = ReadBytes(count * bufferSize);
|
||||
T[] values = new T[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
long unwind = 0;
|
||||
|
||||
for (int weave = 0; weave < bufferSize; weave++)
|
||||
{
|
||||
long splice = interwoven[(weave * count) + i];
|
||||
int strand = (bufferSize - weave - 1) * 8;
|
||||
unwind |= (splice << strand);
|
||||
}
|
||||
|
||||
byte[] buffer = BitConverter.GetBytes(unwind);
|
||||
values[i] = decode(buffer, 0);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public int[] ReadInts(int count)
|
||||
{
|
||||
return ReadInterwovenValues(count, (buffer, start) =>
|
||||
{
|
||||
int value = BitConverter.ToInt32(buffer, start);
|
||||
return (value >> 1) ^ (-(value & 1));
|
||||
});
|
||||
}
|
||||
|
||||
public float[] ReadFloats(int count)
|
||||
{
|
||||
return ReadInterwovenValues(count, (buffer, start) =>
|
||||
{
|
||||
uint u = BitConverter.ToUInt32(buffer, start);
|
||||
uint i = (u >> 1) | (u << 31);
|
||||
|
||||
byte[] b = BitConverter.GetBytes(i);
|
||||
return BitConverter.ToSingle(b, 0);
|
||||
});
|
||||
}
|
||||
|
||||
public int[] ReadInstanceIds(int count)
|
||||
{
|
||||
int[] values = ReadInts(count);
|
||||
|
||||
for (int i = 1; i < count; ++i)
|
||||
values[i] += values[i - 1];
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public override string ReadString()
|
||||
{
|
||||
int length = ReadInt32();
|
||||
byte[] buffer = ReadBytes(length);
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user