From 7b4711f5cfc8e142f3a3251f313f7f42fb353303 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 28 Jul 2025 12:27:18 -0400 Subject: [PATCH] Implement OpenSet for MS-CAB --- .../Wrappers/InstallShieldCabinet.cs | 2 +- .../Wrappers/MicrosoftCabinet.cs | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs b/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs index 887a6116..cca46949 100644 --- a/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs +++ b/SabreTools.Serialization/Wrappers/InstallShieldCabinet.cs @@ -172,7 +172,7 @@ namespace SabreTools.Serialization.Wrappers /// Open a cabinet set for reading, if possible /// /// Filename pattern for matching cabinet files - /// + /// Wrapper representing the set, null on error public static InstallShieldCabinet? OpenSet(string? pattern) { // An invalid pattern means no cabinet files diff --git a/SabreTools.Serialization/Wrappers/MicrosoftCabinet.cs b/SabreTools.Serialization/Wrappers/MicrosoftCabinet.cs index 3e51502f..ec278d27 100644 --- a/SabreTools.Serialization/Wrappers/MicrosoftCabinet.cs +++ b/SabreTools.Serialization/Wrappers/MicrosoftCabinet.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using SabreTools.Models.ArchiveDotOrg; namespace SabreTools.Serialization.Wrappers { @@ -17,9 +18,24 @@ namespace SabreTools.Serialization.Wrappers /// public Models.MicrosoftCabinet.CFFILE[]? Files => Model.Files; + /// + public int FileCount => Model.Header?.FileCount ?? 0; + /// public Models.MicrosoftCabinet.CFFOLDER[]? Folders => Model.Folders; + /// + public int FolderCount => Model.Header?.FolderCount ?? 0; + + /// + public Models.MicrosoftCabinet.CFHEADER? Header => Model.Header; + + /// + /// Reference to the next cabinet header + /// + /// Only used in multi-file + public MicrosoftCabinet? Next { get; set; } + #endregion #region Constructors @@ -86,6 +102,88 @@ namespace SabreTools.Serialization.Wrappers #endregion + #region Cabinet Set + + /// + /// Open a cabinet set for reading, if possible + /// + /// Filename for one cabinet in the set + /// Wrapper representing the set, null on error + public static MicrosoftCabinet? OpenSet(string? filename) + { + // If the file is invalid + if (string.IsNullOrEmpty(filename)) + return null; + else if (!File.Exists(filename!)) + return null; + + // Get the full file path and directory + filename = Path.GetFullPath(filename); + string? directory = Path.GetDirectoryName(filename); + + // Read in the current file and try to parse + var stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + var current = Create(stream); + if (current?.Header == null) + return null; + + // Seek to the first part of the cabinet set + while (current.Header.CabinetPrev != null) + { + // Get the path defined in the current header + string prevPath = current.Header.CabinetPrev; + if (directory != null) + prevPath = Path.Combine(directory, prevPath); + + // If the file doesn't exist + if (!File.Exists(prevPath)) + break; + + // Close the previous cabinet part to avoid locking issues + stream.Close(); + + // Open the previous cabinet and try to parse + stream = File.Open(prevPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + var prev = Create(stream); + if (prev?.Header == null) + break; + + // Assign previous as new current + current = prev; + } + + // Cache the current start of the cabinet set + var start = current; + + // Read in the cabinet parts sequentially + while (current.Header.CabinetNext != null) + { + // Get the path defined in the current header + string nextPath = current.Header.CabinetNext; + if (directory != null) + nextPath = Path.Combine(directory, nextPath); + + // If the file doesn't exist + if (!File.Exists(nextPath)) + break; + + // Open the next cabinet and try to parse + stream = File.Open(nextPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + var next = Create(stream); + if (next?.Header == null) + break; + + // Add next to the current and reset current + current.Next = next; + current = next; + } + + // Return the start of the set + return start; + } + + #endregion + #region Checksumming ///