2019-06-08 03:43:28 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
using System.Linq;
|
2019-06-08 03:43:28 +00:00
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
|
|
using RobloxFiles.BinaryFormat.Chunks;
|
|
|
|
|
|
|
|
|
|
namespace RobloxFiles.BinaryFormat
|
|
|
|
|
{
|
|
|
|
|
public class BinaryRobloxFileWriter : BinaryWriter
|
|
|
|
|
{
|
|
|
|
|
public IBinaryFileChunk Chunk { get; private set; }
|
|
|
|
|
public bool WritingChunk { get; private set; }
|
|
|
|
|
|
|
|
|
|
public string ChunkType { get; private set; }
|
|
|
|
|
public long ChunkStart { get; private set; }
|
|
|
|
|
|
|
|
|
|
public readonly BinaryRobloxFile File;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
// Dictionary mapping ClassNames to their INST chunks.
|
|
|
|
|
private readonly Dictionary<string, INST> ClassMap;
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
// Instances in parent->child order
|
2020-07-13 01:19:30 +00:00
|
|
|
|
private readonly List<Instance> Instances;
|
2019-06-08 03:43:28 +00:00
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
// Instances in child->parent order
|
2020-07-13 01:19:30 +00:00
|
|
|
|
internal List<Instance> PostInstances { get; private set; }
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2020-08-17 05:33:59 +00:00
|
|
|
|
public BinaryRobloxFileWriter(BinaryRobloxFile file, Stream workBuffer) : base(workBuffer)
|
2019-06-08 03:43:28 +00:00
|
|
|
|
{
|
|
|
|
|
File = file;
|
|
|
|
|
|
|
|
|
|
ChunkStart = 0;
|
|
|
|
|
ChunkType = "";
|
|
|
|
|
|
|
|
|
|
Instances = new List<Instance>();
|
2019-06-30 22:01:19 +00:00
|
|
|
|
PostInstances = new List<Instance>();
|
|
|
|
|
|
|
|
|
|
ClassMap = new Dictionary<string, INST>();
|
2019-06-08 03:43:28 +00:00
|
|
|
|
}
|
2019-07-08 17:03:44 +00:00
|
|
|
|
|
|
|
|
|
public static int SizeOf<T>() where T : struct
|
|
|
|
|
{
|
|
|
|
|
int result = 1;
|
|
|
|
|
|
|
|
|
|
if (typeof(T) != typeof(bool))
|
|
|
|
|
result = Marshal.SizeOf<T>();
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2019-06-08 03:43:28 +00:00
|
|
|
|
|
|
|
|
|
private static byte[] GetBytes<T>(T value, int bufferSize, IntPtr converter)
|
|
|
|
|
{
|
|
|
|
|
byte[] bytes = new byte[bufferSize];
|
|
|
|
|
|
|
|
|
|
Marshal.StructureToPtr(value, converter, true);
|
|
|
|
|
Marshal.Copy(converter, bytes, 0, bufferSize);
|
|
|
|
|
|
|
|
|
|
return bytes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] GetBytes<T>(T value) where T : struct
|
|
|
|
|
{
|
2019-07-08 17:03:44 +00:00
|
|
|
|
int bufferSize = SizeOf<T>();
|
2019-06-08 03:43:28 +00:00
|
|
|
|
IntPtr converter = Marshal.AllocHGlobal(bufferSize);
|
|
|
|
|
|
|
|
|
|
var result = GetBytes(value, bufferSize, converter);
|
|
|
|
|
Marshal.FreeHGlobal(converter);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Writes 'count * sizeof(T)' interleaved bytes from a List of T where
|
|
|
|
|
// each value in the array will be encoded using the provided 'encode' function.
|
|
|
|
|
public void WriteInterleaved<T>(List<T> values, Func<T, T> encode = null) where T : struct
|
|
|
|
|
{
|
|
|
|
|
int count = values.Count;
|
2019-07-08 17:03:44 +00:00
|
|
|
|
int bufferSize = SizeOf<T>();
|
2019-06-08 03:43:28 +00:00
|
|
|
|
|
|
|
|
|
byte[][] blocks = new byte[count][];
|
|
|
|
|
IntPtr converter = Marshal.AllocHGlobal(bufferSize);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
T value = values[i];
|
|
|
|
|
|
|
|
|
|
if (encode != null)
|
|
|
|
|
value = encode(value);
|
|
|
|
|
|
|
|
|
|
blocks[i] = GetBytes(value, bufferSize, converter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int layer = bufferSize - 1; layer >= 0; layer--)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
byte value = blocks[i][layer];
|
|
|
|
|
Write(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Marshal.FreeHGlobal(converter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Encodes an int for an interleaved buffer.
|
2020-07-13 01:19:30 +00:00
|
|
|
|
private static int EncodeInt(int value)
|
2019-06-08 03:43:28 +00:00
|
|
|
|
{
|
|
|
|
|
return (value << 1) ^ (value >> 31);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Encodes a float for an interleaved buffer.
|
2020-07-13 01:19:30 +00:00
|
|
|
|
private static float EncodeFloat(float value)
|
2019-06-08 03:43:28 +00:00
|
|
|
|
{
|
|
|
|
|
byte[] buffer = BitConverter.GetBytes(value);
|
|
|
|
|
uint bits = BitConverter.ToUInt32(buffer, 0);
|
|
|
|
|
|
|
|
|
|
bits = (bits << 1) | (bits >> 31);
|
|
|
|
|
buffer = BitConverter.GetBytes(bits);
|
|
|
|
|
|
|
|
|
|
return BitConverter.ToSingle(buffer, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-08 17:03:44 +00:00
|
|
|
|
// Writes an interleaved list of integers.
|
2019-06-08 03:43:28 +00:00
|
|
|
|
public void WriteInts(List<int> values)
|
|
|
|
|
{
|
|
|
|
|
WriteInterleaved(values, EncodeInt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Writes an interleaved list of floats
|
|
|
|
|
public void WriteFloats(List<float> values)
|
|
|
|
|
{
|
|
|
|
|
WriteInterleaved(values, EncodeFloat);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-08 17:03:44 +00:00
|
|
|
|
// Accumulatively writes an interleaved array of integers.
|
2019-06-08 03:43:28 +00:00
|
|
|
|
public void WriteInstanceIds(List<int> values)
|
|
|
|
|
{
|
|
|
|
|
int numIds = values.Count;
|
|
|
|
|
var instIds = new List<int>(values);
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < numIds; ++i)
|
|
|
|
|
instIds[i] -= values[i - 1];;
|
|
|
|
|
|
|
|
|
|
WriteInts(instIds);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Writes a string to the buffer with the option to exclude a length prefix.
|
|
|
|
|
public void WriteString(string value, bool raw = false)
|
|
|
|
|
{
|
|
|
|
|
byte[] buffer = Encoding.UTF8.GetBytes(value);
|
|
|
|
|
|
|
|
|
|
if (!raw)
|
|
|
|
|
{
|
|
|
|
|
int length = buffer.Length;
|
|
|
|
|
Write(length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write(buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void RecordInstances(IEnumerable<Instance> instances)
|
|
|
|
|
{
|
|
|
|
|
foreach (Instance instance in instances)
|
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
if (!instance.Archivable)
|
|
|
|
|
continue;
|
2019-06-08 03:43:28 +00:00
|
|
|
|
|
2020-08-17 05:33:59 +00:00
|
|
|
|
int instId = (int)File.NumInstances++;
|
2019-06-08 03:43:28 +00:00
|
|
|
|
string className = instance.ClassName;
|
|
|
|
|
|
2020-08-17 05:33:59 +00:00
|
|
|
|
instance.Referent = instId.ToInvariantString();
|
|
|
|
|
Instances.Add(instance);
|
|
|
|
|
|
|
|
|
|
if (!ClassMap.TryGetValue(className, out INST inst))
|
2019-06-08 03:43:28 +00:00
|
|
|
|
{
|
|
|
|
|
inst = new INST()
|
|
|
|
|
{
|
2019-06-30 22:01:19 +00:00
|
|
|
|
ClassName = className,
|
2019-06-08 03:43:28 +00:00
|
|
|
|
InstanceIds = new List<int>(),
|
|
|
|
|
IsService = instance.IsService
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-30 22:01:19 +00:00
|
|
|
|
ClassMap.Add(className, inst);
|
2019-06-08 03:43:28 +00:00
|
|
|
|
}
|
2020-07-13 01:19:30 +00:00
|
|
|
|
|
2019-06-08 03:43:28 +00:00
|
|
|
|
inst.NumInstances++;
|
|
|
|
|
inst.InstanceIds.Add(instId);
|
|
|
|
|
|
|
|
|
|
RecordInstances(instance.GetChildren());
|
2019-06-30 22:01:19 +00:00
|
|
|
|
PostInstances.Add(instance);
|
2019-06-08 03:43:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
|
|
|
|
internal void ApplyClassMap()
|
|
|
|
|
{
|
|
|
|
|
File.Instances = Instances.ToArray();
|
|
|
|
|
|
|
|
|
|
var classNames = ClassMap
|
|
|
|
|
.Select(type => type.Key)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
classNames.Sort(StringComparer.Ordinal);
|
|
|
|
|
|
|
|
|
|
var classes = classNames
|
|
|
|
|
.Select(className => ClassMap[className])
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < classes.Length; i++, File.NumClasses++)
|
|
|
|
|
{
|
|
|
|
|
string className = classNames[i];
|
|
|
|
|
INST inst = ClassMap[className];
|
|
|
|
|
inst.ClassIndex = i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
File.Classes = classes;
|
|
|
|
|
}
|
2019-06-08 03:43:28 +00:00
|
|
|
|
|
|
|
|
|
// Marks that we are writing a chunk.
|
2020-07-13 01:19:30 +00:00
|
|
|
|
private bool StartWritingChunk(string chunkType)
|
2019-06-08 03:43:28 +00:00
|
|
|
|
{
|
|
|
|
|
if (chunkType.Length != 4)
|
|
|
|
|
throw new Exception("BinaryFileWriter.StartWritingChunk - ChunkType length should be 4!");
|
|
|
|
|
|
|
|
|
|
if (!WritingChunk)
|
|
|
|
|
{
|
|
|
|
|
WritingChunk = true;
|
|
|
|
|
|
|
|
|
|
ChunkType = chunkType;
|
|
|
|
|
ChunkStart = BaseStream.Position;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Marks that we are writing a chunk.
|
2020-07-13 01:19:30 +00:00
|
|
|
|
private bool StartWritingChunk(IBinaryFileChunk chunk)
|
2019-06-08 03:43:28 +00:00
|
|
|
|
{
|
|
|
|
|
if (!WritingChunk)
|
|
|
|
|
{
|
|
|
|
|
string chunkType = chunk.GetType().Name;
|
|
|
|
|
|
|
|
|
|
StartWritingChunk(chunkType);
|
|
|
|
|
Chunk = chunk;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compresses the data that was written into a BinaryRobloxFileChunk and writes it.
|
2020-07-13 01:19:30 +00:00
|
|
|
|
private BinaryRobloxFileChunk FinishWritingChunk(bool compress = true)
|
2019-06-08 03:43:28 +00:00
|
|
|
|
{
|
|
|
|
|
if (!WritingChunk)
|
|
|
|
|
throw new Exception($"BinaryRobloxFileWriter: Cannot finish writing a chunk without starting one!");
|
|
|
|
|
|
|
|
|
|
// Create the compressed chunk.
|
|
|
|
|
var chunk = new BinaryRobloxFileChunk(this, compress);
|
|
|
|
|
|
|
|
|
|
// Clear out the uncompressed data.
|
|
|
|
|
BaseStream.Position = ChunkStart;
|
|
|
|
|
BaseStream.SetLength(ChunkStart);
|
|
|
|
|
|
|
|
|
|
// Write the compressed chunk.
|
|
|
|
|
chunk.Handler = Chunk;
|
|
|
|
|
chunk.WriteChunk(this);
|
|
|
|
|
|
|
|
|
|
// Clean up for the next chunk.
|
|
|
|
|
WritingChunk = false;
|
|
|
|
|
|
|
|
|
|
ChunkStart = 0;
|
|
|
|
|
ChunkType = "";
|
|
|
|
|
Chunk = null;
|
|
|
|
|
|
|
|
|
|
return chunk;
|
|
|
|
|
}
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
internal BinaryRobloxFileChunk SaveChunk(IBinaryFileChunk handler, int insertPos = -1)
|
2019-06-30 22:01:19 +00:00
|
|
|
|
{
|
2020-07-13 01:19:30 +00:00
|
|
|
|
StartWritingChunk(handler);
|
|
|
|
|
handler.Save(this);
|
|
|
|
|
|
|
|
|
|
var chunk = FinishWritingChunk();
|
|
|
|
|
|
|
|
|
|
if (insertPos >= 0)
|
|
|
|
|
File.ChunksImpl.Insert(insertPos, chunk);
|
|
|
|
|
else
|
|
|
|
|
File.ChunksImpl.Add(chunk);
|
|
|
|
|
|
|
|
|
|
return chunk;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
internal BinaryRobloxFileChunk WriteChunk(string chunkType, string content, bool compress = false)
|
2019-06-30 22:01:19 +00:00
|
|
|
|
{
|
2020-07-13 01:19:30 +00:00
|
|
|
|
if (chunkType.Length > 4)
|
|
|
|
|
chunkType = chunkType.Substring(0, 4);
|
|
|
|
|
|
|
|
|
|
while (chunkType.Length < 4)
|
|
|
|
|
chunkType += '\0';
|
2019-06-30 22:01:19 +00:00
|
|
|
|
|
2020-07-13 01:19:30 +00:00
|
|
|
|
StartWritingChunk(chunkType);
|
|
|
|
|
WriteString(content);
|
|
|
|
|
|
|
|
|
|
var chunk = FinishWritingChunk(compress);
|
|
|
|
|
File.ChunksImpl.Add(chunk);
|
|
|
|
|
|
|
|
|
|
return chunk;
|
2019-06-30 22:01:19 +00:00
|
|
|
|
}
|
2019-06-08 03:43:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|