using System; using System.Collections.Generic; using System.IO; using SabreTools.Data.Models.CFB; using SabreTools.IO.Extensions; using SabreTools.Numerics.Extensions; namespace SabreTools.Wrappers { public partial class CFB : WrapperBase { #region Descriptive Properties /// public override string DescriptionString => "Compact File Binary"; #endregion #region Extension Properties /// public FileHeader Header => Model.Header; /// public DirectoryEntry[] DirectoryEntries => Model.DirectoryEntries; /// public SectorNumber[] FATSectorNumbers => Model.FATSectorNumbers; /// public SectorNumber[] MiniFATSectorNumbers => Model.MiniFATSectorNumbers; /// /// Byte array representing the mini stream /// public byte[] MiniStreamData { get { // Use the cached value, if it exists if (field is not null) return field; // If there are no directory entries if (DirectoryEntries.Length == 0) return []; // Get the mini stream offset from root object var startingSector = (SectorNumber)DirectoryEntries[0].StartingSectorLocation; // Get the mini stream data field = GetFATSectorChainData(startingSector); return field ?? []; } } = null; /// /// Normal sector size in bytes /// public long SectorSize => (long)Math.Pow(2, Header.SectorShift); /// /// Mini sector size in bytes /// public long MiniSectorSize => (long)Math.Pow(2, Header.MiniSectorShift); #endregion #region Constructors /// public CFB(Binary model, byte[] data) : base(model, data) { } /// public CFB(Binary model, byte[] data, int offset) : base(model, data, offset) { } /// public CFB(Binary model, byte[] data, int offset, int length) : base(model, data, offset, length) { } /// public CFB(Binary model, Stream data) : base(model, data) { } /// public CFB(Binary model, Stream data, long offset) : base(model, data, offset) { } /// public CFB(Binary model, Stream data, long offset, long length) : base(model, data, offset, length) { } #endregion #region Static Constructors /// /// 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 is null || data.Length == 0) return null; // If the offset is out of bounds if (offset < 0 || offset >= data.Length) return null; // Create a memory stream and use that var 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 is null || !data.CanRead) return null; try { // Cache the current offset long currentOffset = data.Position; var model = new Serialization.Readers.CFB().Deserialize(data); if (model is null) return null; return new CFB(model, data, currentOffset); } 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(SectorNumber? startingSector) { // If we have an invalid sector if (startingSector is null || startingSector < 0 || FATSectorNumbers is null || (long)startingSector >= FATSectorNumbers.Length) return null; // Setup the returned list var sectors = new List { startingSector.Value }; var lastSector = startingSector; while (true) { if (lastSector is null) break; // Get the next sector from the lookup table var nextSector = FATSectorNumbers[(uint)lastSector!.Value]; // If we have an invalid sector if (nextSector >= SectorNumber.MAXREGSECT) 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(SectorNumber startingSector) { // Get the sector chain first var sectorChain = GetFATSectorChain(startingSector); if (sectorChain is 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 >= Length) return null; // Try to read the sector data var sectorData = ReadRangeFromSource(sectorDataOffset, (int)SectorSize); if (sectorData.Length == 0) return null; // Add the sector data to the output data.AddRange(sectorData); } return [.. data]; } /// /// Convert a FAT sector value to a byte offset /// /// Sector to convert /// File offset in bytes, -1 on error public long FATSectorToFileOffset(SectorNumber? sector) { // If we have an invalid sector number if (sector is null || sector > 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(SectorNumber? startingSector) { // If we have an invalid sector if (startingSector is null || startingSector < 0 || MiniFATSectorNumbers is null || (long)startingSector >= MiniFATSectorNumbers.Length) return null; // Setup the returned list var sectors = new List { startingSector.Value }; var lastSector = startingSector; while (true) { if (lastSector is null) break; // Get the next sector from the lookup table var nextSector = MiniFATSectorNumbers[(uint)lastSector!.Value]; // If we have an invalid sector if (nextSector >= SectorNumber.MAXREGSECT) 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(SectorNumber startingSector) { // Validate the mini stream data if (MiniStreamData is null) return null; // Get the sector chain var sectorChain = GetMiniFATSectorChain(startingSector); if (sectorChain is null) return null; // Sequentially read the sectors var data = new List(); for (int i = 0; i < sectorChain.Count; i++) { // Try to get the mini stream data offset int streamDataOffset = (int)MiniFATSectorToMiniStreamOffset(sectorChain[i]); if (streamDataOffset < 0 || streamDataOffset > MiniStreamData.Length) return null; // Try to read the sector data var sectorData = MiniStreamData.ReadBytes(ref streamDataOffset, (int)MiniSectorSize); if (sectorData is null) return null; // Add the sector data to the output data.AddRange(sectorData); } return [.. data]; } /// /// Convert a Mini FAT sector value to a byte offset /// /// Sector to convert /// Stream offset in bytes, -1 on error /// Offset is within the mini stream, not the full file public long MiniFATSectorToMiniStreamOffset(SectorNumber? sector) { // If we have an invalid sector number if (sector is null || sector > SectorNumber.MAXREGSECT) return -1; // Get the mini stream location return (long)sector * MiniSectorSize; } #endregion } }