From fbe09d9082da682832e14ec0aeed58df29e03a4d Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 13 Jan 2023 22:24:25 -0800 Subject: [PATCH] Add IS-CAB wrapper --- BurnOutSharp.Builders/InstallShieldCabinet.cs | 1 + .../InstallShieldCabinet/VolumeHeader.cs | 1 + BurnOutSharp.Wrappers/InstallShieldCabinet.cs | 538 ++++++++++++++++++ 3 files changed, 540 insertions(+) create mode 100644 BurnOutSharp.Wrappers/InstallShieldCabinet.cs diff --git a/BurnOutSharp.Builders/InstallShieldCabinet.cs b/BurnOutSharp.Builders/InstallShieldCabinet.cs index a7c84fff..e8d3a780 100644 --- a/BurnOutSharp.Builders/InstallShieldCabinet.cs +++ b/BurnOutSharp.Builders/InstallShieldCabinet.cs @@ -380,6 +380,7 @@ namespace BurnOutSharp.Builders } else { + // TODO: Should standard and high values be combined? volumeHeader.DataOffset = data.ReadUInt32(); volumeHeader.DataOffsetHigh = data.ReadUInt32(); volumeHeader.FirstFileIndex = data.ReadUInt32(); diff --git a/BurnOutSharp.Models/InstallShieldCabinet/VolumeHeader.cs b/BurnOutSharp.Models/InstallShieldCabinet/VolumeHeader.cs index b3877fb6..f60f1ed9 100644 --- a/BurnOutSharp.Models/InstallShieldCabinet/VolumeHeader.cs +++ b/BurnOutSharp.Models/InstallShieldCabinet/VolumeHeader.cs @@ -1,6 +1,7 @@ namespace BurnOutSharp.Models.InstallShieldCabinet { /// + /// TODO: Should standard and high values be combined? public sealed class VolumeHeader { public uint DataOffset; diff --git a/BurnOutSharp.Wrappers/InstallShieldCabinet.cs b/BurnOutSharp.Wrappers/InstallShieldCabinet.cs new file mode 100644 index 00000000..8c43241e --- /dev/null +++ b/BurnOutSharp.Wrappers/InstallShieldCabinet.cs @@ -0,0 +1,538 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace BurnOutSharp.Wrappers +{ + public partial class InstallShieldCabinet : WrapperBase + { + #region Pass-Through Properties + + #region Common Header + + /// + public string Signature => _cabinet.CommonHeader.Signature; + + /// + public uint Version => _cabinet.CommonHeader.Version; + + /// + public uint VolumeInfo => _cabinet.CommonHeader.VolumeInfo; + + /// + public uint CabDescriptorOffset => _cabinet.CommonHeader.CabDescriptorOffset; + + /// + public uint CabDescriptorSize => _cabinet.CommonHeader.CabDescriptorSize; + + #endregion + + #region Volume Header + + /// + public uint DataOffset => _cabinet.VolumeHeader.DataOffset; + + /// + public uint DataOffsetHigh => _cabinet.VolumeHeader.DataOffsetHigh; + + /// + public uint FirstFileIndex => _cabinet.VolumeHeader.FirstFileIndex; + + /// + public uint LastFileIndex => _cabinet.VolumeHeader.LastFileIndex; + + /// + public uint FirstFileOffset => _cabinet.VolumeHeader.FirstFileOffset; + + /// + public uint FirstFileOffsetHigh => _cabinet.VolumeHeader.FirstFileOffsetHigh; + + /// + public uint FirstFileSizeExpanded => _cabinet.VolumeHeader.FirstFileSizeExpanded; + + /// + public uint FirstFileSizeExpandedHigh => _cabinet.VolumeHeader.FirstFileSizeExpandedHigh; + + /// + public uint FirstFileSizeCompressed => _cabinet.VolumeHeader.FirstFileSizeCompressed; + + /// + public uint FirstFileSizeCompressedHigh => _cabinet.VolumeHeader.FirstFileSizeCompressedHigh; + + /// + public uint LastFileOffset => _cabinet.VolumeHeader.LastFileOffset; + + /// + public uint LastFileOffsetHigh => _cabinet.VolumeHeader.LastFileOffsetHigh; + + /// + public uint LastFileSizeExpanded => _cabinet.VolumeHeader.LastFileSizeExpanded; + + /// + public uint LastFileSizeExpandedHigh => _cabinet.VolumeHeader.LastFileSizeExpandedHigh; + + /// + public uint LastFileSizeCompressed => _cabinet.VolumeHeader.LastFileSizeCompressed; + + /// + public uint LastFileSizeCompressedHigh => _cabinet.VolumeHeader.LastFileSizeCompressedHigh; + + #endregion + + #region File Descriptor Offsets + + /// + public uint[] FileDescriptorOffsets => _cabinet.FileDescriptorOffsets; + + #endregion + + #region Directory Descriptors + + /// + public Models.InstallShieldCabinet.FileDescriptor[] DirectoryDescriptors => _cabinet.DirectoryDescriptors; + + #endregion + + #region File Descriptors + + /// + public Models.InstallShieldCabinet.FileDescriptor[] FileDescriptors => _cabinet.FileDescriptors; + + #endregion + + #region File Group Offsets + + /// + public Dictionary FileGroupOffsets => _cabinet.FileGroupOffsets; + + #endregion + + #region File Groups + + /// + public Models.InstallShieldCabinet.FileGroup[] FileGroups => _cabinet.FileGroups; + + #endregion + + #region Component Offsets + + /// + public Dictionary ComponentOffsets => _cabinet.ComponentOffsets; + + #endregion + + #region Components + + /// + public Models.InstallShieldCabinet.Component[] Components => _cabinet.Components; + + #endregion + + #endregion + + #region Extension Properties + + /// + /// The major version of the cabinet + /// + public int MajorVersion + { + get + { + uint majorVersion = Version; + if (majorVersion >> 24 == 1) + { + majorVersion = (majorVersion >> 12) & 0x0F; + } + else if (majorVersion >> 24 == 2 || majorVersion >> 24 == 4) + { + majorVersion = majorVersion & 0xFFFF; + if (majorVersion != 0) + majorVersion /= 100; + } + + return (int)majorVersion; + } + } + + #endregion + + #region Instance Variables + + /// + /// Internal representation of the cabinet + /// + private Models.InstallShieldCabinet.Cabinet _cabinet; + + #endregion + + #region Constructors + + /// + /// Private constructor + /// + private InstallShieldCabinet() { } + + /// + /// Create an InstallShield Cabinet from a byte array and offset + /// + /// Byte array representing the cabinet + /// Offset within the array to parse + /// A cabinet wrapper on success, null on failure + public static InstallShieldCabinet 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 InstallShield Cabinet from a Stream + /// + /// Stream representing the cabinet + /// A cabinet wrapper on success, null on failure + public static InstallShieldCabinet Create(Stream data) + { + // If the data is invalid + if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead) + return null; + + var cabinet = Builders.InstallShieldCabinet.ParseCabinet(data); + if (cabinet == null) + return null; + + var wrapper = new InstallShieldCabinet + { + _cabinet = cabinet, + _dataSource = DataSource.Stream, + _streamData = data, + }; + return wrapper; + } + + #endregion + + #region Printing + + /// + public override StringBuilder PrettyPrint() + { + StringBuilder builder = new StringBuilder(); + + builder.AppendLine("InstallShield Cabinet Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(); + + // Headers + PrintCommonHeader(builder); + PrintVolumeHeader(builder); + + // File Descriptors + PrintFileDescriptorOffsets(builder); + PrintDirectoryDescriptors(builder); + PrintFileDescriptors(builder); + + // File Groups + PrintFileGroupOffsets(builder); + PrintFileGroups(builder); + + // Components + PrintComponentOffsets(builder); + PrintComponents(builder); + + return builder; + } + + /// + /// Print common header information + /// + /// StringBuilder to append information to + private void PrintCommonHeader(StringBuilder builder) + { + builder.AppendLine(" Common Header Information:"); + builder.AppendLine(" -------------------------"); + builder.AppendLine($" Signature: {Signature}"); + builder.AppendLine($" Version: {Version} (0x{Version:X}) [{MajorVersion}]"); + builder.AppendLine($" Volume info: {VolumeInfo} (0x{VolumeInfo:X})"); + builder.AppendLine($" Cabinet descriptor offset: {CabDescriptorOffset} (0x{CabDescriptorOffset:X})"); + builder.AppendLine($" Cabinet descriptor size: {CabDescriptorSize} (0x{CabDescriptorSize:X})"); + builder.AppendLine(); + } + + /// + /// Print volume header information + /// + /// StringBuilder to append information to + private void PrintVolumeHeader(StringBuilder builder) + { + builder.AppendLine(" Volume Header Information:"); + builder.AppendLine(" -------------------------"); + if (MajorVersion <= 5) + { + builder.AppendLine($" Data offset: {DataOffset} (0x{DataOffset:X})"); + builder.AppendLine($" First file index: {FirstFileIndex} (0x{FirstFileIndex:X})"); + builder.AppendLine($" Last file index: {LastFileIndex} (0x{LastFileIndex:X})"); + builder.AppendLine($" First file offset: {FirstFileOffset} (0x{FirstFileOffset:X})"); + builder.AppendLine($" First file size expanded: {FirstFileSizeExpanded} (0x{FirstFileSizeExpanded:X})"); + builder.AppendLine($" First file size compressed: {FirstFileSizeCompressed} (0x{FirstFileSizeCompressed:X})"); + builder.AppendLine($" Last file offset: {LastFileOffset} (0x{LastFileOffset:X})"); + builder.AppendLine($" Last file size expanded: {LastFileSizeExpanded} (0x{LastFileSizeExpanded:X})"); + builder.AppendLine($" Last file size compressed: {LastFileSizeCompressed} (0x{LastFileSizeCompressed:X})"); + } + else + { + // TODO: Should standard and high values be combined? + builder.AppendLine($" Data offset: {DataOffset} (0x{DataOffset:X})"); + builder.AppendLine($" Data offset high: {DataOffsetHigh} (0x{DataOffsetHigh:X})"); + builder.AppendLine($" First file index: {FirstFileIndex} (0x{FirstFileIndex:X})"); + builder.AppendLine($" Last file index: {LastFileIndex} (0x{LastFileIndex:X})"); + builder.AppendLine($" First file offset: {FirstFileOffset} (0x{FirstFileOffset:X})"); + builder.AppendLine($" First file offset high: {FirstFileOffsetHigh} (0x{FirstFileOffsetHigh:X})"); + builder.AppendLine($" First file size expanded: {FirstFileSizeExpanded} (0x{FirstFileSizeExpanded:X})"); + builder.AppendLine($" First file size expanded high: {FirstFileSizeExpandedHigh} (0x{FirstFileSizeExpandedHigh:X})"); + builder.AppendLine($" First file size compressed: {FirstFileSizeCompressed} (0x{FirstFileSizeCompressed:X})"); + builder.AppendLine($" First file size compressed high: {FirstFileSizeCompressedHigh} (0x{FirstFileSizeCompressedHigh:X})"); + builder.AppendLine($" Last file offset: {LastFileOffset} (0x{LastFileOffset:X})"); + builder.AppendLine($" Last file offset high: {LastFileOffsetHigh} (0x{LastFileOffsetHigh:X})"); + builder.AppendLine($" Last file size expanded: {LastFileSizeExpanded} (0x{LastFileSizeExpanded:X})"); + builder.AppendLine($" Last file size expanded high: {LastFileSizeExpandedHigh} (0x{LastFileSizeExpandedHigh:X})"); + builder.AppendLine($" Last file size compressed: {LastFileSizeCompressed} (0x{LastFileSizeCompressed:X})"); + builder.AppendLine($" Last file size compressed high: {LastFileSizeCompressedHigh} (0x{LastFileSizeCompressedHigh:X})"); + } + builder.AppendLine(); + } + + /// + /// Print file descriptor offsets information + /// + /// StringBuilder to append information to + private void PrintFileDescriptorOffsets(StringBuilder builder) + { + builder.AppendLine(" File Descriptor Offsets:"); + builder.AppendLine(" -------------------------"); + if (FileDescriptorOffsets == null || FileDescriptorOffsets.Length == 0) + { + builder.AppendLine(" No file descriptor offsets"); + } + else + { + for (int i = 0; i < FileDescriptorOffsets.Length; i++) + { + builder.AppendLine($" File Descriptor Offset {i}: {FileDescriptorOffsets[i]} (0x{FileDescriptorOffsets[i]:X})"); + } + } + builder.AppendLine(); + } + + /// + /// Print directory descriptors information + /// + /// StringBuilder to append information to + private void PrintDirectoryDescriptors(StringBuilder builder) + { + builder.AppendLine(" Directory Descriptors:"); + builder.AppendLine(" -------------------------"); + if (DirectoryDescriptors == null || DirectoryDescriptors.Length == 0) + { + builder.AppendLine(" No directory descriptors"); + } + else + { + for (int i = 0; i < DirectoryDescriptors.Length; i++) + { + var directoryDescriptor = DirectoryDescriptors[i]; + builder.AppendLine($" Directory Descriptor {i}:"); + builder.AppendLine($" Name offset: {directoryDescriptor.NameOffset} (0x{directoryDescriptor.NameOffset:X})"); + builder.AppendLine($" Name: {directoryDescriptor.Name ?? "[NULL]"}"); + builder.AppendLine($" Directory index: {directoryDescriptor.DirectoryIndex} (0x{directoryDescriptor.DirectoryIndex:X})"); + builder.AppendLine($" Flags: {directoryDescriptor.Flags} (0x{directoryDescriptor.Flags:X})"); + builder.AppendLine($" Expanded size: {directoryDescriptor.ExpandedSize} (0x{directoryDescriptor.ExpandedSize:X})"); + builder.AppendLine($" Compressed size: {directoryDescriptor.CompressedSize} (0x{directoryDescriptor.CompressedSize:X})"); + builder.AppendLine($" Data offset: {directoryDescriptor.DataOffset} (0x{directoryDescriptor.DataOffset:X})"); + builder.AppendLine($" MD5: {BitConverter.ToString(directoryDescriptor.MD5 ?? new byte[0]).Replace('-', ' ')}"); + builder.AppendLine($" Volume: {directoryDescriptor.Volume} (0x{directoryDescriptor.Volume:X})"); + builder.AppendLine($" Link previous: {directoryDescriptor.LinkPrevious} (0x{directoryDescriptor.LinkPrevious:X})"); + builder.AppendLine($" Link next: {directoryDescriptor.LinkNext} (0x{directoryDescriptor.LinkNext:X})"); + builder.AppendLine($" Link flags: {directoryDescriptor.LinkFlags} (0x{directoryDescriptor.LinkFlags:X})"); + } + } + builder.AppendLine(); + } + + /// + /// Print file descriptors information + /// + /// StringBuilder to append information to + private void PrintFileDescriptors(StringBuilder builder) + { + builder.AppendLine(" File Descriptors:"); + builder.AppendLine(" -------------------------"); + if (FileDescriptors == null || FileDescriptors.Length == 0) + { + builder.AppendLine(" No file descriptors"); + } + else + { + for (int i = 0; i < FileDescriptors.Length; i++) + { + var fileDescriptor = FileDescriptors[i]; + builder.AppendLine($" File Descriptor {i}:"); + builder.AppendLine($" Name offset: {fileDescriptor.NameOffset} (0x{fileDescriptor.NameOffset:X})"); + builder.AppendLine($" Name: {fileDescriptor.Name ?? "[NULL]"}"); + builder.AppendLine($" Directory index: {fileDescriptor.DirectoryIndex} (0x{fileDescriptor.DirectoryIndex:X})"); + builder.AppendLine($" Flags: {fileDescriptor.Flags} (0x{fileDescriptor.Flags:X})"); + builder.AppendLine($" Expanded size: {fileDescriptor.ExpandedSize} (0x{fileDescriptor.ExpandedSize:X})"); + builder.AppendLine($" Compressed size: {fileDescriptor.CompressedSize} (0x{fileDescriptor.CompressedSize:X})"); + builder.AppendLine($" Data offset: {fileDescriptor.DataOffset} (0x{fileDescriptor.DataOffset:X})"); + builder.AppendLine($" MD5: {BitConverter.ToString(fileDescriptor.MD5 ?? new byte[0]).Replace('-', ' ')}"); + builder.AppendLine($" Volume: {fileDescriptor.Volume} (0x{fileDescriptor.Volume:X})"); + builder.AppendLine($" Link previous: {fileDescriptor.LinkPrevious} (0x{fileDescriptor.LinkPrevious:X})"); + builder.AppendLine($" Link next: {fileDescriptor.LinkNext} (0x{fileDescriptor.LinkNext:X})"); + builder.AppendLine($" Link flags: {fileDescriptor.LinkFlags} (0x{fileDescriptor.LinkFlags:X})"); + } + } + builder.AppendLine(); + } + + /// + /// Print file group offsets information + /// + /// StringBuilder to append information to + private void PrintFileGroupOffsets(StringBuilder builder) + { + builder.AppendLine(" File Group Offsets:"); + builder.AppendLine(" -------------------------"); + if (FileGroupOffsets == null || FileGroupOffsets.Count == 0) + { + builder.AppendLine(" No file group offsets"); + } + else + { + foreach (var kvp in FileGroupOffsets) + { + long offset = kvp.Key; + var offsetList = kvp.Value; + builder.AppendLine($" File Group Offset {offset}:"); + builder.AppendLine($" Name offset: {offsetList.NameOffset} (0x{offsetList.NameOffset:X})"); + builder.AppendLine($" Name: {offsetList.Name ?? "[NULL]"}"); + builder.AppendLine($" Descriptor offset: {offsetList.DescriptorOffset} (0x{offsetList.DescriptorOffset:X})"); + builder.AppendLine($" Next offset: {offsetList.NextOffset} (0x{offsetList.NextOffset:X})"); + } + } + builder.AppendLine(); + } + + /// + /// Print file groups information + /// + /// StringBuilder to append information to + private void PrintFileGroups(StringBuilder builder) + { + builder.AppendLine(" File Groups:"); + builder.AppendLine(" -------------------------"); + if (FileGroups == null || FileGroups.Length == 0) + { + builder.AppendLine(" No file groups"); + } + else + { + for (int i = 0; i < FileGroups.Length; i++) + { + var fileGroup = FileGroups[i]; + builder.AppendLine($" File Group {i}:"); + builder.AppendLine($" Name offset: {fileGroup.NameOffset} (0x{fileGroup.NameOffset:X})"); + builder.AppendLine($" Name: {fileGroup.Name ?? "[NULL]"}"); + builder.AppendLine($" First file: {fileGroup.FirstFile} (0x{fileGroup.FirstFile:X})"); + builder.AppendLine($" Last file: {fileGroup.LastFile} (0x{fileGroup.LastFile:X})"); + } + } + builder.AppendLine(); + } + + /// + /// Print component offsets information + /// + /// StringBuilder to append information to + private void PrintComponentOffsets(StringBuilder builder) + { + builder.AppendLine(" Component Offsets:"); + builder.AppendLine(" -------------------------"); + if (ComponentOffsets == null || ComponentOffsets.Count == 0) + { + builder.AppendLine(" No component offsets"); + } + else + { + foreach (var kvp in ComponentOffsets) + { + long offset = kvp.Key; + var offsetList = kvp.Value; + builder.AppendLine($" Component Offset {offset}:"); + builder.AppendLine($" Name offset: {offsetList.NameOffset} (0x{offsetList.NameOffset:X})"); + builder.AppendLine($" Name: {offsetList.Name ?? "[NULL]"}"); + builder.AppendLine($" Descriptor offset: {offsetList.DescriptorOffset} (0x{offsetList.DescriptorOffset:X})"); + builder.AppendLine($" Next offset: {offsetList.NextOffset} (0x{offsetList.NextOffset:X})"); + } + } + builder.AppendLine(); + } + + /// + /// Print components information + /// + /// StringBuilder to append information to + private void PrintComponents(StringBuilder builder) + { + builder.AppendLine(" Components:"); + builder.AppendLine(" -------------------------"); + if (Components == null || Components.Length == 0) + { + builder.AppendLine(" No components"); + } + else + { + for (int i = 0; i < Components.Length; i++) + { + var component = Components[i]; + builder.AppendLine($" Component {i}:"); + builder.AppendLine($" Name offset: {component.NameOffset} (0x{component.NameOffset:X})"); + builder.AppendLine($" Name: {component.Name ?? "[NULL]"}"); + builder.AppendLine($" File group count: {component.FileGroupCount} (0x{component.FileGroupCount:X})"); + builder.AppendLine($" File group table offset: {component.FileGroupTableOffset} (0x{component.FileGroupTableOffset:X})"); + builder.AppendLine($" File group names:"); + builder.AppendLine(" -------------------------"); + if (component.FileGroupNames == null || component.FileGroupNames.Length == 0) + { + builder.AppendLine(" No file group names"); + } + else + { + for (int j = 0; j < component.FileGroupNames.Length; j++) + { + builder.AppendLine($" File Group Name {j}: {component.FileGroupNames[j] ?? "[NULL]"}"); + } + } + } + } + builder.AppendLine(); + } + +#if NET6_0_OR_GREATER + + /// + public override string ExportJSON() => System.Text.Json.JsonSerializer.Serialize(_cabinet, _jsonSerializerOptions); + +#endif + + #endregion + } +}