0.476.0.421371

This commit is contained in:
Max 2021-05-01 17:40:09 -05:00
parent 31eddea24e
commit 009d84f49f
18 changed files with 2513 additions and 2694 deletions

View File

@ -82,31 +82,46 @@ namespace RobloxFiles
switch (chunk.ChunkType) switch (chunk.ChunkType)
{ {
case "INST": case "INST":
{
handler = new INST(); handler = new INST();
break; break;
}
case "PROP": case "PROP":
{
handler = new PROP(); handler = new PROP();
break; break;
}
case "PRNT": case "PRNT":
{
handler = new PRNT(); handler = new PRNT();
break; break;
}
case "META": case "META":
{
handler = new META(); handler = new META();
break; break;
}
case "SSTR": case "SSTR":
{
handler = new SSTR(); handler = new SSTR();
break; break;
}
case "SIGN": case "SIGN":
{
handler = new SIGN(); handler = new SIGN();
break; break;
}
case "END\0": case "END\0":
{
ChunksImpl.Add(chunk); ChunksImpl.Add(chunk);
reading = false; reading = false;
break; break;
}
case string unhandled: case string unhandled:
Console.Error.WriteLine("BinaryRobloxFile - Unhandled chunk-type: {0}!", unhandled); {
LogError($"BinaryRobloxFile - Unhandled chunk-type: {unhandled}!");
break; break;
default: break; }
} }
if (handler != null) if (handler != null)

View File

