diff --git a/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs b/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs
index f1f011cf..887a6116 100644
--- a/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs
+++ b/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs
@@ -1,7 +1,9 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
+using SabreTools.IO.Compression.zlib;
using SabreTools.Models.InstallShieldCabinet;
+using static SabreTools.Models.InstallShieldCabinet.Constants;
namespace SabreTools.Serialization.Wrappers
{
@@ -72,6 +74,15 @@ namespace SabreTools.Serialization.Wrappers
#endregion
+ #region Constants
+
+ ///
+ /// Maximum size of the window in bits
+ ///
+ private const int MAX_WBITS = 15;
+
+ #endregion
+
#region Constructors
///
@@ -157,6 +168,73 @@ namespace SabreTools.Serialization.Wrappers
return new Regex(@"\d+$").Replace(pattern, string.Empty);
}
+ ///
+ /// Open a cabinet set for reading, if possible
+ ///
+ /// Filename pattern for matching cabinet files
+ ///
+ public static InstallShieldCabinet? OpenSet(string? pattern)
+ {
+ // An invalid pattern means no cabinet files
+ if (string.IsNullOrEmpty(pattern))
+ return null;
+
+ // Create a placeholder wrapper for output
+ InstallShieldCabinet? set = null;
+
+ // Loop until there are no parts left
+ bool iterate = true;
+ InstallShieldCabinet? previous = null;
+ for (int i = 1; iterate; i++)
+ {
+ var file = OpenFileForReading(pattern, i, HEADER_SUFFIX);
+ if (file != null)
+ iterate = false;
+ else
+ file = OpenFileForReading(pattern, i, CABINET_SUFFIX);
+
+ if (file == null)
+ break;
+
+ var header = Create(file);
+ if (header == null)
+ break;
+
+ if (previous != null)
+ previous.Next = header;
+ else
+ previous = set = header;
+ }
+
+ return set;
+ }
+
+ ///
+ /// Open a cabinet file for reading
+ ///
+ /// Filename pattern for matching cabinet files
+ /// Cabinet part index to be opened
+ /// Cabinet files suffix (e.g. `.cab`)
+ /// A Stream representing the cabinet part, null on error
+ private static Stream? OpenFileForReading(string? pattern, int index, string suffix)
+ {
+ // An invalid pattern means no cabinet files
+ if (string.IsNullOrEmpty(pattern))
+ return null;
+
+ // Attempt lower-case extension
+ string filename = $"{pattern}{index}.{suffix}";
+ if (File.Exists(filename))
+ return File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+
+ // Attempt upper-case extension
+ filename = $"{pattern}{index}.{suffix.ToUpperInvariant()}";
+ if (File.Exists(filename))
+ return File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+
+ return null;
+ }
+
#endregion
#region Component
@@ -271,6 +349,33 @@ namespace SabreTools.Serialization.Wrappers
return Model.FileDescriptors[index];
}
+ ///
+ /// Get the file descriptor at a given index, if possible
+ ///
+ /// Verifies the file descriptor flags before returning
+ public FileDescriptor? GetFileDescriptorWithVerification(int index, out string? error)
+ {
+ var fileDescriptor = GetFileDescriptor(index);
+ if (fileDescriptor == null)
+ {
+ error = $"Failed to get file descriptor for file {index}";
+ return null;
+ }
+
+#if NET20 || NET35
+ if ((fileDescriptor.Flags & FileFlags.FILE_INVALID) != 0 || fileDescriptor.DataOffset == 0)
+#else
+ if (fileDescriptor.Flags.HasFlag(FileFlags.FILE_INVALID) || fileDescriptor.DataOffset == 0)
+#endif
+ {
+ error = $"File at {index} is marked as invalid";
+ return null;
+ }
+
+ error = null;
+ return fileDescriptor;
+ }
+
///
/// Get the file name at a given index, if possible
///
@@ -287,6 +392,24 @@ namespace SabreTools.Serialization.Wrappers
return descriptor.Name;
}
+ ///
+ /// Get the packed size of a file, if possible
+ ///
+ public static ulong GetReadableBytes(FileDescriptor? descriptor)
+ {
+ if (descriptor == null)
+ return 0;
+
+#if NET20 || NET35
+ if ((descriptor.Flags & FileFlags.FILE_COMPRESSED) != 0)
+#else
+ if (descriptor.Flags.HasFlag(FileFlags.FILE_COMPRESSED))
+#endif
+ return descriptor.CompressedSize;
+ else
+ return descriptor.ExpandedSize;
+ }
+
#endregion
#region File Group
@@ -355,5 +478,83 @@ namespace SabreTools.Serialization.Wrappers
=> GetFileGroupFromFile(index)?.Name;
#endregion
+
+ #region Extraction
+
+ ///
+ /// Uncompress a source byte array to a destination
+ ///
+ public unsafe static int Uncompress(byte[] dest, ref ulong destLen, byte[] source, ref ulong sourceLen)
+ {
+ fixed (byte* sourcePtr = source)
+ fixed (byte* destPtr = dest)
+ {
+ var stream = new ZLib.z_stream_s
+ {
+ next_in = sourcePtr,
+ avail_in = (uint)sourceLen,
+ next_out = destPtr,
+ avail_out = (uint)destLen,
+ };
+
+ // make second parameter negative to disable checksum verification
+ int err = ZLib.inflateInit2_(stream, -MAX_WBITS, ZLib.zlibVersion(), source.Length);
+ if (err != zlibConst.Z_OK)
+ return err;
+
+ err = ZLib.inflate(stream, 1);
+ if (err != zlibConst.Z_STREAM_END)
+ {
+ ZLib.inflateEnd(stream);
+ return err;
+ }
+
+ destLen = stream.total_out;
+ sourceLen = stream.total_in;
+ return ZLib.inflateEnd(stream);
+ }
+ }
+
+ ///
+ /// 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)
+ {
+ fixed (byte* sourcePtr = source)
+ fixed (byte* destPtr = dest)
+ {
+ var stream = new ZLib.z_stream_s
+ {
+ next_in = sourcePtr,
+ avail_in = (uint)sourceLen,
+ next_out = destPtr,
+ avail_out = (uint)destLen,
+ };
+
+ destLen = 0;
+ sourceLen = 0;
+
+ // make second parameter negative to disable checksum verification
+ int err = ZLib.inflateInit2_(stream, -MAX_WBITS, ZLib.zlibVersion(), source.Length);
+ if (err != zlibConst.Z_OK)
+ return err;
+
+ while (stream.avail_in > 1)
+ {
+ err = ZLib.inflate(stream, 1);
+ if (err != zlibConst.Z_OK)
+ {
+ ZLib.inflateEnd(stream);
+ return err;
+ }
+ }
+
+ destLen = stream.total_out;
+ sourceLen = stream.total_in;
+ return ZLib.inflateEnd(stream);
+ }
+ }
+
+ #endregion
}
}