diff --git a/SabreTools.Data.Models/InstallShieldCabinet/FileGroup.cs b/SabreTools.Data.Models/InstallShieldCabinet/FileGroup.cs index 1bf59443..c60801b7 100644 --- a/SabreTools.Data.Models/InstallShieldCabinet/FileGroup.cs +++ b/SabreTools.Data.Models/InstallShieldCabinet/FileGroup.cs @@ -17,12 +17,14 @@ namespace SabreTools.Data.Models.InstallShieldCabinet /// /// Size of the expanded data /// - public uint ExpandedSize { get; set; } + /// 32-bit in versions 5 and below + public ulong ExpandedSize { get; set; } /// /// Size of the compressed data /// - public uint CompressedSize { get; set; } + /// 32-bit in versions 5 and below + public ulong CompressedSize { get; set; } /// /// Attributes (junk2) @@ -54,26 +56,51 @@ namespace SabreTools.Data.Models.InstallShieldCabinet /// public uint LanguageOffset { get; set; } + /// + /// Language + /// + public string Language { get; set; } = string.Empty; + /// /// Offset to the HTTP location /// public uint HTTPLocationOffset { get; set; } + /// + /// HTTP location + /// + public string HTTPLocation { get; set; } = string.Empty; + /// /// Offset to the FTP location /// public uint FTPLocationOffset { get; set; } + /// + /// FTP location + /// + public string FTPLocation { get; set; } = string.Empty; + /// /// Misc offset /// public uint MiscOffset { get; set; } + /// + /// Misc string + /// + public string Misc { get; set; } = string.Empty; + /// /// Offset to the target directory /// public uint TargetDirectoryOffset { get; set; } + /// + /// Target directory + /// + public string TargetDirectory { get; set; } = string.Empty; + /// /// Overwrite setting flags /// diff --git a/SabreTools.Data.Models/InstallShieldCabinet/VolumeHeader.cs b/SabreTools.Data.Models/InstallShieldCabinet/VolumeHeader.cs index 1aa19351..bb8e1bd6 100644 --- a/SabreTools.Data.Models/InstallShieldCabinet/VolumeHeader.cs +++ b/SabreTools.Data.Models/InstallShieldCabinet/VolumeHeader.cs @@ -4,36 +4,29 @@ namespace SabreTools.Data.Models.InstallShieldCabinet /// TODO: Should standard and high values be combined? public sealed class VolumeHeader { - public uint DataOffset { get; set; } - - public uint DataOffsetHigh { get; set; } + /// 32-bit in versions 5 and below + public ulong DataOffset { get; set; } public uint FirstFileIndex { get; set; } public uint LastFileIndex { get; set; } - public uint FirstFileOffset { get; set; } + /// 32-bit in versions 5 and below + public ulong FirstFileOffset { get; set; } - public uint FirstFileOffsetHigh { get; set; } + /// 32-bit in versions 5 and below + public ulong FirstFileSizeExpanded { get; set; } - public uint FirstFileSizeExpanded { get; set; } + /// 32-bit in versions 5 and below + public ulong FirstFileSizeCompressed { get; set; } - public uint FirstFileSizeExpandedHigh { get; set; } + /// 32-bit in versions 5 and below + public ulong LastFileOffset { get; set; } - public uint FirstFileSizeCompressed { get; set; } + /// 32-bit in versions 5 and below + public ulong LastFileSizeExpanded { get; set; } - public uint FirstFileSizeCompressedHigh { get; set; } - - public uint LastFileOffset { get; set; } - - public uint LastFileOffsetHigh { get; set; } - - public uint LastFileSizeExpanded { get; set; } - - public uint LastFileSizeExpandedHigh { get; set; } - - public uint LastFileSizeCompressed { get; set; } - - public uint LastFileSizeCompressedHigh { get; set; } + /// 32-bit in versions 5 and below + public ulong LastFileSizeCompressed { get; set; } } } diff --git a/SabreTools.Serialization.Readers/InstallShieldCabinet.cs b/SabreTools.Serialization.Readers/InstallShieldCabinet.cs index 1f2a6f95..74898e6c 100644 --- a/SabreTools.Serialization.Readers/InstallShieldCabinet.cs +++ b/SabreTools.Serialization.Readers/InstallShieldCabinet.cs @@ -70,6 +70,8 @@ namespace SabreTools.Serialization.Readers #endregion + // TODO: Read the setup types table (SETUPTYPEHEADER and SETUPTYPEDESC) + #region File Descriptor Offsets // Get the file table offset @@ -163,7 +165,7 @@ namespace SabreTools.Serialization.Readers // Create and fill the file group offsets cabinet.FileGroupOffsets = []; - for (int i = 0; i < (cabinet.Descriptor.FileGroupOffsets?.Length ?? 0); i++) + for (int i = 0; i < cabinet.Descriptor.FileGroupOffsets.Length; i++) { // Get the file group offset long offset = cabinet.Descriptor.FileGroupOffsets![i]; @@ -215,17 +217,11 @@ namespace SabreTools.Serialization.Readers // Get the offset OffsetList? list = kvp.Value; if (list is null) - { - fileGroupId++; continue; - } // If we have an invalid offset if (list.DescriptorOffset <= 0) - { - fileGroupId++; continue; - } /// Seek to the file group data.SeekIfPossible(descriptorOffset + list.DescriptorOffset, SeekOrigin.Begin); @@ -234,13 +230,16 @@ namespace SabreTools.Serialization.Readers 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 ?? 0); i++) + for (int i = 0; i < cabinet.Descriptor.ComponentOffsets.Length; i++) { // Get the component offset long offset = cabinet.Descriptor.ComponentOffsets![i]; @@ -292,17 +291,11 @@ namespace SabreTools.Serialization.Readers // Get the offset OffsetList? list = kvp.Value; if (list is null) - { - componentId++; continue; - } // If we have an invalid offset if (list.DescriptorOffset <= 0) - { - componentId++; continue; - } // Seek to the component data.SeekIfPossible(descriptorOffset + list.DescriptorOffset, SeekOrigin.Begin); @@ -313,8 +306,6 @@ namespace SabreTools.Serialization.Readers #endregion - // TODO: Parse setup types - return cabinet; } catch @@ -407,7 +398,7 @@ namespace SabreTools.Serialization.Readers // Read the display name, if possible if (obj.DisplayNameOffset != 0) { - // Seek to the name + // Seek to the string data.SeekIfPossible(descriptorOffset + obj.DisplayNameOffset, SeekOrigin.Begin); // Read the string @@ -420,7 +411,7 @@ namespace SabreTools.Serialization.Readers // Read the name, if possible if (obj.NameOffset != 0) { - // Seek to the name + // Seek to the string data.SeekIfPossible(descriptorOffset + obj.NameOffset, SeekOrigin.Begin); // Read the string @@ -456,7 +447,7 @@ namespace SabreTools.Serialization.Readers // Cache the current offset long preNameOffset = data.Position; - // Seek to the name offset + // Seek to the string offset data.SeekIfPossible(descriptorOffset + nameOffset, SeekOrigin.Begin); if (majorVersion >= 17) @@ -501,6 +492,7 @@ namespace SabreTools.Serialization.Readers obj.ComponentTableOffset = data.ReadUInt32LittleEndian(); obj.Reserved5 = data.ReadUInt32LittleEndian(); obj.Reserved6 = data.ReadUInt32LittleEndian(); + obj.FileGroupOffsets = new uint[71]; for (int i = 0; i < 71; i++) { @@ -569,10 +561,23 @@ namespace SabreTools.Serialization.Readers 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(); @@ -585,7 +590,7 @@ namespace SabreTools.Serialization.Readers // Read the name, if possible if (obj.NameOffset != 0) { - // Seek to the name + // Seek to the string data.SeekIfPossible(descriptorOffset + obj.NameOffset, SeekOrigin.Begin); // Read the string @@ -613,8 +618,18 @@ namespace SabreTools.Serialization.Readers var obj = new FileGroup(); obj.NameOffset = data.ReadUInt32LittleEndian(); - obj.ExpandedSize = data.ReadUInt32LittleEndian(); - obj.CompressedSize = 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 @@ -643,7 +658,7 @@ namespace SabreTools.Serialization.Readers // Read the name, if possible if (obj.NameOffset != 0) { - // Seek to the name + // Seek to the string data.SeekIfPossible(descriptorOffset + obj.NameOffset, SeekOrigin.Begin); // Read the string @@ -653,6 +668,71 @@ namespace SabreTools.Serialization.Readers 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); @@ -677,7 +757,7 @@ namespace SabreTools.Serialization.Readers // Cache the current offset long currentOffset = data.Position; - // Seek to the name offset + // Seek to the string offset data.SeekIfPossible(descriptorOffset + obj.NameOffset, SeekOrigin.Begin); // Read the string @@ -718,22 +798,15 @@ namespace SabreTools.Serialization.Readers } else { - obj.DataOffset = data.ReadUInt32LittleEndian(); - obj.DataOffsetHigh = data.ReadUInt32LittleEndian(); + obj.DataOffset = data.ReadUInt64LittleEndian(); obj.FirstFileIndex = data.ReadUInt32LittleEndian(); obj.LastFileIndex = data.ReadUInt32LittleEndian(); - obj.FirstFileOffset = data.ReadUInt32LittleEndian(); - obj.FirstFileOffsetHigh = data.ReadUInt32LittleEndian(); - obj.FirstFileSizeExpanded = data.ReadUInt32LittleEndian(); - obj.FirstFileSizeExpandedHigh = data.ReadUInt32LittleEndian(); - obj.FirstFileSizeCompressed = data.ReadUInt32LittleEndian(); - obj.FirstFileSizeCompressedHigh = data.ReadUInt32LittleEndian(); - obj.LastFileOffset = data.ReadUInt32LittleEndian(); - obj.LastFileOffsetHigh = data.ReadUInt32LittleEndian(); - obj.LastFileSizeExpanded = data.ReadUInt32LittleEndian(); - obj.LastFileSizeExpandedHigh = data.ReadUInt32LittleEndian(); - obj.LastFileSizeCompressed = data.ReadUInt32LittleEndian(); - obj.LastFileSizeCompressedHigh = 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; diff --git a/SabreTools.Wrappers/InstallShieldCabinet.Extraction.cs b/SabreTools.Wrappers/InstallShieldCabinet.Extraction.cs index 6d7cbaf8..8132b649 100644 --- a/SabreTools.Wrappers/InstallShieldCabinet.Extraction.cs +++ b/SabreTools.Wrappers/InstallShieldCabinet.Extraction.cs @@ -241,11 +241,29 @@ namespace SabreTools.Wrappers if (!cabinet.FileIsValid(i)) continue; - // Build the full path from the cabinet + // Retrieve all output file pieces string filename = cabinet.GetFileName(i) ?? $"BAD_FILENAME{i}"; uint dirIndex = cabinet.GetDirectoryIndexFromFile(i); string dir = cabinet.GetDirectoryName((int)dirIndex) ?? $"BAD_DIRNAME{dirIndex}"; - filename = Path.Combine(dir, filename); + string group = cabinet.GetFileGroupNameFromFile(i) ?? $"BAD_GROUPNAME{dirIndex}"; + + // Consistently replace problematic path characters + foreach (char c in PathReplacementCharacters) + { + dir = dir.Replace(c, '_'); + } + + foreach (char c in PathReplacementCharacters) + { + group = group.Replace(c, '_'); + } + + // Build the full path +#if NET20 || NET35 + filename = Path.Combine(Path.Combine(group, dir), filename); +#else + filename = Path.Combine(group, dir, filename); +#endif // Ensure directory separators are consistent if (Path.DirectorySeparatorChar == '\\') @@ -253,12 +271,6 @@ namespace SabreTools.Wrappers else if (Path.DirectorySeparatorChar == '/') filename = filename.Replace('\\', '/'); - // Consistently replace problematic path characters - foreach (char c in PathReplacementCharacters) - { - filename = filename.Replace(c, '_'); - } - // Ensure the full output directory exists filename = Path.Combine(outputDirectory, filename); var directoryName = Path.GetDirectoryName(filename); diff --git a/SabreTools.Wrappers/InstallShieldCabinet.Printing.cs b/SabreTools.Wrappers/InstallShieldCabinet.Printing.cs index aa874bc5..dd9ae146 100644 --- a/SabreTools.Wrappers/InstallShieldCabinet.Printing.cs +++ b/SabreTools.Wrappers/InstallShieldCabinet.Printing.cs @@ -24,7 +24,7 @@ namespace SabreTools.Wrappers // Headers Print(builder, Model.CommonHeader, MajorVersion); - Print(builder, Model.VolumeHeader, MajorVersion); + Print(builder, Model.VolumeHeader); Print(builder, Model.Descriptor); // File Descriptors @@ -53,42 +53,19 @@ namespace SabreTools.Wrappers builder.AppendLine(); } - private static void Print(StringBuilder builder, VolumeHeader header, int majorVersion) + private static void Print(StringBuilder builder, VolumeHeader header) { builder.AppendLine(" Volume Header Information:"); builder.AppendLine(" -------------------------"); - if (majorVersion <= 5) - { - builder.AppendLine(header.DataOffset, " Data offset"); - builder.AppendLine(header.FirstFileIndex, " First file index"); - builder.AppendLine(header.LastFileIndex, " Last file index"); - builder.AppendLine(header.FirstFileOffset, " First file offset"); - builder.AppendLine(header.FirstFileSizeExpanded, " First file size expanded"); - builder.AppendLine(header.FirstFileSizeCompressed, " First file size compressed"); - builder.AppendLine(header.LastFileOffset, " Last file offset"); - builder.AppendLine(header.LastFileSizeExpanded, " Last file size expanded"); - builder.AppendLine(header.LastFileSizeCompressed, " Last file size compressed"); - } - else - { - builder.AppendLine(header.DataOffset, " Data offset"); - builder.AppendLine(header.DataOffsetHigh, " Data offset high"); - builder.AppendLine(header.FirstFileIndex, " First file index"); - builder.AppendLine(header.LastFileIndex, " Last file index"); - builder.AppendLine(header.FirstFileOffset, " First file offset"); - builder.AppendLine(header.FirstFileOffsetHigh, " First file offset high"); - builder.AppendLine(header.FirstFileSizeExpanded, " First file size expanded"); - builder.AppendLine(header.FirstFileSizeExpandedHigh, " First file size expanded high"); - builder.AppendLine(header.FirstFileSizeCompressed, " First file size compressed"); - builder.AppendLine(header.FirstFileSizeCompressedHigh, " First file size compressed high"); - builder.AppendLine(header.LastFileOffset, " Last file offset"); - builder.AppendLine(header.LastFileOffsetHigh, " Last file offset high"); - builder.AppendLine(header.LastFileSizeExpanded, " Last file size expanded"); - builder.AppendLine(header.LastFileSizeExpandedHigh, " Last file size expanded high"); - builder.AppendLine(header.LastFileSizeCompressed, " Last file size compressed"); - builder.AppendLine(header.LastFileSizeCompressedHigh, " Last file size compressed high"); - } - + builder.AppendLine(header.DataOffset, " Data offset"); + builder.AppendLine(header.FirstFileIndex, " First file index"); + builder.AppendLine(header.LastFileIndex, " Last file index"); + builder.AppendLine(header.FirstFileOffset, " First file offset"); + builder.AppendLine(header.FirstFileSizeExpanded, " First file size expanded"); + builder.AppendLine(header.FirstFileSizeCompressed, " First file size compressed"); + builder.AppendLine(header.LastFileOffset, " Last file offset"); + builder.AppendLine(header.LastFileSizeExpanded, " Last file size expanded"); + builder.AppendLine(header.LastFileSizeCompressed, " Last file size compressed"); builder.AppendLine(); }