Roblox-File-Format/BinaryFormat/BinaryRobloxFile.cs

223 lines
7.9 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using RobloxFiles.BinaryFormat;
using RobloxFiles.BinaryFormat.Chunks;
using RobloxFiles.DataTypes;
namespace RobloxFiles
{
2019-05-19 04:44:51 +00:00
public class BinaryRobloxFile : RobloxFile
{
// Header Specific
public const string MagicHeader = "<roblox!\x89\xff\x0d\x0a\x1a\x0a";
public ushort Version { get; internal set; }
public uint NumClasses { get; internal set; }
public uint NumInstances { get; internal set; }
public long Reserved { get; internal set; }
// Runtime Specific
internal List<BinaryRobloxFileChunk> ChunksImpl = new List<BinaryRobloxFileChunk>();
public IReadOnlyList<BinaryRobloxFileChunk> Chunks => ChunksImpl;
public override string ToString() => GetType().Name;
public Instance[] Instances { get; internal set; }
public INST[] Classes { get; internal set; }
2020-08-17 05:33:59 +00:00
internal META META;
internal SSTR SSTR;
internal SIGN SIGN;
public bool HasMetadata => (META != null);
public Dictionary<string, string> Metadata => META?.Data;
public bool HasSharedStrings => (SSTR != null);
public IReadOnlyDictionary<uint, SharedString> SharedStrings => SSTR?.Strings;
2019-05-19 04:44:51 +00:00
public bool HasSignatures => (SIGN != null);
public IReadOnlyList<Signature> Signatures => SIGN?.Signatures;
public BinaryRobloxFile()
2019-05-19 04:44:51 +00:00
{
2020-09-14 16:20:34 +00:00
Name = "Bin:";
Referent = "-1";
2020-09-14 16:20:34 +00:00
ParentLocked = true;
2019-05-19 04:44:51 +00:00
}
2019-05-19 04:44:51 +00:00
protected override void ReadFile(byte[] contents)
{
using (MemoryStream file = new MemoryStream(contents))
using (BinaryRobloxFileReader reader = new BinaryRobloxFileReader(this, file))
{
// Verify the signature of the file.
byte[] binSignature = reader.ReadBytes(14);
string signature = Encoding.UTF7.GetString(binSignature);
if (signature != MagicHeader)
throw new InvalidDataException("Provided file's signature does not match BinaryRobloxFile.MagicHeader!");
// Read header data.
Version = reader.ReadUInt16();
NumClasses = reader.ReadUInt32();
NumInstances = reader.ReadUInt32();
Reserved = reader.ReadInt64();
// Begin reading the file chunks.
bool reading = true;
Classes = new INST[NumClasses];
Instances = new Instance[NumInstances];
while (reading)
{
try
{
var chunk = new BinaryRobloxFileChunk(reader);
IBinaryFileChunk handler = null;
switch (chunk.ChunkType)
{
case "INST":
handler = new INST();
break;
case "PROP":
handler = new PROP();
break;
case "PRNT":
handler = new PRNT();
break;
case "META":
handler = new META();
break;
case "SSTR":
handler = new SSTR();
break;
case "SIGN":
handler = new SIGN();
break;
case "END\0":
ChunksImpl.Add(chunk);
reading = false;
break;
case string unhandled:
2020-08-17 05:33:59 +00:00
Console.Error.WriteLine("BinaryRobloxFile - Unhandled chunk-type: {0}!", unhandled);
break;
default: break;
}
if (handler != null)
{
2020-08-17 05:33:59 +00:00
using (var readBuffer = new MemoryStream(chunk.Data))
{
using (var dataReader = new BinaryRobloxFileReader(this, readBuffer))
{
chunk.Handler = handler;
handler.Load(dataReader);
}
}
ChunksImpl.Add(chunk);
}
}
catch (EndOfStreamException)
{
throw new Exception("Unexpected end of file!");
}
}
}
}
2019-05-19 04:44:51 +00:00
public override void Save(Stream stream)
{
//////////////////////////////////////////////////////////////////////////
// Generate the chunk data.
//////////////////////////////////////////////////////////////////////////
2020-08-17 05:33:59 +00:00
using (var workBuffer = new MemoryStream())
using (var writer = new BinaryRobloxFileWriter(this, workBuffer))
{
// Clear the existing data.
Referent = "-1";
ChunksImpl.Clear();
2020-08-17 05:33:59 +00:00
NumInstances = 0;
NumClasses = 0;
2019-06-11 01:27:57 +00:00
SSTR = null;
2020-08-17 05:33:59 +00:00
// Recursively capture all instances and classes.
writer.RecordInstances(Children);
// Apply the recorded instances and classes.
writer.ApplyClassMap();
// Write the INST chunks.
foreach (INST inst in Classes)
writer.SaveChunk(inst);
// Write the PROP chunks.
foreach (INST inst in Classes)
{
var props = PROP.CollectProperties(writer, inst);
foreach (string propName in props.Keys)
{
PROP prop = props[propName];
writer.SaveChunk(prop);
}
}
// Write the PRNT chunk.
var parents = new PRNT();
writer.SaveChunk(parents);
2020-08-17 05:33:59 +00:00
// Write the SSTR chunk.
if (HasSharedStrings)
writer.SaveChunk(SSTR, 0);
2019-06-11 01:27:57 +00:00
// Write the META chunk.
if (HasMetadata)
writer.SaveChunk(META, 0);
// Write the SIGN chunk.
if (HasSignatures)
writer.SaveChunk(SIGN);
2019-06-11 01:27:57 +00:00
// Write the END chunk.
writer.WriteChunk("END", "</roblox>");
}
//////////////////////////////////////////////////////////////////////////
// Write the chunk buffers with the header data
//////////////////////////////////////////////////////////////////////////
using (BinaryWriter writer = new BinaryWriter(stream))
{
stream.Position = 0;
stream.SetLength(0);
writer.Write(MagicHeader
.Select(ch => (byte)ch)
.ToArray());
writer.Write(Version);
writer.Write(NumClasses);
writer.Write(NumInstances);
writer.Write(Reserved);
foreach (BinaryRobloxFileChunk chunk in Chunks)
{
if (chunk.HasWriteBuffer)
{
byte[] writeBuffer = chunk.WriteBuffer;
writer.Write(writeBuffer);
}
}
}
}
}
2019-06-08 05:18:00 +00:00
}