1
0

170 lines
5.8 KiB
C#

using System.IO.Hashing;
using System.Text;
using Flawless.Core.Modal;
using Nerdbank.Streams;
namespace Flawless.Core.BinaryDataFormat;
public static class DataExtractor
{
public static byte GuessStandardDepotHeaderVersion(Stream depotStream)
{
depotStream.Seek(8, SeekOrigin.Current);
var val = depotStream.ReadByte();
depotStream.Seek(-9, SeekOrigin.Current);
if (val < 0) ;
return (byte)val;
}
public static byte GuessNetworkDepotHeaderVersion(Stream depotStream)
{
var val = depotStream.ReadByte();
depotStream.Seek(-1, SeekOrigin.Current);
if (val < 0) ;
return (byte)val;
}
public static StandardDepotHeaderV1 ExtractStandardDepotHeaderV1(Stream depotStream)
{
StandardDepotHeaderV1 r = default;
_ = depotStream.Position; // Test if can seek
try
{
using var reader = new BinaryReader(depotStream, Encoding.ASCII, true);
// Magic number check
r.MagicNumber = reader.ReadUInt32();
if (r.MagicNumber != StandardDepotHeaderV1.FormatMagicNumber)
throw new InvalidDataException("File may not a depot or broken.");
// Do Crc checking
r.HeaderCRCChecksum = reader.ReadUInt32();
Span<byte> crcArea = stackalloc byte[64 - 8];
if (depotStream.Read(crcArea) != 64 - 8) throw new InvalidDataException("Stream is too short!");
if (Crc32.HashToUInt32(crcArea) != r.HeaderCRCChecksum)
throw new InvalidDataException("Header CRC check is failed!");
depotStream.Seek(8 - 64, SeekOrigin.Current);
// Let's read file
r.Version = reader.ReadByte();
if (r.Version != 1) throw new InvalidDataException($"Version is mismatch! Current version is {r.Version}");
r.CompressType = reader.ReadByte();
reader.ReadInt16(); // Preserved
reader.ReadInt32(); // Preserved
r.DepotMd5ChecksumLower = reader.ReadUInt64();
r.DepotMd5ChecksumUpper = reader.ReadUInt64();
r.GenerateTime = reader.ReadUInt64();
r.FileMapSize = reader.ReadUInt64();
r.PayloadSize = reader.ReadUInt64();
reader.ReadUInt64(); // Preserved
}
catch (EndOfStreamException e)
{
throw new InvalidDataException("Stream is too small! Maybe file is broken.", e);
}
return r;
}
public static NetworkDepotHeaderV1 ExtractNetworkDepotHeaderV1(Stream depotStream)
{
NetworkDepotHeaderV1 r;
_ = depotStream.Position; // Test if can seek
try
{
using var reader = new BinaryReader(depotStream, Encoding.ASCII, true);
// Let's read file
r.Version = reader.ReadByte();
if (r.Version != 1) throw new InvalidDataException($"Version is mismatch! Current version is {r.Version}");
r.NetworkTransmissionFeature = reader.ReadByte();
reader.ReadUInt16(); // Preserve
reader.ReadUInt32(); // Preserve
r.Md5ChecksumLower = reader.ReadUInt64();
r.Md5ChecksumUpper = reader.ReadUInt64();
r.GenerateTime = reader.ReadUInt64();
r.FileMapSize = reader.ReadUInt64();
r.PayloadSize = reader.ReadUInt64();
}
catch (EndOfStreamException e)
{
throw new InvalidDataException("Stream is too small! Maybe file is broken.", e);
}
return r;
}
public static Stream ExtractStandardDepotFile(Stream depotStream, DepotFileInfo fileInfo)
{
return ExtractStandardDepotFile(depotStream, fileInfo.Size, fileInfo.Offset);
}
public static Stream ExtractStandardDepotFile(Stream depotStream, ulong size, ulong offset)
{
depotStream.Seek((long)offset + 64, SeekOrigin.Begin);
return depotStream.ReadSlice((long) size);
}
public static async IAsyncEnumerable<DepotFileInfo> ExtractDepotFileInfoMapAsync(ulong fileMapSize, Stream depotStream)
{
var splitor = '$';
depotStream.Seek((long) fileMapSize, SeekOrigin.End);
var state = -1;
var buffer = new char[64];
var builder = new StringBuilder();
using var reader = new StreamReader(depotStream, Encoding.UTF8, leaveOpen: true);
var path = string.Empty;
var size = 0UL;
var offset = 0UL;
// Read loop
while (true)
{
try
{
var length = await reader.ReadBlockAsync(buffer, 0, 64);
if (length == 0) break;
for (int i = 0; i < length; i++)
{
var c = buffer[i];
if (c != splitor) builder.Append(c);
else
{
switch ((state = (state + 1) % 3))
{
case 0: path = builder.ToString(); break;
case 1: size = ulong.Parse(builder.ToString()); break;
case 2: offset = ulong.Parse(builder.ToString()); break;
}
builder.Clear();
}
}
}
catch (EndOfStreamException e)
{
throw new InvalidDataException("Stream is too small! Maybe file is broken.", e);
}
// Do output at here.
yield return new DepotFileInfo(offset, size, path);
}
// Check is this the real ending
if (builder.Length > 0 || state != 0)
throw new InvalidDataException("Stream is too small! Maybe file is broken.");
}
}