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
///