2019-01-26 00:39:37 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Text;
|
2022-12-16 20:22:34 +00:00
|
|
|
|
using System.IO.Compression;
|
|
|
|
|
|
2019-01-26 00:39:37 +00:00
|
|
|
|
using LZ4;
|
2022-12-16 20:22:34 +00:00
|
|
|
|
using ZstdSharp;
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2019-02-01 17:19:20 +00:00
|
|
|
|
namespace RobloxFiles.BinaryFormat
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2019-02-04 19:30:33 +00:00
|
|
|
|
/// <summary>
|
2019-05-19 04:44:51 +00:00
|
|
|
|
/// BinaryRobloxFileChunk represents a generic LZ4-compressed chunk
|
2019-02-04 19:30:33 +00:00
|
|
|
|
/// of data in Roblox's Binary File Format.
|
|
|
|
|
/// </summary>
|
2019-05-19 04:44:51 +00:00
|
|
|
|
public class BinaryRobloxFileChunk
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
|
|
|
|
public readonly string ChunkType;
|
2019-06-08 03:43:28 +00:00
|
|
|
|
public readonly int Reserved;
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
|
|
|
|
public readonly int CompressedSize;
|
|
|
|
|
public readonly int Size;
|
2019-02-04 19:30:33 +00:00
|
|
|
|
|
|
|
|
|
public readonly byte[] CompressedData;
|
2019-01-26 00:39:37 +00:00
|
|
|
|
public readonly byte[] Data;
|
|
|
|
|
|
|
|
|
|
public bool HasCompressedData => (CompressedSize > 0);
|
2019-06-08 03:43:28 +00:00
|
|
|
|
public IBinaryFileChunk Handler { get; internal set; }
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
2019-06-08 03:43:28 +00:00
|
|
|
|
public bool HasWriteBuffer { get; private set; }
|
|
|
|
|
public byte[] WriteBuffer { get; private set; }
|
|
|
|
|
|
2019-05-19 04:44:51 +00:00
|
|
|
|
public override string ToString()
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2019-06-08 03:43:28 +00:00
|
|
|
|
string chunkType = ChunkType.Replace('\0', ' ');
|
2019-06-30 22:01:19 +00:00
|
|
|
|
return $"'{chunkType}' Chunk ({Size} bytes) [{Handler?.ToString()}]";
|
2019-01-26 00:39:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-19 04:44:51 +00:00
|
|
|
|
public BinaryRobloxFileChunk(BinaryRobloxFileReader reader)
|
2019-01-26 00:39:37 +00:00
|
|
|
|
{
|
2019-06-08 03:43:28 +00:00
|
|
|
|
byte[] rawChunkType = reader.ReadBytes(4);
|
|
|
|
|
ChunkType = Encoding.ASCII.GetString(rawChunkType);
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
|
|
|
|
CompressedSize = reader.ReadInt32();
|
|
|
|
|
Size = reader.ReadInt32();
|
2019-06-08 03:43:28 +00:00
|
|
|
|
Reserved = reader.ReadInt32();
|
2019-01-26 00:39:37 +00:00
|
|
|
|
|
|
|
|
|
if (HasCompressedData)
|
|
|
|
|
{
|
|
|
|
|
CompressedData = reader.ReadBytes(CompressedSize);
|
2022-12-16 20:22:34 +00:00
|
|
|
|
Data = new byte[Size];
|
|
|
|
|
|
|
|
|
|
using (var compStream = new MemoryStream(CompressedData))
|
|
|
|
|
{
|
|
|
|
|
Stream decompStream = null;
|
|
|
|
|
|
2022-12-21 19:06:37 +00:00
|
|
|
|
|
|
|
|
|
if (CompressedData[0] == 0x78 || CompressedData[0] == 0x58)
|
2022-12-16 20:22:34 +00:00
|
|
|
|
{
|
|
|
|
|
// Probably zlib
|
|
|
|
|
decompStream = new DeflateStream(compStream, CompressionMode.Decompress);
|
|
|
|
|
}
|
|
|
|
|
else if (BitConverter.ToString(CompressedData, 1, 3) == "B5-2F-FD")
|
|
|
|
|
{
|
|
|
|
|
// Probably zstd
|
|
|
|
|
decompStream = new DecompressionStream(compStream);
|
|
|
|
|
}
|
2022-12-21 19:06:37 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Probably LZ4
|
|
|
|
|
var decomp = LZ4Codec.Decode(CompressedData, 0, CompressedSize, Size);
|
|
|
|
|
decompStream = new MemoryStream(decomp);
|
|
|
|
|
}
|
2022-12-16 20:22:34 +00:00
|
|
|
|
|
|
|
|
|
if (decompStream == null)
|
|
|
|
|
throw new Exception("Unsupported compression scheme!");
|
|
|
|
|
|
|
|
|
|
decompStream.Read(Data, 0, Size);
|
|
|
|
|
decompStream.Dispose();
|
|
|
|
|
}
|
2019-01-26 00:39:37 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Data = reader.ReadBytes(Size);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-08 03:43:28 +00:00
|
|
|
|
|
|
|
|
|
public BinaryRobloxFileChunk(BinaryRobloxFileWriter writer, bool compress = true)
|
|
|
|
|
{
|
|
|
|
|
if (!writer.WritingChunk)
|
|
|
|
|
throw new Exception("BinaryRobloxFileChunk: Supplied writer must have WritingChunk set to true.");
|
|
|
|
|
|
|
|
|
|
Stream stream = writer.BaseStream;
|
|
|
|
|
|
|
|
|
|
using (BinaryReader reader = new BinaryReader(stream, Encoding.UTF8, true))
|
|
|
|
|
{
|
|
|
|
|
long length = (stream.Position - writer.ChunkStart);
|
|
|
|
|
stream.Position = writer.ChunkStart;
|
|
|
|
|
|
|
|
|
|
Size = (int)length;
|
|
|
|
|
Data = reader.ReadBytes(Size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CompressedData = LZ4Codec.Encode(Data, 0, Size);
|
|
|
|
|
CompressedSize = CompressedData.Length;
|
|
|
|
|
|
|
|
|
|
if (!compress || CompressedSize > Size)
|
|
|
|
|
{
|
|
|
|
|
CompressedSize = 0;
|
2020-08-17 05:33:59 +00:00
|
|
|
|
CompressedData = Array.Empty<byte>();
|
2019-06-08 03:43:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ChunkType = writer.ChunkType;
|
|
|
|
|
Reserved = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void WriteChunk(BinaryRobloxFileWriter writer)
|
|
|
|
|
{
|
|
|
|
|
// Record where we are when we start writing.
|
|
|
|
|
var stream = writer.BaseStream;
|
|
|
|
|
long startPos = stream.Position;
|
|
|
|
|
|
|
|
|
|
// Write the chunk's data.
|
|
|
|
|
writer.WriteString(ChunkType, true);
|
|
|
|
|
|
|
|
|
|
writer.Write(CompressedSize);
|
|
|
|
|
writer.Write(Size);
|
|
|
|
|
|
|
|
|
|
writer.Write(Reserved);
|
|
|
|
|
|
|
|
|
|
if (CompressedSize > 0)
|
|
|
|
|
writer.Write(CompressedData);
|
|
|
|
|
else
|
|
|
|
|
writer.Write(Data);
|
|
|
|
|
|
|
|
|
|
// Capture the data we wrote into a byte[] array.
|
|
|
|
|
long endPos = stream.Position;
|
|
|
|
|
int length = (int)(endPos - startPos);
|
|
|
|
|
|
|
|
|
|
using (MemoryStream buffer = new MemoryStream())
|
|
|
|
|
{
|
|
|
|
|
stream.Position = startPos;
|
|
|
|
|
stream.CopyTo(buffer, length);
|
|
|
|
|
|
|
|
|
|
WriteBuffer = buffer.ToArray();
|
|
|
|
|
HasWriteBuffer = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-26 00:39:37 +00:00
|
|
|
|
}
|
|
|
|
|
}
|