Remove Extractor, finally

This commit is contained in:
Matt Nadareski
2025-08-29 10:20:51 -04:00
parent 015a971147
commit 091ee56368
3 changed files with 211 additions and 245 deletions

View File

@@ -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
{
/// <summary>
/// Linked CAB headers
/// </summary>
public Header HeaderList { get; }
/// <summary>
/// Default buffer size
/// </summary>
private const int BUFFER_SIZE = 64 * 1024;
#region Constructors
public Extractor(Header headerList)
{
HeaderList = headerList;
}
#endregion
#region File
/// <summary>
/// Save the file at the given index to the filename specified
/// </summary>
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;
}
/// <summary>
/// Save the file at the given index to the filename specified as raw
/// </summary>
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
}
}

View File

@@ -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
/// <summary>
/// Cabinet file to read from
/// </summary>
private readonly Extractor _extractor;
private readonly Header _cabinet;
/// <summary>
/// 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
/// <summary>
/// Create a new <see cref="Reader"> from an existing cabinet, index, and file descriptor
/// </summary>
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)
{

View File

@@ -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
}
}
/// <summary>
/// Save the file at the given index to the filename specified
/// </summary>
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;
}
/// <summary>
/// Save the file at the given index to the filename specified as raw
/// </summary>
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;
}
/// <summary>
/// Uncompress a source byte array to a destination
/// </summary>
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
/// <summary>
/// Uncompress a source byte array to a destination (old version)
/// </summary>
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)