diff --git a/Bytes/CFB.Deserializer.cs b/Bytes/CFB.Deserializer.cs new file mode 100644 index 00000000..d20d0517 --- /dev/null +++ b/Bytes/CFB.Deserializer.cs @@ -0,0 +1,28 @@ +using System.IO; +using SabreTools.Models.CFB; + +namespace SabreTools.Serialization.Bytes +{ + public partial class CFB : IByteSerializer + { + /// +#if NET48 + public Binary Deserialize(byte[] data, int offset) +#else + public Binary? Deserialize(byte[]? data, int offset) +#endif + { + // 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 new Streams.CFB().Deserialize(dataStream); + } + } +} \ No newline at end of file diff --git a/Streams/CFB.Deserializer.cs b/Streams/CFB.Deserializer.cs new file mode 100644 index 00000000..89eed96f --- /dev/null +++ b/Streams/CFB.Deserializer.cs @@ -0,0 +1,362 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using SabreTools.IO; +using SabreTools.Models.CFB; +using static SabreTools.Models.CFB.Constants; + +namespace SabreTools.Serialization.Streams +{ + public partial class CFB : IStreamSerializer + { + /// +#if NET48 + public Binary Deserialize(Stream data) +#else + public Binary? Deserialize(Stream? data) +#endif + { + // 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 + + #region DIFAT Sector Numbers + + // Create a DIFAT sector table + var difatSectors = new List(); + + // Add the sectors from the header + difatSectors.AddRange(fileHeader.DIFAT); + + // Loop through and add the DIFAT sectors + SectorNumber currentSector = (SectorNumber)fileHeader.FirstDIFATSectorLocation; + for (int i = 0; i < fileHeader.NumberOfDIFATSectors; i++) + { + // If we have a readable sector + if (currentSector <= SectorNumber.MAXREGSECT) + { + // Get the new next sector information + long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift)); + if (sectorOffset < 0 || sectorOffset >= data.Length) + return null; + + // Seek to the next sector + data.Seek(sectorOffset, SeekOrigin.Begin); + + // Try to parse the sectors + var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift); + if (sectorNumbers == null) + return null; + + // Add the sector shifts + difatSectors.AddRange(sectorNumbers); + } + + // Get the next sector from the DIFAT + currentSector = difatSectors[i]; + } + + // Assign the DIFAT sectors table + binary.DIFATSectorNumbers = difatSectors.ToArray(); + + #endregion + + #region FAT Sector Numbers + + // Create a FAT sector table + var fatSectors = new List(); + + // Loop through and add the FAT sectors + currentSector = binary.DIFATSectorNumbers[0]; + for (int i = 0; i < fileHeader.NumberOfFATSectors; i++) + { + // If we have a readable sector + if (currentSector <= SectorNumber.MAXREGSECT) + { + // Get the new next sector information + long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift)); + if (sectorOffset < 0 || sectorOffset >= data.Length) + return null; + + // Seek to the next sector + data.Seek(sectorOffset, SeekOrigin.Begin); + + // Try to parse the sectors + var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift); + if (sectorNumbers == null) + return null; + + // Add the sector shifts + fatSectors.AddRange(sectorNumbers); + } + + // Get the next sector from the DIFAT + currentSector = binary.DIFATSectorNumbers[i]; + } + + // Assign the FAT sectors table + binary.FATSectorNumbers = fatSectors.ToArray(); + + #endregion + + #region Mini FAT Sector Numbers + + // Create a mini FAT sector table + var miniFatSectors = new List(); + + // Loop through and add the mini FAT sectors + currentSector = (SectorNumber)fileHeader.FirstMiniFATSectorLocation; + for (int i = 0; i < fileHeader.NumberOfMiniFATSectors; i++) + { + // If we have a readable sector + if (currentSector <= SectorNumber.MAXREGSECT) + { + // Get the new next sector information + long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift)); + if (sectorOffset < 0 || sectorOffset >= data.Length) + return null; + + // Seek to the next sector + data.Seek(sectorOffset, SeekOrigin.Begin); + + // Try to parse the sectors + var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift); + if (sectorNumbers == null) + return null; + + // Add the sector shifts + miniFatSectors.AddRange(sectorNumbers); + } + + // Get the next sector from the DIFAT + currentSector = binary.DIFATSectorNumbers[i]; + } + + // Assign the mini FAT sectors table + binary.MiniFATSectorNumbers = miniFatSectors.ToArray(); + + #endregion + + #region Directory Entries + + // Get the offset of the first directory sector + long firstDirectoryOffset = (long)(fileHeader.FirstDirectorySectorLocation * Math.Pow(2, fileHeader.SectorShift)); + if (firstDirectoryOffset < 0 || firstDirectoryOffset >= data.Length) + return null; + + // Seek to the first directory sector + data.Seek(firstDirectoryOffset, SeekOrigin.Begin); + + // Create a directory sector table + var directorySectors = new List(); + + // Get the number of directory sectors + uint directorySectorCount = 0; + switch (fileHeader.MajorVersion) + { + case 3: + directorySectorCount = int.MaxValue; + break; + case 4: + directorySectorCount = fileHeader.NumberOfDirectorySectors; + break; + } + + // Loop through and add the directory sectors + currentSector = (SectorNumber)fileHeader.FirstDirectorySectorLocation; + for (int i = 0; i < directorySectorCount; i++) + { + // If we have an end of chain + if (currentSector == SectorNumber.ENDOFCHAIN) + break; + + // If we have a readable sector + if (currentSector <= SectorNumber.MAXREGSECT) + { + // Get the new next sector information + long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift)); + if (sectorOffset < 0 || sectorOffset >= data.Length) + return null; + + // Seek to the next sector + data.Seek(sectorOffset, SeekOrigin.Begin); + + // Try to parse the sectors + var directoryEntries = ParseDirectoryEntries(data, fileHeader.SectorShift, fileHeader.MajorVersion); + if (directoryEntries == null) + return null; + + // Add the sector shifts + directorySectors.AddRange(directoryEntries); + } + + // Get the next sector from the DIFAT + currentSector = binary.DIFATSectorNumbers[i]; + } + + // Assign the Directory sectors table + binary.DirectoryEntries = directorySectors.ToArray(); + + #endregion + + 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.ReadUInt64(); + if (header.Signature != SignatureUInt64) + 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 SectorNumber[109]; + for (int i = 0; i < header.DIFAT.Length; i++) + { + header.DIFAT[i] = (SectorNumber)data.ReadUInt32(); + } + + // Skip rest of sector for version 4 + if (header.MajorVersion == 4) + _ = data.ReadBytes(3584); + + return header; + } + + /// + /// Parse a Stream into a sector full of sector numbers + /// + /// Stream to parse + /// Sector shift from the header + /// Filled sector full of sector numbers on success, null on error + private static SectorNumber[] ParseSectorNumbers(Stream data, ushort sectorShift) + { + // TODO: Use marshalling here instead of building + int sectorCount = (int)(Math.Pow(2, sectorShift) / sizeof(uint)); + SectorNumber[] sectorNumbers = new SectorNumber[sectorCount]; + + for (int i = 0; i < sectorNumbers.Length; i++) + { + sectorNumbers[i] = (SectorNumber)data.ReadUInt32(); + } + + return sectorNumbers; + } + + /// + /// Parse a Stream into a sector full of directory entries + /// + /// Stream to parse + /// Sector shift from the header + /// Major version from the header + /// Filled sector full of directory entries on success, null on error + private static DirectoryEntry[] ParseDirectoryEntries(Stream data, ushort sectorShift, ushort majorVersion) + { + // TODO: Use marshalling here instead of building + const int directoryEntrySize = 64 + 2 + 1 + 1 + 4 + 4 + 4 + 16 + 4 + 8 + 8 + 4 + 8; + int sectorCount = (int)(Math.Pow(2, sectorShift) / directoryEntrySize); + DirectoryEntry[] directoryEntries = new DirectoryEntry[sectorCount]; + + for (int i = 0; i < directoryEntries.Length; i++) + { + var directoryEntry = ParseDirectoryEntry(data, majorVersion); + if (directoryEntry == null) + return null; + + directoryEntries[i] = directoryEntry; + } + + return directoryEntries; + } + + /// + /// Parse a Stream into a directory entry + /// + /// Stream to parse + /// Major version from the header + /// Filled directory entry on success, null on error + private static DirectoryEntry ParseDirectoryEntry(Stream data, ushort majorVersion) + { + // TODO: Use marshalling here instead of building + DirectoryEntry directoryEntry = new DirectoryEntry(); + + byte[] name = data.ReadBytes(64); + directoryEntry.Name = Encoding.Unicode.GetString(name).TrimEnd('\0'); + directoryEntry.NameLength = data.ReadUInt16(); + directoryEntry.ObjectType = (ObjectType)data.ReadByteValue(); + directoryEntry.ColorFlag = (ColorFlag)data.ReadByteValue(); + directoryEntry.LeftSiblingID = (StreamID)data.ReadUInt32(); + directoryEntry.RightSiblingID = (StreamID)data.ReadUInt32(); + directoryEntry.ChildID = (StreamID)data.ReadUInt32(); + directoryEntry.CLSID = data.ReadGuid(); + directoryEntry.StateBits = data.ReadUInt32(); + directoryEntry.CreationTime = data.ReadUInt64(); + directoryEntry.ModifiedTime = data.ReadUInt64(); + directoryEntry.StartingSectorLocation = data.ReadUInt32(); + directoryEntry.StreamSize = data.ReadUInt64(); + if (majorVersion == 3) + directoryEntry.StreamSize &= 0x0000FFFF; + + return directoryEntry; + } + } +} \ No newline at end of file diff --git a/Streams/CFB.Serializer.cs b/Streams/CFB.Serializer.cs new file mode 100644 index 00000000..7e95e4a2 --- /dev/null +++ b/Streams/CFB.Serializer.cs @@ -0,0 +1,16 @@ +using System; +using System.IO; +using SabreTools.Models.CFB; + +namespace SabreTools.Serialization.Streams +{ + public partial class CFB : IStreamSerializer + { + /// +#if NET48 + public Stream Serialize(Binary obj) => throw new NotImplementedException(); +#else + public Stream? Serialize(Binary? obj) => throw new NotImplementedException(); +#endif + } +} \ No newline at end of file