Editor/RbxlReader/PlaceBinary.cs
#pragma warning disable SYSLIB0001 // Type or member is obsolete. Need to be using UTF-7
using System.ComponentModel.Design.Serialization;
using System.Text;
using RbxlReader.Chunks;
using RbxlReader.Instances;
namespace RbxlReader;
/// <summary>
/// Low-level .rbxl file representation
/// </summary>
public class PlaceBinary {
public static readonly string MAGIC_HEADER = "<roblox!\x89\xff\x0d\x0a\x1a\x0a";
public ushort Version {get; protected set;}
public int NumberClasses {get; protected set;}
public int NumberInstances {get; protected set;}
public long Reserved {get; protected set;}
/// <summary>
/// Raw chunk data
/// </summary>
public List<BinaryChunkData> Chunks = new();
/// <summary>
/// ChunkInfo instance struct, containing chunk information such as INST classes, or PROP properties.
/// </summary>
public ChunkStruct ChunkInfo = new();
/// <summary>
/// Empty Instance that serves as a root for place's entire hierarchy. Throws an error if you try to reference Root's parent (does not exist)
/// </summary>
public Instance Root {get;} = new("Root");
public Instance? Workspace => Root.FindFirstChildOfClass("Workspace");
public INST[] IdToINST {get; protected set;}
public Instance[] IdToInstance {get; protected set;}
/// <summary>
/// Create class and parse from file
/// </summary>
/// <param name="path"></param>
public PlaceBinary(string path) {
Root.Rbxl = this;
using (FileStream file = File.Open(path, FileMode.Open)) {
RbxlBinaryReader reader = new(file);
parseHeader(file, reader);
IdToINST = new INST[NumberClasses];
IdToInstance = new Instance[NumberInstances];
// parse chunks
bool endReached = false;
while (!endReached) {
BinaryChunkData chunk = new(reader, this);
Chunks.Add(chunk);
evaluateChunk(chunk);
if (chunk.ChunkName == "END\0") endReached = true;
}
}
}
/// <summary>
/// Create empty PlaceBinary
/// </summary>
public PlaceBinary() {
Root.Rbxl = this;
IdToINST = new INST[1];
IdToInstance = new Instance[1];
}
private void parseHeader(Stream stream, RbxlBinaryReader reader) {
byte[] signature = reader.ReadBytes(14);
string signatureString = Encoding.UTF7.GetString(signature);
if (signatureString != MAGIC_HEADER) throw new InvalidDataException("Data signature does not match file header!");
Version = reader.ReadUInt16();
NumberClasses = reader.ReadInt32();
NumberInstances = reader.ReadInt32();
Reserved = reader.ReadInt64();
}
private IChunkInfo? evaluateChunk(BinaryChunkData chunk) {
switch(chunk.ChunkName) {
case "META":
META info = new(chunk);
ChunkInfo.META = info;
return info;
case "INST":
INST inst = new(chunk);
ChunkInfo.INST.Add(inst.ClassName, inst);
IdToINST[inst.Index] = inst;
foreach (KeyValuePair<int, Instance> keyval in inst.LinkedInstances) {
IdToInstance[keyval.Key] = keyval.Value;
keyval.Value.Rbxl = this;
}
return inst;
case "PROP":
PROP propchunk = new(chunk);
ChunkInfo.PROP.Add(propchunk);
return propchunk;
case "PRNT":
PRNT parent = new(chunk);
ChunkInfo.PRNT = parent;
return parent;
default:
return null;
}
}
}