using System; using System.Collections.Generic; using System.IO; using System.Text; using SabreTools.Data.Extensions; using SabreTools.Data.Models.InstallShieldCabinet; using SabreTools.IO.Extensions; using SabreTools.Numerics.Extensions; using SabreTools.Text.Extensions; using static SabreTools.Data.Models.InstallShieldCabinet.Constants; #pragma warning disable IDE0017 // Simplify object initialization namespace SabreTools.Serialization.Readers { public class InstallShieldCabinet : BaseBinaryReader { /// public override Cabinet? Deserialize(Stream? data) { // If the data is invalid if (data is null || !data.CanRead) return null; try { // Cache the current offset long initialOffset = data.Position; // Create a new cabinet to fill var cabinet = new Cabinet(); #region Common Header // Try to parse the cabinet header var commonHeader = ParseCommonHeader(data); if (commonHeader.Signature != SignatureString) return null; // Set the cabinet header cabinet.CommonHeader = commonHeader; #endregion // Get the major version int majorVersion = commonHeader.GetMajorVersion(); #region Volume Header // Set the volume header cabinet.VolumeHeader = ParseVolumeHeader(data, majorVersion); #endregion #region Descriptor // If the descriptor does not exist if (commonHeader.DescriptorSize == 0) return cabinet; // Get the descriptor offset long descriptorOffset = initialOffset + commonHeader.DescriptorOffset; if (descriptorOffset < initialOffset || descriptorOffset >= data.Length) return null; // Seek to the descriptor data.SeekIfPossible(descriptorOffset, SeekOrigin.Begin); // Set the descriptor cabinet.Descriptor = ParseDescriptor(data); #endregion // TODO: Read the setup types table (SETUPTYPEHEADER and SETUPTYPEDESC) #region File Descriptor Offsets // Get the file table offset long fileTableOffset = descriptorOffset + cabinet.Descriptor.FileTableOffset; if (fileTableOffset < initialOffset || fileTableOffset >= data.Length) return null; // Seek to the file table data.SeekIfPossible(fileTableOffset, SeekOrigin.Begin); // Get the number of file table items uint fileTableItems; if (majorVersion <= 5) fileTableItems = cabinet.Descriptor.DirectoryCount + cabinet.Descriptor.FileCount; else fileTableItems = cabinet.Descriptor.DirectoryCount; // Create and fill the file table cabinet.FileDescriptorOffsets = new uint[fileTableItems]; for (int i = 0; i < cabinet.FileDescriptorOffsets.Length; i++) { cabinet.FileDescriptorOffsets[i] = data.ReadUInt32LittleEndian(); } #endregion #region Directory Descriptors // Create and fill the directory descriptors cabinet.DirectoryNames = new string[cabinet.Descriptor.DirectoryCount]; for (int i = 0; i < cabinet.Descriptor.DirectoryCount; i++) { // Get the directory descriptor offset long offset = descriptorOffset + cabinet.Descriptor.FileTableOffset + cabinet.FileDescriptorOffsets[i]; // If we have an invalid offset if (offset < initialOffset || offset >= data.Length) continue; // Seek to the file descriptor offset data.SeekIfPossible(offset, SeekOrigin.Begin); // Create and add the file descriptor string? directoryName = ParseDirectoryName(data, majorVersion); if (directoryName is not null) cabinet.DirectoryNames[i] = directoryName; } #endregion #region File Descriptors // Create and fill the file descriptors cabinet.FileDescriptors = new FileDescriptor[cabinet.Descriptor.FileCount]; for (int i = 0; i < cabinet.Descriptor.FileCount; i++) { // Get the file descriptor offset long offset; if (majorVersion <= 5) { offset = descriptorOffset + cabinet.Descriptor.FileTableOffset + cabinet.FileDescriptorOffsets[cabinet.Descriptor.DirectoryCount + i]; } else { offset = descriptorOffset + cabinet.Descriptor.FileTableOffset + cabinet.Descriptor.FileTableOffset2 + (uint)(i * 0x57); } // If we have an invalid offset if (offset < initialOffset || offset >= data.Length) continue; // Seek to the file descriptor offset data.SeekIfPossible(offset, SeekOrigin.Begin); // Create and add the file descriptor cabinet.FileDescriptors[i] = ParseFileDescriptor(data, majorVersion, descriptorOffset + cabinet.Descriptor.FileTableOffset); } #endregion #region File Group Offsets // Create and fill the file group offsets cabinet.FileGroupOffsets = []; for (int i = 0; i < cabinet.Descriptor.FileGroupOffsets.Length; i++) { // Get the file group offset long offset = cabinet.Descriptor.FileGroupOffsets![i]; if (offset == 0) continue; // Adjust the file group offset offset += descriptorOffset; if (offset < initialOffset || offset >= data.Length) continue; // Seek to the file group offset data.SeekIfPossible(offset, SeekOrigin.Begin); // Create and add the offset OffsetList offsetList = ParseOffsetList(data, majorVersion, descriptorOffset); cabinet.FileGroupOffsets[offset] = offsetList; // If we have a nonzero next offset long nextOffset = offsetList.NextOffset; while (nextOffset != 0) { // Get the next offset to read long internalOffset = descriptorOffset + nextOffset; // Seek to the file group offset data.SeekIfPossible(internalOffset, SeekOrigin.Begin); // Create and add the offset offsetList = ParseOffsetList(data, majorVersion, descriptorOffset); cabinet.FileGroupOffsets[nextOffset] = offsetList; // Set the next offset nextOffset = offsetList.NextOffset; } } #endregion #region File Groups // Create the file groups array cabinet.FileGroups = new FileGroup[cabinet.FileGroupOffsets.Count]; // Create and fill the file groups int fileGroupId = 0; foreach (var kvp in cabinet.FileGroupOffsets) { // Get the offset OffsetList? list = kvp.Value; if (list is null) continue; // If we have an invalid offset if (list.DescriptorOffset <= 0) continue; /// Seek to the file group data.SeekIfPossible(descriptorOffset + list.DescriptorOffset, SeekOrigin.Begin); // Add the file group cabinet.FileGroups[fileGroupId++] = ParseFileGroup(data, majorVersion, descriptorOffset); } // Order the groups by first file index Array.Sort(cabinet.FileGroups, (a, b) => (int)a.FirstFile - (int)b.FirstFile); #endregion #region Component Offsets // Create and fill the component offsets cabinet.ComponentOffsets = []; for (int i = 0; i < cabinet.Descriptor.ComponentOffsets.Length; i++) { // Get the component offset long offset = cabinet.Descriptor.ComponentOffsets![i]; if (offset == 0) continue; // Adjust the component offset offset += descriptorOffset; if (offset < initialOffset || offset >= data.Length) continue; // Seek to the component offset data.SeekIfPossible(offset, SeekOrigin.Begin); // Create and add the offset OffsetList offsetList = ParseOffsetList(data, majorVersion, descriptorOffset); cabinet.ComponentOffsets[cabinet.Descriptor.ComponentOffsets[i]] = offsetList; // If we have a nonzero next offset long nextOffset = offsetList.NextOffset; while (nextOffset != 0) { // Get the next offset to read long internalOffset = descriptorOffset + nextOffset; // Seek to the file group offset data.SeekIfPossible(internalOffset, SeekOrigin.Begin); // Create and add the offset offsetList = ParseOffsetList(data, majorVersion, descriptorOffset); cabinet.ComponentOffsets[nextOffset] = offsetList; // Set the next offset nextOffset = offsetList.NextOffset; } } #endregion #region Components // Create the components array cabinet.Components = new Component[cabinet.ComponentOffsets.Count]; // Create and fill the components int componentId = 0; foreach (KeyValuePair kvp in cabinet.ComponentOffsets) { // Get the offset OffsetList? list = kvp.Value; if (list is null) continue; // If we have an invalid offset if (list.DescriptorOffset <= 0) continue; // Seek to the component data.SeekIfPossible(descriptorOffset + list.DescriptorOffset, SeekOrigin.Begin); // Add the component cabinet.Components[componentId++] = ParseComponent(data, majorVersion, descriptorOffset); } #endregion return cabinet; } catch { // Ignore the actual error return null; } } /// /// Parse a Stream into a CommonHeader /// /// Stream to parse /// Filled CommonHeader on success, null on error public static CommonHeader ParseCommonHeader(Stream data) { var obj = new CommonHeader(); byte[] signature = data.ReadBytes(4); obj.Signature = Encoding.ASCII.GetString(signature); obj.Version = data.ReadUInt32LittleEndian(); obj.VolumeInfo = data.ReadUInt32LittleEndian(); obj.DescriptorOffset = data.ReadUInt32LittleEndian(); obj.DescriptorSize = data.ReadUInt32LittleEndian(); return obj; } /// /// Parse a Stream into a Component /// /// Stream to parse /// Major version of the cabinet /// Offset of the cabinet descriptor /// Filled Component on success, null on error public static Component ParseComponent(Stream data, int majorVersion, long descriptorOffset) { var obj = new Component(); obj.NameOffset = data.ReadUInt32LittleEndian(); obj.DescriptionOffset = data.ReadUInt32LittleEndian(); obj.StatusTextOffset = data.ReadUInt32LittleEndian(); obj.Status = (ComponentStatus)data.ReadUInt16LittleEndian(); obj.PasswordOffset = data.ReadUInt32LittleEndian(); obj.MiscOffset = data.ReadUInt32LittleEndian(); obj.ComponentIndex = data.ReadUInt16LittleEndian(); obj.DisplayNameOffset = data.ReadUInt32LittleEndian(); obj.CDRomFolderOffset = data.ReadUInt32LittleEndian(); obj.HTTPLocationOffset = data.ReadUInt32LittleEndian(); obj.FTPLocationOffset = data.ReadUInt32LittleEndian(); obj.Guid = new Guid[2]; for (int i = 0; i < obj.Guid.Length; i++) { obj.Guid[i] = data.ReadGuid(); } obj.CLSIDOffset = data.ReadUInt32LittleEndian(); obj.Reserved2 = data.ReadBytes(28); obj.Reserved3 = data.ReadBytes(majorVersion <= 5 ? 2 : 1); obj.DependsCount = data.ReadUInt16LittleEndian(); obj.DependsOffset = data.ReadUInt32LittleEndian(); obj.FileGroupCount = data.ReadUInt16LittleEndian(); obj.FileGroupNamesOffset = data.ReadUInt32LittleEndian(); obj.X3Count = data.ReadUInt16LittleEndian(); obj.X3Offset = data.ReadUInt32LittleEndian(); obj.SubComponentsCount = data.ReadUInt16LittleEndian(); obj.SubComponentsOffset = data.ReadUInt32LittleEndian(); obj.NextComponentOffset = data.ReadUInt32LittleEndian(); obj.OnInstallingOffset = data.ReadUInt32LittleEndian(); obj.OnInstalledOffset = data.ReadUInt32LittleEndian(); obj.OnUninstallingOffset = data.ReadUInt32LittleEndian(); obj.OnUninstalledOffset = data.ReadUInt32LittleEndian(); // Cache the current position long currentPosition = data.Position; // Read the name, if possible if (obj.NameOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.NameOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.Name = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.Name = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the description, if possible if (obj.DescriptionOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.DescriptionOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.Description = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.Description = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the status text, if possible if (obj.StatusTextOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.StatusTextOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.StatusText = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.StatusText = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the display name, if possible if (obj.DisplayNameOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.DisplayNameOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.DisplayName = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.DisplayName = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the CD-ROM folder, if possible if (obj.CDRomFolderOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.CDRomFolderOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.CDRomFolder = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.CDRomFolder = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the HTTP location, if possible if (obj.HTTPLocationOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.HTTPLocationOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.HTTPLocation = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.HTTPLocation = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the FTP location, if possible if (obj.FTPLocationOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.FTPLocationOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.FTPLocation = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.FTPLocation = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the CLSID, if possible if (obj.CLSIDOffset != 0) { // Seek to the CLSID data.SeekIfPossible(descriptorOffset + obj.CLSIDOffset, SeekOrigin.Begin); // Read the GUID obj.CLSID = data.ReadGuid(); } // Read the file group names, if possible if (obj.FileGroupCount != 0 && obj.FileGroupNamesOffset != 0) { // Seek to the file group table offset data.SeekIfPossible(descriptorOffset + obj.FileGroupNamesOffset, SeekOrigin.Begin); // Read the file group names table obj.FileGroupNames = new string[obj.FileGroupCount]; for (int j = 0; j < obj.FileGroupCount; j++) { // Get the name offset uint nameOffset = data.ReadUInt32LittleEndian(); // Cache the current offset long preNameOffset = data.Position; // Seek to the string offset data.SeekIfPossible(descriptorOffset + nameOffset, SeekOrigin.Begin); if (majorVersion >= 17) obj.FileGroupNames[j] = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.FileGroupNames[j] = data.ReadNullTerminatedAnsiString() ?? string.Empty; // Seek back to the original position data.SeekIfPossible(preNameOffset, SeekOrigin.Begin); } } // Seek back to the correct offset data.SeekIfPossible(currentPosition, SeekOrigin.Begin); return obj; } /// /// Parse a Stream into a Descriptor /// /// Stream to parse /// Filled Descriptor on success, null on error public static Descriptor ParseDescriptor(Stream data) { var obj = new Descriptor(); obj.StringsOffset = data.ReadUInt32LittleEndian(); obj.Reserved0 = data.ReadUInt32LittleEndian(); obj.ComponentListOffset = data.ReadUInt32LittleEndian(); obj.FileTableOffset = data.ReadUInt32LittleEndian(); obj.Reserved1 = data.ReadUInt32LittleEndian(); obj.FileTableSize = data.ReadUInt32LittleEndian(); obj.FileTableSize2 = data.ReadUInt32LittleEndian(); obj.DirectoryCount = data.ReadUInt16LittleEndian(); obj.Reserved2 = data.ReadUInt32LittleEndian(); obj.Reserved3 = data.ReadUInt16LittleEndian(); obj.Reserved4 = data.ReadUInt32LittleEndian(); obj.FileCount = data.ReadUInt32LittleEndian(); obj.FileTableOffset2 = data.ReadUInt32LittleEndian(); obj.ComponentTableInfoCount = data.ReadUInt16LittleEndian(); obj.ComponentTableOffset = data.ReadUInt32LittleEndian(); obj.Reserved5 = data.ReadUInt32LittleEndian(); obj.Reserved6 = data.ReadUInt32LittleEndian(); obj.FileGroupOffsets = new uint[71]; for (int i = 0; i < 71; i++) { obj.FileGroupOffsets[i] = data.ReadUInt32LittleEndian(); } obj.ComponentOffsets = new uint[71]; for (int i = 0; i < 71; i++) { obj.ComponentOffsets[i] = data.ReadUInt32LittleEndian(); } obj.SetupTypesOffset = data.ReadUInt32LittleEndian(); obj.SetupTableOffset = data.ReadUInt32LittleEndian(); obj.Reserved7 = data.ReadUInt32LittleEndian(); obj.Reserved8 = data.ReadUInt32LittleEndian(); return obj; } /// /// Parse a Stream into a directory name /// /// Stream to parse /// Major version of the cabinet /// Filled directory name on success, null on error public static string? ParseDirectoryName(Stream data, int majorVersion) { // Read the string if (majorVersion >= 17) return data.ReadNullTerminatedUnicodeString(); else return data.ReadNullTerminatedAnsiString(); } /// /// Parse a Stream into a FileDescriptor /// /// Stream to parse /// Major version of the cabinet /// Offset of the cabinet descriptor /// Filled FileDescriptor on success, null on error public static FileDescriptor ParseFileDescriptor(Stream data, int majorVersion, long descriptorOffset) { var obj = new FileDescriptor(); // Read the descriptor based on version if (majorVersion <= 5) { obj.Volume = 0xFFFF; // Set by the header index obj.NameOffset = data.ReadUInt32LittleEndian(); obj.DirectoryIndex = data.ReadUInt32LittleEndian(); obj.Flags = (FileFlags)data.ReadUInt16LittleEndian(); obj.ExpandedSize = data.ReadUInt32LittleEndian(); obj.CompressedSize = data.ReadUInt32LittleEndian(); _ = data.ReadBytes(0x14); // Skip 0x14 bytes, unknown data? obj.DataOffset = data.ReadUInt32LittleEndian(); if (majorVersion == 5) obj.MD5 = data.ReadBytes(0x10); } else { obj.Flags = (FileFlags)data.ReadUInt16LittleEndian(); obj.ExpandedSize = data.ReadUInt64LittleEndian(); obj.CompressedSize = data.ReadUInt64LittleEndian(); obj.DataOffset = data.ReadUInt64LittleEndian(); obj.MD5 = data.ReadBytes(0x10); // TODO: This is actually 3 fields // - DWORD dwVerMS; // - DWORD dwVerLS; // - DWORD junk1[2]; _ = data.ReadBytes(0x10); // Skip 0x10 bytes, unknown data? obj.NameOffset = data.ReadUInt32LittleEndian(); obj.DirectoryIndex = data.ReadUInt16LittleEndian(); // TODO: This is actually 4 fields // - DWORD Attrs; // - WORD FatDate // - WORD FatTime; // - DWORD junk2; _ = data.ReadBytes(0x0C); // Skip 0x0C bytes, unknown data? obj.LinkPrevious = data.ReadUInt32LittleEndian(); obj.LinkNext = data.ReadUInt32LittleEndian(); obj.LinkFlags = (LinkFlags)data.ReadByteValue(); obj.Volume = data.ReadUInt16LittleEndian(); } // Cache the current position long currentPosition = data.Position; // Read the name, if possible if (obj.NameOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.NameOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.Name = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.Name = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Seek back to the correct offset data.SeekIfPossible(currentPosition, SeekOrigin.Begin); return obj; } /// /// Parse a Stream into a FileGroup /// /// Stream to parse /// Major version of the cabinet /// Offset of the cabinet descriptor /// Filled FileGroup on success, null on error public static FileGroup ParseFileGroup(Stream data, int majorVersion, long descriptorOffset) { var obj = new FileGroup(); obj.NameOffset = data.ReadUInt32LittleEndian(); if (majorVersion <= 5) { obj.ExpandedSize = data.ReadUInt32LittleEndian(); obj.CompressedSize = data.ReadUInt32LittleEndian(); } else { obj.ExpandedSize = data.ReadUInt64LittleEndian(); obj.CompressedSize = data.ReadUInt64LittleEndian(); } obj.Attributes = (FileGroupAttributes)data.ReadUInt16LittleEndian(); // TODO: Figure out what data lives in this area for V5 and below if (majorVersion <= 5) data.SeekIfPossible(0x36, SeekOrigin.Current); obj.FirstFile = data.ReadUInt32LittleEndian(); obj.LastFile = data.ReadUInt32LittleEndian(); obj.UnknownStringOffset = data.ReadUInt32LittleEndian(); obj.OperatingSystemOffset = data.ReadUInt32LittleEndian(); obj.LanguageOffset = data.ReadUInt32LittleEndian(); obj.HTTPLocationOffset = data.ReadUInt32LittleEndian(); obj.FTPLocationOffset = data.ReadUInt32LittleEndian(); obj.MiscOffset = data.ReadUInt32LittleEndian(); obj.TargetDirectoryOffset = data.ReadUInt32LittleEndian(); obj.OverwriteFlags = (FileGroupFlags)data.ReadUInt32LittleEndian(); obj.Reserved = new uint[4]; for (int i = 0; i < obj.Reserved.Length; i++) { obj.Reserved[i] = data.ReadUInt32LittleEndian(); } // Cache the current position long currentPosition = data.Position; // Read the name, if possible if (obj.NameOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.NameOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.Name = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.Name = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the language, if possible if (obj.LanguageOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.LanguageOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.Language = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.Language = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the HTTP location, if possible if (obj.HTTPLocationOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.HTTPLocationOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.HTTPLocation = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.HTTPLocation = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the FTP location, if possible if (obj.FTPLocationOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.FTPLocationOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.FTPLocation = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.FTPLocation = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the target directory, if possible if (obj.MiscOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.MiscOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.Misc = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.Misc = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Read the target directory, if possible if (obj.TargetDirectoryOffset != 0) { // Seek to the string data.SeekIfPossible(descriptorOffset + obj.TargetDirectoryOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.TargetDirectory = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.TargetDirectory = data.ReadNullTerminatedAnsiString() ?? string.Empty; } // Seek back to the correct offset data.SeekIfPossible(currentPosition, SeekOrigin.Begin); return obj; } /// /// Parse a Stream into an OffsetList /// /// Stream to parse /// Major version of the cabinet /// Offset of the cabinet descriptor /// Filled OffsetList on success, null on error public static OffsetList ParseOffsetList(Stream data, int majorVersion, long descriptorOffset) { var obj = new OffsetList(); obj.NameOffset = data.ReadUInt32LittleEndian(); obj.DescriptorOffset = data.ReadUInt32LittleEndian(); obj.NextOffset = data.ReadUInt32LittleEndian(); // Cache the current offset long currentOffset = data.Position; // Seek to the string offset data.SeekIfPossible(descriptorOffset + obj.NameOffset, SeekOrigin.Begin); // Read the string if (majorVersion >= 17) obj.Name = data.ReadNullTerminatedUnicodeString() ?? string.Empty; else obj.Name = data.ReadNullTerminatedAnsiString() ?? string.Empty; // Seek back to the correct offset data.SeekIfPossible(currentOffset, SeekOrigin.Begin); return obj; } /// /// Parse a Stream into a VolumeHeader /// /// Stream to parse /// Major version of the cabinet /// Filled VolumeHeader on success, null on error public static VolumeHeader ParseVolumeHeader(Stream data, int majorVersion) { var obj = new VolumeHeader(); // Read the descriptor based on version if (majorVersion <= 5) { obj.DataOffset = data.ReadUInt32LittleEndian(); _ = data.ReadBytes(0x04); // Skip 0x04 bytes, unknown data? obj.FirstFileIndex = data.ReadUInt32LittleEndian(); obj.LastFileIndex = data.ReadUInt32LittleEndian(); obj.FirstFileOffset = data.ReadUInt32LittleEndian(); obj.FirstFileSizeExpanded = data.ReadUInt32LittleEndian(); obj.FirstFileSizeCompressed = data.ReadUInt32LittleEndian(); obj.LastFileOffset = data.ReadUInt32LittleEndian(); obj.LastFileSizeExpanded = data.ReadUInt32LittleEndian(); obj.LastFileSizeCompressed = data.ReadUInt32LittleEndian(); } else { obj.DataOffset = data.ReadUInt64LittleEndian(); obj.FirstFileIndex = data.ReadUInt32LittleEndian(); obj.LastFileIndex = data.ReadUInt32LittleEndian(); obj.FirstFileOffset = data.ReadUInt64LittleEndian(); obj.FirstFileSizeExpanded = data.ReadUInt64LittleEndian(); obj.FirstFileSizeCompressed = data.ReadUInt64LittleEndian(); obj.LastFileOffset = data.ReadUInt64LittleEndian(); obj.LastFileSizeExpanded = data.ReadUInt64LittleEndian(); obj.LastFileSizeCompressed = data.ReadUInt64LittleEndian(); } return obj; } } }