using System.IO.Hashing; using System.Text; using Flawless.Core.Modal; using Nerdbank.Streams; namespace Flawless.Core.BinaryDataFormat; public static class DataTransformer { 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 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 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 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 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."); } }