diff --git a/BurnOutSharp.Builders/CFB.cs b/BurnOutSharp.Builders/CFB.cs new file mode 100644 index 00000000..b95368ba --- /dev/null +++ b/BurnOutSharp.Builders/CFB.cs @@ -0,0 +1,138 @@ +using System.IO; +using System.Text; +using BurnOutSharp.Models.CFB; +using BurnOutSharp.Utilities; +using static BurnOutSharp.Models.CFB.Constants; + +namespace BurnOutSharp.Builders +{ + public class CFB + { + #region Byte Data + + /// + /// Parse a byte array into a Compound File Binary + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled Compound File Binary on success, null on error + public static Binary ParseBinary(byte[] data, int offset) + { + // If the data is invalid + if (data == null) + return null; + + // If the offset is out of bounds + if (offset < 0 || offset >= data.Length) + return null; + + // Create a memory stream and parse that + MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset); + return ParseBinary(dataStream); + } + + #endregion + + #region Stream Data + + /// + /// Parse a Stream into a Compound File Binary + /// + /// Stream to parse + /// Filled Compound File Binary on success, null on error + public static Binary ParseBinary(Stream data) + { + // If the data is invalid + if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead) + return null; + + // If the offset is out of bounds + if (data.Position < 0 || data.Position >= data.Length) + return null; + + // Cache the current offset + int initialOffset = (int)data.Position; + + // Create a new binary to fill + var binary = new Binary(); + + #region Header + + // Try to parse the file header + var fileHeader = ParseFileHeader(data); + if (fileHeader == null) + return null; + + // Set the file header + binary.Header = fileHeader; + + #endregion + + // TODO: Implement FAT sector parsing + // TODO: Implement Mini FAT sector parsing + // TODO: Implement DIFAT sector parsing + // TODO: Implement directory sector parsing + + return binary; + } + + /// + /// Parse a Stream into a file header + /// + /// Stream to parse + /// Filled file header on success, null on error + private static FileHeader ParseFileHeader(Stream data) + { + // TODO: Use marshalling here instead of building + FileHeader header = new FileHeader(); + + header.Signature = data.ReadBytes(8); + if (header.Signature != SignatureBytes) + return null; + + header.CLSID = data.ReadGuid(); + header.MinorVersion = data.ReadUInt16(); + header.MajorVersion = data.ReadUInt16(); + header.ByteOrder = data.ReadUInt16(); + if (header.ByteOrder != 0xFFFE) + return null; + + header.SectorShift = data.ReadUInt16(); + if (header.MajorVersion == 3 && header.SectorShift != 0x0009) + return null; + else if (header.MajorVersion == 4 && header.SectorShift != 0x000C) + return null; + + header.MiniSectorShift = data.ReadUInt16(); + header.Reserved = data.ReadBytes(6); + header.NumberOfDirectorySectors = data.ReadUInt32(); + if (header.MajorVersion == 3 && header.NumberOfDirectorySectors != 0) + return null; + + header.NumberOfFATSectors = data.ReadUInt32(); + header.FirstDirectorySectorLocation = data.ReadUInt32(); + header.TransactionSignatureNumber = data.ReadUInt32(); + header.MiniStreamCutoffSize = data.ReadUInt32(); + if (header.MiniStreamCutoffSize != 0x00001000) + return null; + + header.FirstMiniFATSectorLocation = data.ReadUInt32(); + header.NumberOfMiniFATSectors = data.ReadUInt32(); + header.FirstDIFATSectorLocation = data.ReadUInt32(); + header.NumberOfDIFATSectors = data.ReadUInt32(); + header.DIFAT = new uint[109]; + for (int i = 0; i < header.DIFAT.Length; i++) + { + header.DIFAT[i] = data.ReadUInt32(); + } + + // Skip rest of sector for version 4 + if (header.MajorVersion == 4) + _ = data.ReadBytes(3584); + + return header; + } + + #endregion + } +} diff --git a/BurnOutSharp.Models/CFB/Constants.cs b/BurnOutSharp.Models/CFB/Constants.cs index c08b61ed..59e93f01 100644 --- a/BurnOutSharp.Models/CFB/Constants.cs +++ b/BurnOutSharp.Models/CFB/Constants.cs @@ -2,6 +2,6 @@ namespace BurnOutSharp.Models.CFB { public static class Constants { - public static readonly byte[] Signature = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; + public static readonly byte[] SignatureBytes = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; } } \ No newline at end of file diff --git a/BurnOutSharp.Utilities/Extensions.cs b/BurnOutSharp.Utilities/Extensions.cs index 9efc42a7..be63499a 100644 --- a/BurnOutSharp.Utilities/Extensions.cs +++ b/BurnOutSharp.Utilities/Extensions.cs @@ -271,6 +271,17 @@ namespace BurnOutSharp.Utilities return value; } + /// + /// Read a Guid from the stream + /// + public static Guid ReadGuid(this byte[] content, ref int offset) + { + byte[] buffer = new byte[16]; + Array.Copy(content, offset, buffer, 0, 16); + offset += 16; + return new Guid(buffer); + } + /// /// Read a null-terminated string from the stream /// @@ -409,6 +420,16 @@ namespace BurnOutSharp.Utilities return BitConverter.ToUInt64(buffer, 0); } + /// + /// Read a Guid from the stream + /// + public static Guid ReadGuid(this Stream stream) + { + byte[] buffer = new byte[16]; + stream.Read(buffer, 0, 16); + return new Guid(buffer); + } + /// /// Read a null-terminated string from the stream ///