diff --git a/SabreTools.Serialization/UnshieldSharp/Extractor.cs b/SabreTools.Serialization/UnshieldSharp/Extractor.cs deleted file mode 100644 index bed9ef7f..00000000 --- a/SabreTools.Serialization/UnshieldSharp/Extractor.cs +++ /dev/null @@ -1,232 +0,0 @@ -using System; -using System.IO; -using SabreTools.Hashing; -using SabreTools.IO.Compression.zlib; -using SabreTools.Models.InstallShieldCabinet; -using Header = SabreTools.Serialization.Wrappers.InstallShieldCabinet; - -namespace UnshieldSharpInternal -{ - // TODO: Figure out if individual parts of a split cab can be extracted separately - internal class Extractor - { - /// - /// Linked CAB headers - /// - public Header HeaderList { get; } - - /// - /// Default buffer size - /// - private const int BUFFER_SIZE = 64 * 1024; - - #region Constructors - - public Extractor(Header headerList) - { - HeaderList = headerList; - } - - #endregion - - #region File - - /// - /// Save the file at the given index to the filename specified - /// - public bool FileSave(int index, string filename, bool useOld = false) - { - // Get the file descriptor - if (!HeaderList.TryGetFileDescriptor(index, out var fileDescriptor) || fileDescriptor == null) - return false; - - // If the file is split - if (fileDescriptor.LinkFlags == LinkFlags.LINK_PREV) - return FileSave((int)fileDescriptor.LinkPrevious, filename, useOld); - - // Get the reader at the index - var reader = Reader.Create(this, index, fileDescriptor); - if (reader == null) - return false; - - // Create the output file and hasher - FileStream output = File.OpenWrite(filename); - var md5 = new HashWrapper(HashType.MD5); - - ulong bytesLeft = Header.GetReadableBytes(fileDescriptor); - byte[] inputBuffer; - byte[] outputBuffer = new byte[BUFFER_SIZE]; - ulong totalWritten = 0; - - // Read while there are bytes remaining - while (bytesLeft > 0) - { - ulong bytesToWrite = BUFFER_SIZE; - int result; - - // Handle compressed files -#if NET20 || NET35 - if ((fileDescriptor.Flags & FileFlags.FILE_COMPRESSED) != 0) -#else - if (fileDescriptor.Flags.HasFlag(FileFlags.FILE_COMPRESSED)) -#endif - { - // Attempt to read the length value - byte[] lengthArr = new byte[sizeof(ushort)]; - if (!reader.Read(lengthArr, 0, lengthArr.Length)) - { - Console.Error.WriteLine($"Failed to read {lengthArr.Length} bytes of file {index} ({HeaderList.GetFileName(index)}) from input cabinet file {fileDescriptor.Volume}"); - reader.Dispose(); - output?.Close(); - return false; - } - - // Validate the number of bytes to read - ushort bytesToRead = BitConverter.ToUInt16(lengthArr, 0); - if (bytesToRead == 0) - { - Console.Error.WriteLine("bytesToRead can't be zero"); - reader.Dispose(); - output?.Close(); - return false; - } - - // Attempt to read the specified number of bytes - inputBuffer = new byte[BUFFER_SIZE + 1]; - if (!reader.Read(inputBuffer, 0, bytesToRead)) - { - Console.Error.WriteLine($"Failed to read {lengthArr.Length} bytes of file {index} ({HeaderList.GetFileName(index)}) from input cabinet file {fileDescriptor.Volume}"); - reader.Dispose(); - output?.Close(); - return false; - } - - // Add a null byte to make inflate happy - inputBuffer[bytesToRead] = 0; - ulong readBytes = (ulong)(bytesToRead + 1); - - // Uncompress into a buffer - if (useOld) - result = Header.UncompressOld(outputBuffer, ref bytesToWrite, inputBuffer, ref readBytes); - else - result = Header.Uncompress(outputBuffer, ref bytesToWrite, inputBuffer, ref readBytes); - - // If we didn't get a positive result that's not a data error (false positives) - if (result != zlibConst.Z_OK && result != zlibConst.Z_DATA_ERROR) - { - Console.Error.WriteLine($"Decompression failed with code {result.ToZlibConstName()}. bytes_to_read={bytesToRead}, volume={fileDescriptor.Volume}, read_bytes={readBytes}"); - reader.Dispose(); - output?.Close(); - return false; - } - - // Set remaining bytes - bytesLeft -= 2; - bytesLeft -= bytesToRead; - } - - // Handle uncompressed files - else - { - bytesToWrite = Math.Min(bytesLeft, BUFFER_SIZE); - if (!reader.Read(outputBuffer, 0, (int)bytesToWrite)) - { - Console.Error.WriteLine($"Failed to write {bytesToWrite} bytes from input cabinet file {fileDescriptor.Volume}"); - reader.Dispose(); - output?.Close(); - return false; - } - - // Set remaining bytes - bytesLeft -= (uint)bytesToWrite; - } - - // Hash and write the next block - md5.Process(outputBuffer, 0, (int)bytesToWrite); - output?.Write(outputBuffer, 0, (int)bytesToWrite); - totalWritten += bytesToWrite; - } - - // Validate the number of bytes written - if (fileDescriptor.ExpandedSize != totalWritten) - { - Console.Error.WriteLine($"Expanded size expected to be {fileDescriptor.ExpandedSize}, but was {totalWritten}"); - reader.Dispose(); - output?.Close(); - return false; - } - - // Finalize output values - md5.Terminate(); - reader?.Dispose(); - output?.Close(); - - // Failing the file has been disabled because for a subset of CABs the values don't seem to match - // TODO: Investigate what is causing this to fail and what data needs to be hashed - - // // Validate the data written, if required - // if (HeaderList!.MajorVersion >= 6) - // { - // string? md5result = md5.CurrentHashString; - // if (md5result == null || md5result != BitConverter.ToString(fileDescriptor.MD5!)) - // { - // Console.Error.WriteLine($"MD5 checksum failure for file {index} ({HeaderList.GetFileName(index)})"); - // return false; - // } - // } - - return true; - } - - /// - /// Save the file at the given index to the filename specified as raw - /// - public bool FileSaveRaw(int index, string filename) - { - // Get the file descriptor - if (!HeaderList.TryGetFileDescriptor(index, out var fileDescriptor) || fileDescriptor == null) - return false; - - // If the file is split - if (fileDescriptor.LinkFlags == LinkFlags.LINK_PREV) - return FileSaveRaw((int)fileDescriptor.LinkPrevious, filename); - - // Get the reader at the index - var reader = Reader.Create(this, index, fileDescriptor); - if (reader == null) - return false; - - // Create the output file - FileStream output = File.OpenWrite(filename); - - ulong bytesLeft = Header.GetReadableBytes(fileDescriptor); - byte[] outputBuffer = new byte[BUFFER_SIZE]; - - // Read while there are bytes remaining - while (bytesLeft > 0) - { - ulong bytesToWrite = Math.Min(bytesLeft, BUFFER_SIZE); - if (!reader.Read(outputBuffer, 0, (int)bytesToWrite)) - { - Console.Error.WriteLine($"Failed to read {bytesToWrite} bytes from input cabinet file {fileDescriptor.Volume}"); - reader.Dispose(); - output?.Close(); - return false; - } - - // Set remaining bytes - bytesLeft -= (uint)bytesToWrite; - - // Write the next block - output.Write(outputBuffer, 0, (int)bytesToWrite); - } - - // Finalize output values - reader.Dispose(); - output?.Close(); - return true; - } - - #endregion - } -} diff --git a/SabreTools.Serialization/UnshieldSharp/Reader.cs b/SabreTools.Serialization/UnshieldSharp/Reader.cs index fd002c96..4f1144af 100644 --- a/SabreTools.Serialization/UnshieldSharp/Reader.cs +++ b/SabreTools.Serialization/UnshieldSharp/Reader.cs @@ -1,6 +1,7 @@ using System; using System.IO; using SabreTools.Models.InstallShieldCabinet; +using Header = SabreTools.Serialization.Wrappers.InstallShieldCabinet; namespace UnshieldSharpInternal { @@ -11,7 +12,7 @@ namespace UnshieldSharpInternal /// /// Cabinet file to read from /// - private readonly Extractor _extractor; + private readonly Header _cabinet; /// /// Currently selected index @@ -57,9 +58,9 @@ namespace UnshieldSharpInternal #region Constructors - private Reader(Extractor cabinet, uint index, FileDescriptor fileDescriptor) + private Reader(Header cabinet, uint index, FileDescriptor fileDescriptor) { - _extractor = cabinet; + _cabinet = cabinet; _index = index; _fileDescriptor = fileDescriptor; } @@ -69,7 +70,7 @@ namespace UnshieldSharpInternal /// /// Create a new from an existing cabinet, index, and file descriptor /// - public static Reader? Create(Extractor cabinet, int index, FileDescriptor fileDescriptor) + public static Reader? Create(Header cabinet, int index, FileDescriptor fileDescriptor) { var reader = new Reader(cabinet, (uint)index, fileDescriptor); for (; ; ) @@ -87,7 +88,7 @@ namespace UnshieldSharpInternal } // Start with the correct volume for IS5 cabinets - if (reader._extractor.HeaderList.MajorVersion <= 5 && index > (int)reader._volumeHeader.LastFileIndex) + if (reader._cabinet.MajorVersion <= 5 && index > (int)reader._volumeHeader.LastFileIndex) { // Normalize the volume ID for odd cases if (fileDescriptor.Volume == ushort.MinValue || fileDescriptor.Volume == ushort.MaxValue) @@ -147,7 +148,7 @@ namespace UnshieldSharpInternal #else if (_fileDescriptor.Flags.HasFlag(FileFlags.FILE_OBFUSCATED)) #endif - SabreTools.Serialization.Wrappers.InstallShieldCabinet.Deobfuscate(buffer, size, ref _obfuscationOffset); + Header.Deobfuscate(buffer, size, ref _obfuscationOffset); return true; } @@ -167,7 +168,7 @@ namespace UnshieldSharpInternal private bool OpenVolume(ushort volume) { // Read the volume from the cabinet set - var next = _extractor.HeaderList.OpenVolume(volume, out var volumeStream); + var next = _cabinet.OpenVolume(volume, out var volumeStream); if (next?.VolumeHeader == null || volumeStream == null) { Console.Error.WriteLine($"Failed to open input cabinet file {volume}"); @@ -180,9 +181,9 @@ namespace UnshieldSharpInternal _volumeHeader = next.VolumeHeader; // Enable support for split archives for IS5 - if (_extractor.HeaderList.MajorVersion == 5) + if (_cabinet.MajorVersion == 5) { - if (_index < (_extractor.HeaderList.FileCount - 1) + if (_index < (_cabinet.FileCount - 1) && _index == _volumeHeader.LastFileIndex && _volumeHeader.LastFileSizeCompressed != _fileDescriptor.CompressedSize) { diff --git a/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs b/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs index 1b31dc99..2fff04a3 100644 --- a/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs +++ b/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs @@ -1,9 +1,11 @@ using System; using System.IO; using System.Text.RegularExpressions; +using SabreTools.Hashing; using SabreTools.IO.Compression.zlib; using SabreTools.Models.InstallShieldCabinet; using SabreTools.Serialization.Interfaces; +using UnshieldSharpInternal; using static SabreTools.Models.InstallShieldCabinet.Constants; namespace SabreTools.Serialization.Wrappers @@ -404,7 +406,6 @@ namespace SabreTools.Serialization.Wrappers try { - var cabfile = new UnshieldSharpInternal.Extractor(cabinet); for (int i = 0; i < cabinet.FileCount; i++) { try @@ -426,7 +427,7 @@ namespace SabreTools.Serialization.Wrappers if (directoryName != null && !Directory.Exists(directoryName)) Directory.CreateDirectory(directoryName); - cabfile.FileSave(i, filename); + cabinet.FileSave(i, filename); } catch (Exception ex) { @@ -443,10 +444,206 @@ namespace SabreTools.Serialization.Wrappers } } + /// + /// Save the file at the given index to the filename specified + /// + public bool FileSave(int index, string filename, bool useOld = false) + { + // Get the file descriptor + if (!TryGetFileDescriptor(index, out var fileDescriptor) || fileDescriptor == null) + return false; + + // If the file is split + if (fileDescriptor.LinkFlags == LinkFlags.LINK_PREV) + return FileSave((int)fileDescriptor.LinkPrevious, filename, useOld); + + // Get the reader at the index + var reader = Reader.Create(this, index, fileDescriptor); + if (reader == null) + return false; + + // Create the output file and hasher + FileStream output = File.OpenWrite(filename); + var md5 = new HashWrapper(HashType.MD5); + + ulong bytesLeft = GetReadableBytes(fileDescriptor); + byte[] inputBuffer; + byte[] outputBuffer = new byte[BUFFER_SIZE]; + ulong totalWritten = 0; + + // Read while there are bytes remaining + while (bytesLeft > 0) + { + ulong bytesToWrite = BUFFER_SIZE; + int result; + + // Handle compressed files +#if NET20 || NET35 + if ((fileDescriptor.Flags & FileFlags.FILE_COMPRESSED) != 0) +#else + if (fileDescriptor.Flags.HasFlag(FileFlags.FILE_COMPRESSED)) +#endif + { + // Attempt to read the length value + byte[] lengthArr = new byte[sizeof(ushort)]; + if (!reader.Read(lengthArr, 0, lengthArr.Length)) + { + Console.Error.WriteLine($"Failed to read {lengthArr.Length} bytes of file {index} ({GetFileName(index)}) from input cabinet file {fileDescriptor.Volume}"); + reader.Dispose(); + output?.Close(); + return false; + } + + // Validate the number of bytes to read + ushort bytesToRead = BitConverter.ToUInt16(lengthArr, 0); + if (bytesToRead == 0) + { + Console.Error.WriteLine("bytesToRead can't be zero"); + reader.Dispose(); + output?.Close(); + return false; + } + + // Attempt to read the specified number of bytes + inputBuffer = new byte[BUFFER_SIZE + 1]; + if (!reader.Read(inputBuffer, 0, bytesToRead)) + { + Console.Error.WriteLine($"Failed to read {lengthArr.Length} bytes of file {index} ({GetFileName(index)}) from input cabinet file {fileDescriptor.Volume}"); + reader.Dispose(); + output?.Close(); + return false; + } + + // Add a null byte to make inflate happy + inputBuffer[bytesToRead] = 0; + ulong readBytes = (ulong)(bytesToRead + 1); + + // Uncompress into a buffer + if (useOld) + result = UncompressOld(outputBuffer, ref bytesToWrite, inputBuffer, ref readBytes); + else + result = Uncompress(outputBuffer, ref bytesToWrite, inputBuffer, ref readBytes); + + // If we didn't get a positive result that's not a data error (false positives) + if (result != zlibConst.Z_OK && result != zlibConst.Z_DATA_ERROR) + { + Console.Error.WriteLine($"Decompression failed with code {result.ToZlibConstName()}. bytes_to_read={bytesToRead}, volume={fileDescriptor.Volume}, read_bytes={readBytes}"); + reader.Dispose(); + output?.Close(); + return false; + } + + // Set remaining bytes + bytesLeft -= 2; + bytesLeft -= bytesToRead; + } + + // Handle uncompressed files + else + { + bytesToWrite = Math.Min(bytesLeft, BUFFER_SIZE); + if (!reader.Read(outputBuffer, 0, (int)bytesToWrite)) + { + Console.Error.WriteLine($"Failed to write {bytesToWrite} bytes from input cabinet file {fileDescriptor.Volume}"); + reader.Dispose(); + output?.Close(); + return false; + } + + // Set remaining bytes + bytesLeft -= (uint)bytesToWrite; + } + + // Hash and write the next block + md5.Process(outputBuffer, 0, (int)bytesToWrite); + output?.Write(outputBuffer, 0, (int)bytesToWrite); + totalWritten += bytesToWrite; + } + + // Validate the number of bytes written + if (fileDescriptor.ExpandedSize != totalWritten) + { + Console.Error.WriteLine($"Expanded size expected to be {fileDescriptor.ExpandedSize}, but was {totalWritten}"); + reader.Dispose(); + output?.Close(); + return false; + } + + // Finalize output values + md5.Terminate(); + reader?.Dispose(); + output?.Close(); + + // Failing the file has been disabled because for a subset of CABs the values don't seem to match + // TODO: Investigate what is causing this to fail and what data needs to be hashed + + // // Validate the data written, if required + // if (HeaderList!.MajorVersion >= 6) + // { + // string? md5result = md5.CurrentHashString; + // if (md5result == null || md5result != BitConverter.ToString(fileDescriptor.MD5!)) + // { + // Console.Error.WriteLine($"MD5 checksum failure for file {index} ({HeaderList.GetFileName(index)})"); + // return false; + // } + // } + + return true; + } + + /// + /// Save the file at the given index to the filename specified as raw + /// + public bool FileSaveRaw(int index, string filename) + { + // Get the file descriptor + if (!TryGetFileDescriptor(index, out var fileDescriptor) || fileDescriptor == null) + return false; + + // If the file is split + if (fileDescriptor.LinkFlags == LinkFlags.LINK_PREV) + return FileSaveRaw((int)fileDescriptor.LinkPrevious, filename); + + // Get the reader at the index + var reader = Reader.Create(this, index, fileDescriptor); + if (reader == null) + return false; + + // Create the output file + FileStream output = File.OpenWrite(filename); + + ulong bytesLeft = GetReadableBytes(fileDescriptor); + byte[] outputBuffer = new byte[BUFFER_SIZE]; + + // Read while there are bytes remaining + while (bytesLeft > 0) + { + ulong bytesToWrite = Math.Min(bytesLeft, BUFFER_SIZE); + if (!reader.Read(outputBuffer, 0, (int)bytesToWrite)) + { + Console.Error.WriteLine($"Failed to read {bytesToWrite} bytes from input cabinet file {fileDescriptor.Volume}"); + reader.Dispose(); + output?.Close(); + return false; + } + + // Set remaining bytes + bytesLeft -= (uint)bytesToWrite; + + // Write the next block + output.Write(outputBuffer, 0, (int)bytesToWrite); + } + + // Finalize output values + reader.Dispose(); + output?.Close(); + return true; + } + /// /// Uncompress a source byte array to a destination /// - public unsafe static int Uncompress(byte[] dest, ref ulong destLen, byte[] source, ref ulong sourceLen) + private unsafe static int Uncompress(byte[] dest, ref ulong destLen, byte[] source, ref ulong sourceLen) { fixed (byte* sourcePtr = source) fixed (byte* destPtr = dest) @@ -480,7 +677,7 @@ namespace SabreTools.Serialization.Wrappers /// /// Uncompress a source byte array to a destination (old version) /// - public unsafe static int UncompressOld(byte[] dest, ref ulong destLen, byte[] source, ref ulong sourceLen) + private unsafe static int UncompressOld(byte[] dest, ref ulong destLen, byte[] source, ref ulong sourceLen) { fixed (byte* sourcePtr = source) fixed (byte* destPtr = dest)