using System; using System.Collections.Generic; using System.IO; namespace SabreTools.Serialization.Wrappers { public class CFB : WrapperBase { #region Descriptive Properties /// public override string DescriptionString => "Compact File Binary"; #endregion #region Extension Properties /// /// Normal sector size in bytes /// public long SectorSize => (long)Math.Pow(2, this.Model.Header?.SectorShift ?? 0); /// /// Mini sector size in bytes /// public long MiniSectorSize => (long)Math.Pow(2, this.Model.Header?.MiniSectorShift ?? 0); #endregion #region Constructors /// public CFB(Models.CFB.Binary? model, byte[]? data, int offset) : base(model, data, offset) { // All logic is handled by the base class } /// public CFB(Models.CFB.Binary? model, Stream? data) : base(model, data) { // All logic is handled by the base class } /// /// Create a Compound File Binary from a byte array and offset /// /// Byte array representing the archive /// Offset within the array to parse /// A Compound File Binary wrapper on success, null on failure public static CFB? Create(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 use that MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset); return Create(dataStream); } /// /// Create a Compound File Binary from a Stream /// /// Stream representing the archive /// A Compound File Binary wrapper on success, null on failure public static CFB? Create(Stream? data) { // If the data is invalid if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead) return null; var binary = new Streams.CFB().Deserialize(data); if (binary == null) return null; try { return new CFB(binary, data); } catch { return null; } } #endregion #region FAT Sector Data /// /// Get the ordered FAT sector chain for a given starting sector /// /// Initial FAT sector /// Ordered list of sector numbers, null on error public List? GetFATSectorChain(Models.CFB.SectorNumber? startingSector) { // If we have an invalid sector if (startingSector == null || startingSector < 0 || this.Model.FATSectorNumbers == null || (long)startingSector >= this.Model.FATSectorNumbers.Length) return null; // Setup the returned list var sectors = new List { startingSector }; var lastSector = startingSector; while (true) { if (lastSector == null) break; // Get the next sector from the lookup table var nextSector = this.Model.FATSectorNumbers[(uint)lastSector!.Value]; // If we have an end of chain or free sector if (nextSector == SabreTools.Models.CFB.SectorNumber.ENDOFCHAIN || nextSector == SabreTools.Models.CFB.SectorNumber.FREESECT) break; // Add the next sector to the list and replace the last sector sectors.Add(nextSector); lastSector = nextSector; } return sectors; } /// /// Get the data for the FAT sector chain starting at a given starting sector /// /// Initial FAT sector /// Ordered list of sector numbers, null on error public byte[]? GetFATSectorChainData(Models.CFB.SectorNumber startingSector) { // Get the sector chain first var sectorChain = GetFATSectorChain(startingSector); if (sectorChain == null) return null; // Sequentially read the sectors var data = new List(); for (int i = 0; i < sectorChain.Count; i++) { // Try to get the sector data offset int sectorDataOffset = (int)FATSectorToFileOffset(sectorChain[i]); if (sectorDataOffset < 0 || sectorDataOffset >= GetEndOfFile()) return null; // Try to read the sector data var sectorData = ReadFromDataSource(sectorDataOffset, (int)SectorSize); if (sectorData == null) return null; // Add the sector data to the output data.AddRange(sectorData); } return data.ToArray(); } /// /// Convert a FAT sector value to a byte offset /// /// Sector to convert /// File offset in bytes, -1 on error public long FATSectorToFileOffset(Models.CFB.SectorNumber? sector) { // If we have an invalid sector number if (sector == null || sector > SabreTools.Models.CFB.SectorNumber.MAXREGSECT) return -1; // Convert based on the sector shift value return (long)(sector + 1) * SectorSize; } #endregion #region Mini FAT Sector Data /// /// Get the ordered Mini FAT sector chain for a given starting sector /// /// Initial Mini FAT sector /// Ordered list of sector numbers, null on error public List? GetMiniFATSectorChain(Models.CFB.SectorNumber? startingSector) { // If we have an invalid sector if (startingSector == null || startingSector < 0 || this.Model.MiniFATSectorNumbers == null || (long)startingSector >= this.Model.MiniFATSectorNumbers.Length) return null; // Setup the returned list var sectors = new List { startingSector }; var lastSector = startingSector; while (true) { if (lastSector == null) break; // Get the next sector from the lookup table var nextSector = this.Model.MiniFATSectorNumbers[(uint)lastSector!.Value]; // If we have an end of chain or free sector if (nextSector == SabreTools.Models.CFB.SectorNumber.ENDOFCHAIN || nextSector == SabreTools.Models.CFB.SectorNumber.FREESECT) break; // Add the next sector to the list and replace the last sector sectors.Add(nextSector); lastSector = nextSector; } return sectors; } /// /// Get the data for the Mini FAT sector chain starting at a given starting sector /// /// Initial Mini FAT sector /// Ordered list of sector numbers, null on error public byte[]? GetMiniFATSectorChainData(Models.CFB.SectorNumber startingSector) { // Get the sector chain first var sectorChain = GetMiniFATSectorChain(startingSector); if (sectorChain == null) return null; // Sequentially read the sectors var data = new List(); for (int i = 0; i < sectorChain.Count; i++) { // Try to get the sector data offset int sectorDataOffset = (int)MiniFATSectorToFileOffset(sectorChain[i]); if (sectorDataOffset < 0 || sectorDataOffset >= GetEndOfFile()) return null; // Try to read the sector data var sectorData = ReadFromDataSource(sectorDataOffset, (int)MiniSectorSize); if (sectorData == null) return null; // Add the sector data to the output data.AddRange(sectorData); } return data.ToArray(); } /// /// Convert a Mini FAT sector value to a byte offset /// /// Sector to convert /// File offset in bytes, -1 on error public long MiniFATSectorToFileOffset(Models.CFB.SectorNumber? sector) { // If we have an invalid sector number if (sector == null || sector > SabreTools.Models.CFB.SectorNumber.MAXREGSECT) return -1; // Convert based on the sector shift value return (long)(sector + 1) * MiniSectorSize; } #endregion } }