Files

362 lines
14 KiB
C#
Raw Permalink Normal View History

using System;
using System.Collections.Generic;
2023-09-08 21:25:49 -04:00
using System.IO;
2024-12-16 23:08:45 -05:00
using System.Text;
2025-09-26 13:15:55 -04:00
using SabreTools.Data.Extensions;
2025-09-26 13:06:18 -04:00
using SabreTools.Data.Models.CFB;
2024-04-17 11:52:22 -04:00
using SabreTools.IO.Extensions;
2026-03-24 19:17:25 -04:00
using SabreTools.Numerics.Extensions;
2025-09-26 13:06:18 -04:00
using static SabreTools.Data.Models.CFB.Constants;
2023-09-08 21:25:49 -04:00
2026-01-27 12:03:01 -05:00
#pragma warning disable IDE0017 // Simplify object initialization
2025-09-26 14:57:20 -04:00
namespace SabreTools.Serialization.Readers
2023-09-08 21:25:49 -04:00
{
public class CFB : BaseBinaryReader<Binary>
2023-09-08 21:25:49 -04:00
{
/// <inheritdoc/>
public override Binary? Deserialize(Stream? data)
{
// If the data is invalid
2026-01-25 14:30:18 -05:00
if (data is null || !data.CanRead)
return null;
try
{
2025-08-19 09:09:33 -04:00
// Cache the current offset
long initialOffset = data.Position;
// Create a new binary to fill
var binary = new Binary();
#region Header
// Try to parse the file header
var fileHeader = ParseFileHeader(data);
2024-12-16 23:08:45 -05:00
if (fileHeader?.Signature != SignatureUInt64)
return null;
if (fileHeader.ByteOrder != 0xFFFE)
return null;
if (fileHeader.MajorVersion == 3 && fileHeader.SectorShift != 0x0009)
return null;
else if (fileHeader.MajorVersion == 4 && fileHeader.SectorShift != 0x000C)
return null;
if (fileHeader.MajorVersion == 3 && fileHeader.NumberOfDirectorySectors != 0)
return null;
if (fileHeader.MiniStreamCutoffSize != 0x00001000)
return null;
// Set the file header
binary.Header = fileHeader;
#endregion
#region DIFAT Sector Numbers
// Create a DIFAT sector table
var difatSectors = new List<SectorNumber>();
// Add the sectors from the header
2026-01-25 14:32:49 -05:00
if (fileHeader.DIFAT is not null)
difatSectors.AddRange(fileHeader.DIFAT);
// Loop through and add the DIFAT sectors
var currentSector = (SectorNumber?)fileHeader.FirstDIFATSectorLocation;
for (int i = 0; i < fileHeader.NumberOfDIFATSectors; i++)
{
// If we have an unreadable sector
if (currentSector > SectorNumber.MAXREGSECT)
break;
// Get the new next sector information
2025-08-19 09:09:33 -04:00
long sectorOffset = initialOffset
+ (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
if (sectorOffset < initialOffset || sectorOffset >= data.Length)
return null;
// Seek to the next sector
2025-10-27 22:43:56 -04:00
data.SeekIfPossible(sectorOffset, SeekOrigin.Begin);
// Try to parse the sectors
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
2026-01-25 14:30:18 -05:00
if (sectorNumbers is null)
return null;
// Add all but the last sector number that was parsed
for (int j = 0; j < sectorNumbers.Length - 1; j++)
{
difatSectors.Add(sectorNumbers[j]);
}
// Get the next sector from the final sector number
2025-11-14 09:06:59 -05:00
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
currentSector = sectorNumbers[^1];
#else
currentSector = sectorNumbers[sectorNumbers.Length - 1];
2025-11-14 09:06:59 -05:00
#endif
}
// Assign the DIFAT sectors table
binary.DIFATSectorNumbers = [.. difatSectors];
#endregion
#region FAT Sector Numbers
// Create a FAT sector table
var fatSectors = new List<SectorNumber>();
// Loop through and add the FAT sectors
for (int i = 0; i < fileHeader.NumberOfFATSectors; i++)
{
// Get the next sector from the DIFAT
currentSector = binary.DIFATSectorNumbers[i];
// If we have an unreadable sector
if (currentSector > SectorNumber.MAXREGSECT)
break;
// Get the new next sector information
2025-08-19 09:09:33 -04:00
long sectorOffset = initialOffset
+ (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
if (sectorOffset < initialOffset || sectorOffset >= data.Length)
return null;
// Seek to the next sector
2025-10-27 22:43:56 -04:00
data.SeekIfPossible(sectorOffset, SeekOrigin.Begin);
// Try to parse the sectors
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
2026-01-25 14:30:18 -05:00
if (sectorNumbers is null)
return null;
// Add the sector shifts
fatSectors.AddRange(sectorNumbers);
}
// Assign the FAT sectors table
binary.FATSectorNumbers = [.. fatSectors];
#endregion
#region Mini FAT Sector Numbers
// Create a mini FAT sector table
var miniFatSectors = new List<SectorNumber>();
// Loop through and add the mini FAT sectors
currentSector = (SectorNumber)fileHeader.FirstMiniFATSectorLocation;
for (int i = 0; i < fileHeader.NumberOfMiniFATSectors; i++)
{
// If we have an unreadable sector
if (currentSector > SectorNumber.MAXREGSECT)
break;
// Get the new next sector information
2025-08-19 09:09:33 -04:00
long sectorOffset = initialOffset
+ (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
if (sectorOffset < initialOffset || sectorOffset >= data.Length)
return null;
// Seek to the next sector
2025-10-27 22:43:56 -04:00
data.SeekIfPossible(sectorOffset, SeekOrigin.Begin);
// Try to parse the sectors
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
2026-01-25 14:30:18 -05:00
if (sectorNumbers is null)
return null;
// Add the sector shifts
miniFatSectors.AddRange(sectorNumbers);
// Get the next sector from the FAT
currentSector = binary.FATSectorNumbers[(int)currentSector];
}
// Assign the mini FAT sectors table
binary.MiniFATSectorNumbers = [.. miniFatSectors];
#endregion
#region Directory Entries
// Create a directory sector table
var directorySectors = new List<DirectoryEntry>();
// Get the number of directory sectors
uint directorySectorCount = 0;
switch (fileHeader.MajorVersion)
{
case 3:
directorySectorCount = int.MaxValue;
break;
case 4:
directorySectorCount = fileHeader.NumberOfDirectorySectors;
break;
2026-01-25 16:15:05 -05:00
default:
// TODO: Log invalid values
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;
2025-07-29 23:41:42 -04:00
// If we have an unusable sector for a version 3 file
if (directorySectorCount == int.MaxValue && currentSector > SectorNumber.MAXREGSECT)
break;
2025-07-29 23:41:42 -04:00
// Get the new next sector information
2025-08-19 09:09:33 -04:00
long sectorOffset = initialOffset
+ (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
if (sectorOffset < initialOffset || sectorOffset >= data.Length)
2025-07-29 23:41:42 -04:00
return null;
2025-07-29 23:41:42 -04:00
// Seek to the next sector
2025-10-27 22:43:56 -04:00
data.SeekIfPossible(sectorOffset, SeekOrigin.Begin);
2025-07-29 23:41:42 -04:00
// Try to parse the sectors
var directoryEntries = ParseDirectoryEntries(data, fileHeader.SectorShift, fileHeader.MajorVersion);
2026-01-25 14:30:18 -05:00
if (directoryEntries is null)
2025-07-29 23:41:42 -04:00
return null;
2025-07-29 23:41:42 -04:00
// Add the sector shifts
directorySectors.AddRange(directoryEntries);
// Get the next sector from the FAT
currentSector = binary.FATSectorNumbers[(int)currentSector];
}
// Assign the Directory sectors table
binary.DirectoryEntries = [.. directorySectors];
#endregion
return binary;
}
catch
{
// Ignore the actual error
return null;
}
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a DirectoryEntry
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled DirectoryEntry on success, null on error</returns>
public static DirectoryEntry ParseDirectoryEntry(Stream data)
{
2024-12-16 23:08:45 -05:00
var obj = new DirectoryEntry();
byte[] name = data.ReadBytes(64);
obj.Name = Encoding.Unicode.GetString(name).DecodeStreamName()?.TrimEnd('\0') ?? string.Empty;
2024-12-16 23:08:45 -05:00
obj.NameLength = data.ReadUInt16LittleEndian();
obj.ObjectType = (ObjectType)data.ReadByteValue();
obj.ColorFlag = (ColorFlag)data.ReadByteValue();
obj.LeftSiblingID = (StreamID)data.ReadUInt32LittleEndian();
obj.RightSiblingID = (StreamID)data.ReadUInt32LittleEndian();
obj.ChildID = (StreamID)data.ReadUInt32LittleEndian();
obj.CLSID = data.ReadGuid();
obj.StateBits = data.ReadUInt32LittleEndian();
obj.CreationTime = data.ReadUInt64LittleEndian();
obj.ModifiedTime = data.ReadUInt64LittleEndian();
obj.StartingSectorLocation = data.ReadUInt32LittleEndian();
obj.StreamSize = data.ReadUInt64LittleEndian();
return obj;
}
2024-12-16 23:08:45 -05:00
/// <summary>
/// Parse a Stream into a FileHeader
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled FileHeader on success, null on error</returns>
public static FileHeader ParseFileHeader(Stream data)
{
var obj = new FileHeader();
obj.Signature = data.ReadUInt64LittleEndian();
obj.CLSID = data.ReadGuid();
obj.MinorVersion = data.ReadUInt16LittleEndian();
obj.MajorVersion = data.ReadUInt16LittleEndian();
obj.ByteOrder = data.ReadUInt16LittleEndian();
obj.SectorShift = data.ReadUInt16LittleEndian();
obj.MiniSectorShift = data.ReadUInt16LittleEndian();
obj.Reserved = data.ReadBytes(6);
obj.NumberOfDirectorySectors = data.ReadUInt32LittleEndian();
obj.NumberOfFATSectors = data.ReadUInt32LittleEndian();
obj.FirstDirectorySectorLocation = data.ReadUInt32LittleEndian();
obj.TransactionSignatureNumber = data.ReadUInt32LittleEndian();
obj.MiniStreamCutoffSize = data.ReadUInt32LittleEndian();
obj.FirstMiniFATSectorLocation = data.ReadUInt32LittleEndian();
obj.NumberOfMiniFATSectors = data.ReadUInt32LittleEndian();
obj.FirstDIFATSectorLocation = data.ReadUInt32LittleEndian();
obj.NumberOfDIFATSectors = data.ReadUInt32LittleEndian();
obj.DIFAT = new SectorNumber[109];
for (int i = 0; i < 109; i++)
{
obj.DIFAT[i] = (SectorNumber)data.ReadUInt32LittleEndian();
}
// Skip rest of sector for version 4
2024-12-16 23:08:45 -05:00
if (obj.MajorVersion == 4)
_ = data.ReadBytes(3584);
2024-12-16 23:08:45 -05:00
return obj;
}
/// <summary>
/// Parse a Stream into a sector full of sector numbers
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="sectorShift">Sector shift from the header</param>
/// <returns>Filled sector full of sector numbers on success, null on error</returns>
2024-04-23 21:45:54 -04:00
private static SectorNumber[] ParseSectorNumbers(Stream data, ushort sectorShift)
{
int sectorCount = (int)(Math.Pow(2, sectorShift) / sizeof(uint));
2024-04-23 21:45:54 -04:00
var sectorNumbers = new SectorNumber[sectorCount];
for (int i = 0; i < sectorNumbers.Length; i++)
{
sectorNumbers[i] = (SectorNumber)data.ReadUInt32LittleEndian();
}
return sectorNumbers;
}
/// <summary>
/// Parse a Stream into a sector full of directory entries
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="sectorShift">Sector shift from the header</param>
/// <param name="majorVersion">Major version from the header</param>
/// <returns>Filled sector full of directory entries on success, null on error</returns>
private static DirectoryEntry[]? ParseDirectoryEntries(Stream data, ushort sectorShift, ushort majorVersion)
{
// <see href="https://winprotocoldoc.z19.web.core.windows.net/MS-CFB/%5bMS-CFB%5d.pdf"/>
int directoryEntrySize = 128;
2025-07-29 23:41:42 -04:00
int dirsPerSector = (int)(Math.Pow(2, sectorShift) / directoryEntrySize);
var directoryEntries = new DirectoryEntry[dirsPerSector];
for (int i = 0; i < directoryEntries.Length; i++)
{
2024-12-16 23:08:45 -05:00
var directoryEntry = ParseDirectoryEntry(data);
2024-11-27 20:30:31 -05:00
// Handle version 3 entries
if (majorVersion == 3)
2025-07-30 16:56:15 -04:00
directoryEntry.StreamSize &= 0x00000000FFFFFFFF;
2024-11-27 20:30:31 -05:00
directoryEntries[i] = directoryEntry;
}
return directoryEntries;
}
2023-09-08 21:25:49 -04:00
}
}