@ -1,31 +1,32 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using RobloxFiles.Enums; using RobloxFiles.Enums;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
using RobloxFiles.Utility; using RobloxFiles.Utility;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
namespace RobloxFiles.BinaryFormat.Chunks namespace RobloxFiles.BinaryFormat.Chunks
{ {
public class PROP : IBinaryFileChunk public class PROP : IBinaryFileChunk
{ {
private BinaryRobloxFile File; private BinaryRobloxFile File;
public string Name { get; internal set; }
public string Name { get; internal set; }
public int ClassIndex { get; internal set; } public int ClassIndex { get; internal set; }
public string ClassName { get; private set; }
private INST Class => File.Classes[ClassIndex];
public string ClassName => Class?.ClassName;
public PropertyType Type { get; internal set; } public PropertyType Type { get; internal set; }
public byte TypeId internal byte TypeId
{ {
get { return (byte)Type; } get { return (byte)Type; }
internal set { Type = (PropertyType)value; } set { Type = (PropertyType)value; }
} }
public override string ToString() public override string ToString()
@ -35,26 +36,29 @@ namespace RobloxFiles.BinaryFormat.Chunks
public void Load(BinaryRobloxFileReader reader) public void Load(BinaryRobloxFileReader reader)
{ {
BinaryRobloxFile file = reader.File; File = reader.File;
File = file;
ClassIndex = reader.ReadInt32(); ClassIndex = reader.ReadInt32();
Name = reader.ReadString(); Name = reader.ReadString();
try
{
byte propType = reader.ReadByte(); byte propType = reader.ReadByte();
Type = (PropertyType)propType; Type = (PropertyType)propType;
}
catch (EndOfStreamException)
{
RobloxFile.LogError($"Got corrupted PROP chunk (@ {this})!");
return;
}
INST inst = file.Classes[ClassIndex]; var ids = Class.InstanceIds;
ClassName = inst.ClassName; int instCount = Class.NumInstances;
var props = new Property[instCount];
var ids = inst.InstanceIds;
int instCount = inst.NumInstances;
var props = new Property[inst.NumInstances];
for (int i = 0; i < instCount; i++) for (int i = 0; i < instCount; i++)
{ {
int id = ids[i]; int id = ids[i];
Instance instance = file.Instances[id]; Instance instance = File.Instances[id];
if (instance == null) if (instance == null)
{ {
@ -62,7 +66,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
continue; continue;
} }
Property prop = new Property(instance, this); var prop = new Property(instance, this);
props[i] = prop; props[i] = prop;
instance.AddProperty(ref prop); instance.AddProperty(ref prop);
@ -88,6 +92,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
switch (Type) switch (Type)
{ {
case PropertyType.String: case PropertyType.String:
{
readProperties(i => readProperties(i =>
{ {
string value = reader.ReadString(); string value = reader.ReadString();
@ -104,7 +109,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
{ {
case "Tags": case "Tags":
case "AttributesSerialize": case "AttributesSerialize":
{
return buffer; return buffer;
}
default: default:
{ {
Property prop = props[i]; Property prop = props[i];
@ -130,21 +137,31 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Bool: case PropertyType.Bool:
{
readProperties(i => reader.ReadBoolean()); readProperties(i => reader.ReadBoolean());
break; break;
}
case PropertyType.Int: case PropertyType.Int:
{
int[] ints = readInts(); int[] ints = readInts();
readProperties(i => ints[i]); readProperties(i => ints[i]);
break; break;
}
case PropertyType.Float: case PropertyType.Float:
{
float[] floats = readFloats(); float[] floats = readFloats();
readProperties(i => floats[i]); readProperties(i => floats[i]);
break; break;
}
case PropertyType.Double: case PropertyType.Double:
{
readProperties(i => reader.ReadDouble()); readProperties(i => reader.ReadDouble());
break; break;
}
case PropertyType.UDim: case PropertyType.UDim:
{
float[] UDim_Scales = readFloats(); float[] UDim_Scales = readFloats();
int[] UDim_Offsets = readInts(); int[] UDim_Offsets = readInts();
@ -156,7 +173,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.UDim2: case PropertyType.UDim2:
{
float[] UDim2_Scales_X = readFloats(), float[] UDim2_Scales_X = readFloats(),
UDim2_Scales_Y = readFloats(); UDim2_Scales_Y = readFloats();
@ -175,7 +194,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Ray: case PropertyType.Ray:
{
readProperties(i => readProperties(i =>
{ {
float posX = reader.ReadFloat(), float posX = reader.ReadFloat(),
@ -186,14 +207,16 @@ namespace RobloxFiles.BinaryFormat.Chunks
dirY = reader.ReadFloat(), dirY = reader.ReadFloat(),
dirZ = reader.ReadFloat(); dirZ = reader.ReadFloat();
Vector3 origin = new Vector3(posX, posY, posZ); var origin = new Vector3(posX, posY, posZ);
Vector3 direction = new Vector3(dirX, dirY, dirZ); var direction = new Vector3(dirX, dirY, dirZ);
return new Ray(origin, direction); return new Ray(origin, direction);
}); });
break; break;
}
case PropertyType.Faces: case PropertyType.Faces:
{
readProperties(i => readProperties(i =>
{ {
byte faces = reader.ReadByte(); byte faces = reader.ReadByte();
@ -201,7 +224,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Axes: case PropertyType.Axes:
{
readProperties(i => readProperties(i =>
{ {
byte axes = reader.ReadByte(); byte axes = reader.ReadByte();
@ -209,17 +234,21 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.BrickColor: case PropertyType.BrickColor:
{
int[] BrickColorIds = readInts(); int[] BrickColorIds = readInts();
readProperties(i => readProperties(i =>
{ {
int number = BrickColorIds[i]; BrickColor color = BrickColorIds[i];
return BrickColor.FromNumber(number); return color;
}); });
break; break;
}
case PropertyType.Color3: case PropertyType.Color3:
{
float[] Color3_R = readFloats(), float[] Color3_R = readFloats(),
Color3_G = readFloats(), Color3_G = readFloats(),
Color3_B = readFloats(); Color3_B = readFloats();
@ -234,7 +263,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Vector2: case PropertyType.Vector2:
{
float[] Vector2_X = readFloats(), float[] Vector2_X = readFloats(),
Vector2_Y = readFloats(); Vector2_Y = readFloats();
@ -247,7 +278,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Vector3: case PropertyType.Vector3:
{
float[] Vector3_X = readFloats(), float[] Vector3_X = readFloats(),
Vector3_Y = readFloats(), Vector3_Y = readFloats(),
Vector3_Z = readFloats(); Vector3_Z = readFloats();
@ -262,12 +295,26 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.CFrame: case PropertyType.CFrame:
case PropertyType.Quaternion: case PropertyType.Quaternion:
// Temporarily load the rotation matrices into their properties. case PropertyType.OptionalCFrame:
// We'll update them to CFrames once we iterate over the position data. {
float[][] matrices = new float[instCount][]; float[][] matrices = new float[instCount][];
if (Type == PropertyType.OptionalCFrame)
{
byte cframeType = (byte)PropertyType.CFrame;
byte readType = reader.ReadByte();
if (readType != cframeType)
{
RobloxFile.LogError($"Unexpected property type in OptionalCFrame (expected {cframeType}, got {readType})");
readProperties(i => null);
break;
}
}
for (int i = 0; i < instCount; i++) for (int i = 0; i < instCount; i++)
{ {
byte rawOrientId = reader.ReadByte(); byte rawOrientId = reader.ReadByte();
@ -296,11 +343,14 @@ namespace RobloxFiles.BinaryFormat.Chunks
} }
else if (Type == PropertyType.Quaternion) else if (Type == PropertyType.Quaternion)
{ {
float qx = reader.ReadFloat(), qy = reader.ReadFloat(), float qx = reader.ReadFloat(),
qz = reader.ReadFloat(), qw = reader.ReadFloat(); qy = reader.ReadFloat(),
qz = reader.ReadFloat(),
qw = reader.ReadFloat();
Quaternion quaternion = new Quaternion(qx, qy, qz, qw); var quaternion = new Quaternion(qx, qy, qz, qw);
var rotation = quaternion.ToCFrame(); var rotation = quaternion.ToCFrame();
matrices[i] = rotation.GetComponents(); matrices[i] = rotation.GetComponents();
} }
else else
@ -321,7 +371,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
CFrame_Y = readFloats(), CFrame_Y = readFloats(),
CFrame_Z = readFloats(); CFrame_Z = readFloats();
readProperties(i => var CFrames = new CFrame[instCount];
for (int i = 0; i < instCount; i++)
{ {
float[] matrix = matrices[i]; float[] matrix = matrices[i];
@ -345,11 +397,34 @@ namespace RobloxFiles.BinaryFormat.Chunks
components = position.Concat(matrix).ToArray(); components = position.Concat(matrix).ToArray();
} }
return new CFrame(components); CFrames[i] = new CFrame(components);
}); }
if (Type == PropertyType.OptionalCFrame)
{
byte boolType = (byte)PropertyType.Bool;
byte readType = reader.ReadByte();
if (readType != boolType)
{
RobloxFile.LogError($"Unexpected property type in OptionalCFrame (expected {boolType}, got {readType})");
readProperties(i => null);
break; break;
}
for (int i = 0; i < instCount; i++)
{
var cf = CFrames[i];
bool active = reader.ReadBoolean();
CFrames[i] = active ? cf : null;
}
}
readProperties(i => CFrames[i]);
break;
}
case PropertyType.Enum: case PropertyType.Enum:
{
uint[] enums = reader.ReadUInts(instCount); uint[] enums = reader.ReadUInts(instCount);
readProperties(i => readProperties(i =>
@ -366,7 +441,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
if (info == null) if (info == null)
{ {
RobloxFile.LogError($"Enum cast failed for {inst.ClassName}.{Name} using value {value}!"); RobloxFile.LogError($"Enum cast failed for {ClassName}.{Name} using value {value}!");
return value; return value;
} }
@ -374,23 +449,27 @@ namespace RobloxFiles.BinaryFormat.Chunks
} }
catch catch
{ {
RobloxFile.LogError($"Enum cast failed for {inst.ClassName}.{Name} using value {value}!"); RobloxFile.LogError($"Enum cast failed for {ClassName}.{Name} using value {value}!");
return value; return value;
} }
}); });
break; break;
}
case PropertyType.Ref: case PropertyType.Ref:
{
var instIds = reader.ReadInstanceIds(instCount); var instIds = reader.ReadInstanceIds(instCount);
readProperties(i => readProperties(i =>
{ {
int instId = instIds[i]; int instId = instIds[i];
return instId >= 0 ? file.Instances[instId] : null; return instId >= 0 ? File.Instances[instId] : null;
}); });
break; break;
}
case PropertyType.Vector3int16: case PropertyType.Vector3int16:
{
readProperties(i => readProperties(i =>
{ {
short x = reader.ReadInt16(), short x = reader.ReadInt16(),
@ -401,7 +480,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.NumberSequence: case PropertyType.NumberSequence:
{
readProperties(i => readProperties(i =>
{ {
int numKeys = reader.ReadInt32(); int numKeys = reader.ReadInt32();
@ -420,7 +501,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.ColorSequence: case PropertyType.ColorSequence:
{
readProperties(i => readProperties(i =>
{ {
int numKeys = reader.ReadInt32(); int numKeys = reader.ReadInt32();
@ -443,7 +526,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.NumberRange: case PropertyType.NumberRange:
{
readProperties(i => readProperties(i =>
{ {
float min = reader.ReadFloat(); float min = reader.ReadFloat();
@ -453,7 +538,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Rect: case PropertyType.Rect:
{
float[] Rect_X0 = readFloats(), Rect_Y0 = readFloats(), float[] Rect_X0 = readFloats(), Rect_Y0 = readFloats(),
Rect_X1 = readFloats(), Rect_Y1 = readFloats(); Rect_X1 = readFloats(), Rect_Y1 = readFloats();
@ -466,7 +553,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.PhysicalProperties: case PropertyType.PhysicalProperties:
{
readProperties(i => readProperties(i =>
{ {
bool custom = reader.ReadBoolean(); bool custom = reader.ReadBoolean();
@ -493,7 +582,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Color3uint8: case PropertyType.Color3uint8:
{
byte[] Color3uint8_R = reader.ReadBytes(instCount), byte[] Color3uint8_R = reader.ReadBytes(instCount),
Color3uint8_G = reader.ReadBytes(instCount), Color3uint8_G = reader.ReadBytes(instCount),
Color3uint8_B = reader.ReadBytes(instCount); Color3uint8_B = reader.ReadBytes(instCount);
@ -509,26 +600,32 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Int64: case PropertyType.Int64:
long[] Int64s = reader.ReadInterleaved(instCount, (buffer, start) => {
long[] longs = reader.ReadInterleaved(instCount, (buffer, start) =>
{ {
long result = BitConverter.ToInt64(buffer, start); long result = BitConverter.ToInt64(buffer, start);
return (long)((ulong)result >> 1) ^ (-(result & 1)); return (long)((ulong)result >> 1) ^ (-(result & 1));
}); });
readProperties(i => Int64s[i]); readProperties(i => longs[i]);
break; break;
}
case PropertyType.SharedString: case PropertyType.SharedString:
{
uint[] SharedKeys = reader.ReadUInts(instCount); uint[] SharedKeys = reader.ReadUInts(instCount);
readProperties(i => readProperties(i =>
{ {
uint key = SharedKeys[i]; uint key = SharedKeys[i];
return file.SharedStrings[key]; return File.SharedStrings[key];
}); });
break; break;
}
case PropertyType.ProtectedString: case PropertyType.ProtectedString:
{
readProperties(i => readProperties(i =>
{ {
int length = reader.ReadInt32(); int length = reader.ReadInt32();
@ -538,10 +635,12 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
default: default:
RobloxFile.LogError($"Unhandled property type: {Type}!"); {
RobloxFile.LogError($"Unhandled property type: {Type} in {this}!");
break; break;
// }
} }
reader.Dispose(); reader.Dispose();
@ -574,7 +673,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
Name = prop.Name, Name = prop.Name,
Type = prop.Type, Type = prop.Type,
ClassName = inst.ClassName, File = writer.File,
ClassIndex = inst.ClassIndex ClassIndex = inst.ClassIndex
}; };
@ -630,6 +729,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
break; break;
case PropertyType.Bool: case PropertyType.Bool:
{
props.ForEach(prop => props.ForEach(prop =>
{ {
bool value = prop.CastValue<bool>(); bool value = prop.CastValue<bool>();
@ -637,29 +737,27 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Int: case PropertyType.Int:
var ints = new List<int>();
props.ForEach(prop =>
{ {
int value = prop.CastValue<int>(); var ints = props
ints.Add(value); .Select(prop => prop.CastValue<int>())
}); .ToList();
writer.WriteInts(ints); writer.WriteInts(ints);
break; break;
}
case PropertyType.Float: case PropertyType.Float:
var floats = new List<float>();
props.ForEach(prop =>
{ {
float value = prop.CastValue<float>(); var floats = props
floats.Add(value); .Select(prop => prop.CastValue<float>())
}); .ToList();
writer.WriteFloats(floats); writer.WriteFloats(floats);
break; break;
}
case PropertyType.Double: case PropertyType.Double:
{
props.ForEach(prop => props.ForEach(prop =>
{ {
double value = prop.CastValue<double>(); double value = prop.CastValue<double>();
@ -667,7 +765,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.UDim: case PropertyType.UDim:
{
var UDim_Scales = new List<float>(); var UDim_Scales = new List<float>();
var UDim_Offsets = new List<int>(); var UDim_Offsets = new List<int>();
@ -682,7 +782,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
writer.WriteInts(UDim_Offsets); writer.WriteInts(UDim_Offsets);
break; break;
}
case PropertyType.UDim2: case PropertyType.UDim2:
{
var UDim2_Scales_X = new List<float>(); var UDim2_Scales_X = new List<float>();
var UDim2_Scales_Y = new List<float>(); var UDim2_Scales_Y = new List<float>();
@ -707,7 +809,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
writer.WriteInts(UDim2_Offsets_Y); writer.WriteInts(UDim2_Offsets_Y);
break; break;
}
case PropertyType.Ray: case PropertyType.Ray:
{
props.ForEach(prop => props.ForEach(prop =>
{ {
Ray ray = prop.CastValue<Ray>(); Ray ray = prop.CastValue<Ray>();
@ -724,8 +828,10 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Faces: case PropertyType.Faces:
case PropertyType.Axes: case PropertyType.Axes:
{
props.ForEach(prop => props.ForEach(prop =>
{ {
byte value = prop.CastValue<byte>(); byte value = prop.CastValue<byte>();
@ -733,18 +839,19 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.BrickColor: case PropertyType.BrickColor:
var BrickColorIds = new List<int>();
props.ForEach(prop =>
{ {
BrickColor value = prop.CastValue<BrickColor>(); var brickColorIds = props
BrickColorIds.Add(value.Number); .Select(prop => prop.CastValue<BrickColor>())
}); .Select(value => value.Number)
.ToList();
writer.WriteInts(BrickColorIds); writer.WriteInts(brickColorIds);
break; break;
}
case PropertyType.Color3: case PropertyType.Color3:
{
var Color3_R = new List<float>(); var Color3_R = new List<float>();
var Color3_G = new List<float>(); var Color3_G = new List<float>();
var Color3_B = new List<float>(); var Color3_B = new List<float>();
@ -762,7 +869,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
writer.WriteFloats(Color3_B); writer.WriteFloats(Color3_B);
break; break;
}
case PropertyType.Vector2: case PropertyType.Vector2:
{
var Vector2_X = new List<float>(); var Vector2_X = new List<float>();
var Vector2_Y = new List<float>(); var Vector2_Y = new List<float>();
@ -777,7 +886,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
writer.WriteFloats(Vector2_Y); writer.WriteFloats(Vector2_Y);
break; break;
}
case PropertyType.Vector3: case PropertyType.Vector3:
{
var Vector3_X = new List<float>(); var Vector3_X = new List<float>();
var Vector3_Y = new List<float>(); var Vector3_Y = new List<float>();
var Vector3_Z = new List<float>(); var Vector3_Z = new List<float>();
@ -795,12 +906,18 @@ namespace RobloxFiles.BinaryFormat.Chunks
writer.WriteFloats(Vector3_Z); writer.WriteFloats(Vector3_Z);
break; break;
}
case PropertyType.CFrame: case PropertyType.CFrame:
case PropertyType.Quaternion: case PropertyType.Quaternion:
case PropertyType.OptionalCFrame:
{
var CFrame_X = new List<float>(); var CFrame_X = new List<float>();
var CFrame_Y = new List<float>(); var CFrame_Y = new List<float>();
var CFrame_Z = new List<float>(); var CFrame_Z = new List<float>();
if (Type == PropertyType.OptionalCFrame)
writer.Write((byte)PropertyType.CFrame);
props.ForEach(prop => props.ForEach(prop =>
{ {
CFrame value = null; CFrame value = null;
@ -810,6 +927,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
else else
value = prop.CastValue<CFrame>(); value = prop.CastValue<CFrame>();
if (value == null)
value = new CFrame();
Vector3 pos = value.Position; Vector3 pos = value.Position;
CFrame_X.Add(pos.X); CFrame_X.Add(pos.X);
CFrame_Y.Add(pos.Y); CFrame_Y.Add(pos.Y);
@ -845,27 +965,47 @@ namespace RobloxFiles.BinaryFormat.Chunks
writer.WriteFloats(CFrame_Y); writer.WriteFloats(CFrame_Y);
writer.WriteFloats(CFrame_Z); writer.WriteFloats(CFrame_Z);
if (Type == PropertyType.OptionalCFrame)
{
writer.Write((byte)PropertyType.Bool);
props.ForEach(prop =>
{
if (prop.Value is null)
{
writer.Write(false);
return;
}
writer.Write(true);
});
}
break; break;
}
case PropertyType.Enum: case PropertyType.Enum:
var Enums = new List<uint>(); {
var enums = new List<uint>();
props.ForEach(prop => props.ForEach(prop =>
{ {
if (prop.Value is uint raw) if (prop.Value is uint raw)
{ {
Enums.Add(raw); enums.Add(raw);
return; return;
} }
int signed = (int)prop.Value; int signed = (int)prop.Value;
uint value = (uint)signed; uint value = (uint)signed;
Enums.Add(value); enums.Add(value);
}); });
writer.WriteInterleaved(Enums); writer.WriteInterleaved(enums);
break; break;
}
case PropertyType.Ref: case PropertyType.Ref:
{
var InstanceIds = new List<int>(); var InstanceIds = new List<int>();
props.ForEach(prop => props.ForEach(prop =>
@ -883,7 +1023,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
writer.WriteInstanceIds(InstanceIds); writer.WriteInstanceIds(InstanceIds);
break; break;
}
case PropertyType.Vector3int16: case PropertyType.Vector3int16:
{
props.ForEach(prop => props.ForEach(prop =>
{ {
Vector3int16 value = prop.CastValue<Vector3int16>(); Vector3int16 value = prop.CastValue<Vector3int16>();
@ -893,7 +1035,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.NumberSequence: case PropertyType.NumberSequence:
{
props.ForEach(prop => props.ForEach(prop =>
{ {
NumberSequence value = prop.CastValue<NumberSequence>(); NumberSequence value = prop.CastValue<NumberSequence>();
@ -910,7 +1054,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.ColorSequence: case PropertyType.ColorSequence:
{
props.ForEach(prop => props.ForEach(prop =>
{ {
ColorSequence value = prop.CastValue<ColorSequence>(); ColorSequence value = prop.CastValue<ColorSequence>();
@ -932,7 +1078,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.NumberRange: case PropertyType.NumberRange:
{
props.ForEach(prop => props.ForEach(prop =>
{ {
NumberRange value = prop.CastValue<NumberRange>(); NumberRange value = prop.CastValue<NumberRange>();
@ -941,7 +1089,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Rect: case PropertyType.Rect:
{
var Rect_X0 = new List<float>(); var Rect_X0 = new List<float>();
var Rect_Y0 = new List<float>(); var Rect_Y0 = new List<float>();
@ -968,7 +1118,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
writer.WriteFloats(Rect_Y1); writer.WriteFloats(Rect_Y1);
break; break;
}
case PropertyType.PhysicalProperties: case PropertyType.PhysicalProperties:
{
props.ForEach(prop => props.ForEach(prop =>
{ {
bool custom = (prop.Value != null); bool custom = (prop.Value != null);
@ -988,7 +1140,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
}
case PropertyType.Color3uint8: case PropertyType.Color3uint8:
{
var Color3uint8_R = new List<byte>(); var Color3uint8_R = new List<byte>();
var Color3uint8_G = new List<byte>(); var Color3uint8_G = new List<byte>();
var Color3uint8_B = new List<byte>(); var Color3uint8_B = new List<byte>();
@ -1011,23 +1165,27 @@ namespace RobloxFiles.BinaryFormat.Chunks
writer.Write(bBuffer); writer.Write(bBuffer);
break; break;
}
case PropertyType.Int64: case PropertyType.Int64:
var Int64s = new List<long>(); {
var longs = new List<long>();
props.ForEach(prop => props.ForEach(prop =>
{ {
long value = prop.CastValue<long>(); long value = prop.CastValue<long>();
Int64s.Add(value); longs.Add(value);
}); });
writer.WriteInterleaved(Int64s, value => writer.WriteInterleaved(longs, value =>
{ {
// Move the sign bit to the front. // Move the sign bit to the front.
return (value << 1) ^ (value >> 63); return (value << 1) ^ (value >> 63);
}); });
break; break;
}
case PropertyType.SharedString: case PropertyType.SharedString:
{
var sharedKeys = new List<uint>(); var sharedKeys = new List<uint>();
SSTR sstr = file.SSTR; SSTR sstr = file.SSTR;
@ -1051,7 +1209,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
if (!sstr.Lookup.ContainsKey(key)) if (!sstr.Lookup.ContainsKey(key))
{ {
uint id = (uint)(sstr.Lookup.Count); uint id = (uint)sstr.Lookup.Count;
sstr.Strings.Add(id, shared); sstr.Strings.Add(id, shared);
sstr.Lookup.Add(key, id); sstr.Lookup.Add(key, id);
} }
@ -1062,7 +1220,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
writer.WriteInterleaved(sharedKeys); writer.WriteInterleaved(sharedKeys);
break; break;
}
case PropertyType.ProtectedString: case PropertyType.ProtectedString:
{
props.ForEach(prop => props.ForEach(prop =>
{ {
var protect = prop.CastValue<ProtectedString>(); var protect = prop.CastValue<ProtectedString>();
@ -1073,7 +1233,12 @@ namespace RobloxFiles.BinaryFormat.Chunks
}); });
break; break;
default: break; }
default:
{
RobloxFile.LogError($"Unhandled property type: {Type} in {this}!");
break;
}
} }
} }

View File

@ -1,5 +1,5 @@
// Auto-generated list of creatable Roblox classes. // Auto-generated list of creatable Roblox classes.
// Updated as of 0.474.0.420553 // Updated as of 0.476.0.421371
using System; using System;
@ -7,10 +7,6 @@ using RobloxFiles.DataTypes;
using RobloxFiles.Enums; using RobloxFiles.Enums;
using RobloxFiles.Utility; using RobloxFiles.Utility;
#pragma warning disable CA1041 // Provide ObsoleteAttribute message
#pragma warning disable CA1051 // Do not declare visible instance fields
#pragma warning disable CA1707 // Identifiers should not contain underscores
#pragma warning disable CA1716 // Identifiers should not match keywords
#pragma warning disable IDE1006 // Naming Styles #pragma warning disable IDE1006 // Naming Styles
namespace RobloxFiles namespace RobloxFiles
@ -2095,6 +2091,14 @@ namespace RobloxFiles
} }
} }
public class MemoryStoreService : Instance
{
public MemoryStoreService()
{
IsService = true;
}
}
public class Message : Instance public class Message : Instance
{ {
public string Text = ""; public string Text = "";
@ -2402,7 +2406,9 @@ namespace RobloxFiles
public CFrame ModelMeshCFrame = new CFrame(); public CFrame ModelMeshCFrame = new CFrame();
public SharedString ModelMeshData = SharedString.FromBase64("yuZpQdnvvUBOTYh1jqZ2cA=="); public SharedString ModelMeshData = SharedString.FromBase64("yuZpQdnvvUBOTYh1jqZ2cA==");
public Vector3 ModelMeshSize = new Vector3(); public Vector3 ModelMeshSize = new Vector3();
public bool NeedsPivotMigration;
public BasePart PrimaryPart; public BasePart PrimaryPart;
public CFrame WorldPivotData;
} }
public class Actor : Model public class Actor : Model
@ -2421,6 +2427,7 @@ namespace RobloxFiles
} }
public bool AllowThirdPartySales; public bool AllowThirdPartySales;
public ClientAnimatorThrottlingMode ClientAnimatorThrottling = ClientAnimatorThrottlingMode.Default;
public string CollisionGroups = "Default^0^1"; public string CollisionGroups = "Default^0^1";
public Camera CurrentCamera; public Camera CurrentCamera;
public double DistributedGameTime; public double DistributedGameTime;
@ -2532,10 +2539,11 @@ namespace RobloxFiles
IsService = true; IsService = true;
} }
public string DEPRECATED_SerializedEmulatedPolicyInfo = "";
public string EmulatedCountryCode = ""; public string EmulatedCountryCode = "";
public string EmulatedGameLocale = ""; public string EmulatedGameLocale = "";
public bool PlayerEmulationEnabled; public bool PlayerEmulationEnabled;
public string SerializedEmulatedPolicyInfo = ""; public byte[] SerializedEmulatedPolicyInfo = Array.Empty<byte>();
} }
public class Players : Instance public class Players : Instance
@ -2678,6 +2686,14 @@ namespace RobloxFiles
public int MaxPromptsVisible = 16; public int MaxPromptsVisible = 16;
} }
public class PublishService : Instance
{
public PublishService()
{
IsService = true;
}
}
public class RbxAnalyticsService : Instance public class RbxAnalyticsService : Instance
{ {
public RbxAnalyticsService() public RbxAnalyticsService()

View File

@ -1,5 +1,5 @@
// Auto-generated list of Roblox enums. // Auto-generated list of Roblox enums.
// Updated as of 0.473.0.420291 // Updated as of 0.476.0.421371
namespace RobloxFiles.Enums namespace RobloxFiles.Enums
{ {
@ -124,6 +124,13 @@ namespace RobloxFiles.Enums
Orbital Orbital
} }
public enum ClientAnimatorThrottlingMode
{
Default,
Disabled,
Enabled
}
public enum DevCameraOcclusionMode public enum DevCameraOcclusionMode
{ {
Zoom, Zoom,

Binary file not shown.

View File

@ -377,6 +377,8 @@ return
ModelMeshCFrame = "CFrame"; ModelMeshCFrame = "CFrame";
ModelMeshData = "SharedString"; ModelMeshData = "SharedString";
ModelMeshSize = "Vector3"; ModelMeshSize = "Vector3";
NeedsPivotMigration = "bool";
WorldPivotData = "OptionalCFrame";
}; };
}; };

View File

@ -217,6 +217,7 @@ local formatLinks =
["Instance"] = "Null"; ["Instance"] = "Null";
["Color3uint8"] = "Color3"; ["Color3uint8"] = "Color3";
["OptionalCFrame"] = "Null";
["ProtectedString"] = "String"; ["ProtectedString"] = "String";
} }
@ -357,10 +358,10 @@ local function generateClasses()
writeLine("using RobloxFiles.Utility;") writeLine("using RobloxFiles.Utility;")
writeLine() writeLine()
writeLine("#pragma warning disable CA1041 // Provide ObsoleteAttribute message") -- writeLine("#pragma warning disable CA1041 // Provide ObsoleteAttribute message")
writeLine("#pragma warning disable CA1051 // Do not declare visible instance fields") -- writeLine("#pragma warning disable CA1051 // Do not declare visible instance fields")
writeLine("#pragma warning disable CA1707 // Identifiers should not contain underscores") -- writeLine("#pragma warning disable CA1707 // Identifiers should not contain underscores")
writeLine("#pragma warning disable CA1716 // Identifiers should not match keywords") -- writeLine("#pragma warning disable CA1716 // Identifiers should not match keywords")
writeLine("#pragma warning disable IDE1006 // Naming Styles") writeLine("#pragma warning disable IDE1006 // Naming Styles")
writeLine() writeLine()
@ -640,6 +641,10 @@ local function generateClasses()
writeLine("[Obsolete]") writeLine("[Obsolete]")
end end
if valueType == "OptionalCFrame" then
valueType = "CFrame"
end
writeLine("public %s %s%s;", valueType, name, default) writeLine("public %s %s%s;", valueType, name, default)
if propTags.Deprecated and j ~= #propNames then if propTags.Deprecated and j ~= #propNames then

View File

@ -98,6 +98,7 @@
<Compile Include="Generated\Classes.cs" /> <Compile Include="Generated\Classes.cs" />
<Compile Include="Generated\Enums.cs" /> <Compile Include="Generated\Enums.cs" />
<Compile Include="Interfaces\IAttributeToken.cs" /> <Compile Include="Interfaces\IAttributeToken.cs" />
<Compile Include="Tokens\OptionalCFrame.cs" />
<Compile Include="Tree\Attributes.cs" /> <Compile Include="Tree\Attributes.cs" />
<Compile Include="Tree\Property.cs" /> <Compile Include="Tree\Property.cs" />
<Compile Include="Tree\Instance.cs" /> <Compile Include="Tree\Instance.cs" />

Binary file not shown.

View File

@ -30,6 +30,23 @@ namespace RobloxFiles.Tokens
return new CFrame(components); return new CFrame(components);
} }
public static void WriteCFrame(Property prop, XmlDocument doc, XmlNode node)
{
CFrame cf = prop.CastValue<CFrame>();
float[] components = cf.GetComponents();
for (int i = 0; i < 12; i++)
{
string coordName = Coords[i];
float coordValue = components[i];
XmlElement coord = doc.CreateElement(coordName);
coord.InnerText = coordValue.ToInvariantString();
node.AppendChild(coord);
}
}
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
CFrame result = ReadCFrame(token); CFrame result = ReadCFrame(token);
@ -46,19 +63,7 @@ namespace RobloxFiles.Tokens
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{ {
CFrame cf = prop.CastValue<CFrame>(); WriteCFrame(prop, doc, node);
float[] components = cf.GetComponents();
for (int i = 0; i < 12; i++)
{
string coordName = Coords[i];
float coordValue = components[i];
XmlElement coord = doc.CreateElement(coordName);
coord.InnerText = coordValue.ToInvariantString();
node.AppendChild(coord);
}
} }
} }
} }

33
Tokens/OptionalCFrame.cs Normal file
View File

@ -0,0 +1,33 @@
using System.Xml;
using RobloxFiles.DataTypes;
namespace RobloxFiles.Tokens
{
public class OptionalCFrameToken : IXmlPropertyToken
{
public string XmlPropertyToken => "OptionalCoordinateFrame";
public bool ReadProperty(Property prop, XmlNode token)
{
XmlNode first = token.FirstChild;
prop.Type = PropertyType.OptionalCFrame;
if (first?.Name == "CFrame")
prop.Value = CFrameToken.ReadCFrame(first);
return true;
}
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
{
CFrame value = prop.CastValue<CFrame>();
if (value != null)
{
XmlElement cfNode = doc.CreateElement("CFrame");
CFrameToken.WriteCFrame(prop, doc, cfNode);
node.AppendChild(cfNode);
}
}
}
}

View File

@ -13,8 +13,8 @@ namespace RobloxFiles.Tokens
public bool ReadProperty(Property prop, XmlNode token) public bool ReadProperty(Property prop, XmlNode token)
{ {
ProtectedString contents = token.InnerText; ProtectedString contents = token.InnerText;
prop.Type = PropertyType.ProtectedString; prop.Type = PropertyType.String;
prop.Value = contents; prop.Value = contents.ToString();
return true; return true;
} }

View File

@ -87,7 +87,6 @@ namespace RobloxFiles
{ {
var attributeSupport = new Dictionary<AttributeType, Tokenizer>(); var attributeSupport = new Dictionary<AttributeType, Tokenizer>();
var supportedTypes = new Dictionary<Type, AttributeType>(); var supportedTypes = new Dictionary<Type, AttributeType>();
var assembly = Assembly.GetExecutingAssembly(); var assembly = Assembly.GetExecutingAssembly();
var handlerTypes = var handlerTypes =
@ -247,7 +246,7 @@ namespace RobloxFiles
internal Attributes(MemoryStream stream) internal Attributes(MemoryStream stream)
{ {
using (BinaryReader reader = new BinaryReader(stream)) using (var reader = new BinaryReader(stream))
Initialize(reader); Initialize(reader);
stream.Dispose(); stream.Dispose();

View File

@ -1,12 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using RobloxFiles.BinaryFormat;
using RobloxFiles.BinaryFormat.Chunks; using RobloxFiles.BinaryFormat.Chunks;
using RobloxFiles.DataTypes; using RobloxFiles.DataTypes;
using RobloxFiles.Utility; using RobloxFiles.Utility;
@ -42,14 +39,14 @@ namespace RobloxFiles
Color3uint8, Color3uint8,
Int64, Int64,
SharedString, SharedString,
ProtectedString ProtectedString,
OptionalCFrame
} }
public class Property public class Property
{ {
public string Name { get; internal set; } public string Name { get; internal set; }
public Instance Instance { get; internal set; } public Instance Instance { get; internal set; }
public PropertyType Type { get; internal set; } public PropertyType Type { get; internal set; }
public string XmlToken { get; internal set; } public string XmlToken { get; internal set; }
@ -59,7 +56,9 @@ namespace RobloxFiles
internal static BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.IgnoreCase; internal static BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.IgnoreCase;
// !! FIXME: Map typeof(ProtectedString) to PropertyType.ProtectedString when binary files are allowed to read it. // TODO: Map typeof(ProtectedString) to PropertyType.ProtectedString
// if binary files are ever publically allowed to read it.
public static readonly IReadOnlyDictionary<Type, PropertyType> Types = new Dictionary<Type, PropertyType>() public static readonly IReadOnlyDictionary<Type, PropertyType> Types = new Dictionary<Type, PropertyType>()
{ {
{ typeof(Axes), PropertyType.Axes }, { typeof(Axes), PropertyType.Axes },
@ -103,8 +102,7 @@ namespace RobloxFiles
RawBuffer = RawValue as byte[]; RawBuffer = RawValue as byte[];
return; return;
} }
else if (RawValue is SharedString sharedString)
if (RawValue is SharedString sharedString)
{ {
if (sharedString != null) if (sharedString != null)
{ {
@ -112,8 +110,7 @@ namespace RobloxFiles
return; return;
} }
} }
else if (RawValue is ProtectedString protectedString)
if (RawValue is ProtectedString protectedString)
{ {
if (protectedString != null) if (protectedString != null)
{ {
@ -128,6 +125,7 @@ namespace RobloxFiles
switch (Type) switch (Type)
{ {
case PropertyType.Int: case PropertyType.Int:
{
if (Value is long) if (Value is long)
{ {
Type = PropertyType.Int64; Type = PropertyType.Int64;
@ -136,19 +134,27 @@ namespace RobloxFiles
RawBuffer = BitConverter.GetBytes((int)Value); RawBuffer = BitConverter.GetBytes((int)Value);
break; break;
}
case PropertyType.Bool: case PropertyType.Bool:
{
RawBuffer = BitConverter.GetBytes((bool)Value); RawBuffer = BitConverter.GetBytes((bool)Value);
break; break;
}
case PropertyType.Int64: case PropertyType.Int64:
{
RawBuffer = BitConverter.GetBytes((long)Value); RawBuffer = BitConverter.GetBytes((long)Value);
break; break;
}
case PropertyType.Float: case PropertyType.Float:
{
RawBuffer = BitConverter.GetBytes((float)Value); RawBuffer = BitConverter.GetBytes((float)Value);
break; break;
}
case PropertyType.Double: case PropertyType.Double:
{
RawBuffer = BitConverter.GetBytes((double)Value); RawBuffer = BitConverter.GetBytes((double)Value);
break; break;
default: break; }
} }
} }
@ -163,8 +169,7 @@ namespace RobloxFiles
if (typeName == Name) if (typeName == Name)
{ {
FieldInfo directField = instType FieldInfo directField = instType.GetFields()
.GetFields()
.Where(field => field.Name.StartsWith(Name, StringComparison.InvariantCulture)) .Where(field => field.Name.StartsWith(Name, StringComparison.InvariantCulture))
.Where(field => field.DeclaringType == instType) .Where(field => field.DeclaringType == instType)
.FirstOrDefault(); .FirstOrDefault();
@ -276,6 +281,7 @@ namespace RobloxFiles
get get
{ {
// Improvise what the buffer should be if this is a primitive. // Improvise what the buffer should be if this is a primitive.
if (RawBuffer == null && Value != null) if (RawBuffer == null && Value != null)
ImproviseRawBuffer(); ImproviseRawBuffer();

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -65,6 +65,10 @@ namespace RobloxFiles
if (child.Name == "Item") if (child.Name == "Item")
{ {
Instance item = XmlRobloxFileReader.ReadInstance(child, this); Instance item = XmlRobloxFileReader.ReadInstance(child, this);
if (item == null)
continue;
item.Parent = this; item.Parent = this;
} }
else if (child.Name == "SharedStrings") else if (child.Name == "SharedStrings")