diff --git a/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/CompoundFileBinary/CompoundFileComImport.cs b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/CompoundFileBinary/CompoundFileComImport.cs
new file mode 100644
index 0000000..a4eda5f
--- /dev/null
+++ b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/CompoundFileBinary/CompoundFileComImport.cs
@@ -0,0 +1,531 @@
+// Copyright © 2017-2025 QL-Win Contributors
+//
+// This file is part of QuickLook program.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
+using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
+using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG;
+
+namespace QuickLook.Plugin.ArchiveViewer.CompoundFileBinary;
+
+///
+/// A disposable wrapper for a COM instance.
+/// Provides convenience methods for reading, writing and querying stream metadata
+/// while ensuring the underlying COM object is released when disposed.
+///
+public class DisposableIStream : IDisposable
+{
+ ///
+ /// The underlying COM stream object.
+ ///
+ public IStream Stream { get; private set; }
+
+ ///
+ /// Wrap an existing .
+ ///
+ /// The COM IStream to wrap.
+ public DisposableIStream(IStream stream)
+ {
+ Stream = stream;
+ }
+
+ ///
+ /// Open a named stream from the given storage and wrap it.
+ ///
+ /// Parent storage containing the stream.
+ /// Stream name within the storage.
+ /// Access mode flags (STGM).
+ public DisposableIStream(IStorage storage, string name, STGM mode)
+ {
+ storage.OpenStream(name, IntPtr.Zero, mode, 0, out IStream stream);
+ Stream = stream;
+ }
+
+ ///
+ /// Read up to bytes from the stream into .
+ /// Returns the number of bytes actually read.
+ ///
+ /// Destination buffer.
+ /// Maximum number of bytes to read.
+ /// Number of bytes read.
+ public int Read(byte[] buffer, int length)
+ {
+ // Use unmanaged memory to receive the number of bytes read from IStream.Read.
+ nint pcbRead = Marshal.AllocHGlobal(sizeof(int));
+ Stream.Read(buffer, length, pcbRead);
+ int bytesRead = Marshal.ReadInt32(pcbRead);
+ Marshal.FreeHGlobal(pcbRead);
+ return bytesRead;
+ }
+
+ ///
+ /// Query the STATSTG information for this stream.
+ ///
+ /// Flags controlling returned data (see ).
+ /// A describing the stream.
+ public STATSTG Stat(int statFlag)
+ {
+ Stream.Stat(out STATSTG statstg, statFlag);
+ return statstg;
+ }
+
+ ///
+ /// Write bytes from into the stream.
+ ///
+ /// Source buffer containing data to write.
+ /// Number of bytes from buffer to write.
+ public void Write(byte[] buffer, int length)
+ {
+ nint pcbWritten = Marshal.AllocHGlobal(sizeof(int));
+ Stream.Write(buffer, length, pcbWritten);
+ Marshal.FreeHGlobal(pcbWritten);
+ }
+
+ ///
+ /// Releases the wrapped COM instance.
+ ///
+ public void Dispose()
+ {
+ if (Stream != null)
+ {
+ _ = Marshal.ReleaseComObject(Stream);
+ Stream = null!;
+ }
+ }
+}
+
+///
+/// A disposable wrapper for a COM instance.
+/// Provides helper methods to open nested storages/streams and enumerate children.
+///
+public class DisposableIStorage : IDisposable
+{
+ ///
+ /// Create a new structured storage file at using the provided .
+ /// This wraps StgCreateStorageEx and throws an exception on failure.
+ ///
+ /// Filesystem path for the new storage.
+ /// STGM flags controlling creation mode.
+ /// A new wrapping the created storage.
+ public static DisposableIStorage CreateStorage(string filePath, STGM mode)
+ {
+ // GUID for property set storage (V4); passed to StgCreateStorageEx.
+ Guid propertySetStorageId = new("0000013A-0000-0000-C000-000000000046");
+
+ STGOPTIONS options;
+ options.usVersion = 1;
+ options.reserved = 0;
+ options.ulSectorSize = 4096;
+
+ int hr = Ole32.StgCreateStorageEx(filePath, mode, STGFMT.STGFMT_DOCFILE, 0, ref options, IntPtr.Zero, ref propertySetStorageId, out IStorage storage);
+ if (hr != HRESULT.S_OK)
+ {
+ Exception ex = Marshal.GetExceptionForHR(hr);
+ throw new Exception("Error while creating file: " + (ex?.Message));
+ }
+ return new DisposableIStorage(storage);
+ }
+
+ ///
+ /// The underlying COM IStorage instance.
+ ///
+ public IStorage Storage { get; private set; }
+
+ private DisposableIStorage(IStorage storage)
+ {
+ Storage = storage;
+ }
+
+ ///
+ /// Open an existing structured storage file and wrap it.
+ /// This calls StgOpenStorage and throws an exception on failure.
+ ///
+ /// Path to the storage file to open.
+ /// STGM flags controlling open mode.
+ /// Reserved parameter passed to native API for exclude names mask.
+ public DisposableIStorage(string filePath, STGM mode, nint excludeNames)
+ {
+ int hr = Ole32.StgOpenStorage(filePath, null, mode, excludeNames, 0, out IStorage storage);
+ if (hr != HRESULT.S_OK)
+ {
+ Exception ex = Marshal.GetExceptionForHR(hr);
+ throw new Exception("Error while opening file: " + (ex?.Message));
+ }
+ Storage = storage;
+ }
+
+ ///
+ /// Open a nested storage (child directory-like storage) and return a new wrapper.
+ ///
+ /// Name of the nested storage.
+ /// Optional priority storage parameter for native call.
+ /// STGM access flags.
+ /// Reserved exclude names mask.
+ /// A wrapping the opened nested storage.
+ public DisposableIStorage OpenStorage(string name, IStorage priorityStorage, STGM mode, nint excludeNames)
+ {
+ Storage.OpenStorage(name, priorityStorage, mode, excludeNames, 0, out IStorage subStorage);
+ return new DisposableIStorage(subStorage);
+ }
+
+ ///
+ /// Open a named stream from this storage and return a disposable wrapper for it.
+ ///
+ /// Name of the stream.
+ /// Reserved pointer passed to native call.
+ /// STGM access flags.
+ /// A wrapping the opened stream.
+ public DisposableIStream OpenStream(string name, nint reserved1, STGM mode)
+ {
+ Storage.OpenStream(name, reserved1, mode, 0, out IStream stream);
+ return new DisposableIStream(stream);
+ }
+
+ ///
+ /// Create a new stream within this storage and return a wrapper for writing.
+ ///
+ /// Name for the new stream.
+ /// STGM flags controlling creation mode.
+ /// A for the created stream.
+ public DisposableIStream CreateStream(string name, STGM mode)
+ {
+ Storage.CreateStream(name, mode, 0, 0, out IStream stream);
+ return new DisposableIStream(stream);
+ }
+
+ ///
+ /// Enumerate child elements (streams and storages) of this storage.
+ /// Each yielded describes a single child element.
+ ///
+ ///
+ /// The caller should not assume ownership of the returned structures; they are copies of the native data.
+ ///
+ /// An enumerator yielding entries.
+ public IEnumerator EnumElements()
+ {
+ Storage.EnumElements(0, IntPtr.Zero, 0, out IEnumSTATSTG enumStatStg);
+ STATSTG[] statStg = new STATSTG[1];
+ while (enumStatStg.Next(1, statStg, out uint fetched) == 0 && fetched > 0)
+ {
+ yield return statStg[0];
+ }
+ _ = Marshal.ReleaseComObject(enumStatStg);
+ }
+
+ ///
+ /// Releases the wrapped COM instance.
+ ///
+ public void Dispose()
+ {
+ if (Storage != null)
+ {
+ _ = Marshal.ReleaseComObject(Storage);
+ Storage = null!;
+ }
+ }
+}
+
+///
+/// Enumerates the STATSTG structures returned by .
+///
+[ComImport]
+[Guid("0000000d-0000-0000-C000-000000000046")]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+public interface IEnumSTATSTG
+{
+ ///
+ /// Retrieves a specified number of items in the enumeration sequence.
+ ///
+ /// The number of items to be retrieved.
+ /// An array of STATSTG items.
+ /// The number of items actually retrieved.
+ /// S_OK if the number of items supplied is celt; otherwise, S_FALSE.
+ [PreserveSig]
+ public uint Next(uint requestedCount, [MarshalAs(UnmanagedType.LPArray), Out] STATSTG[] elements, out uint fetchedCount);
+
+ ///
+ /// Skips a specified number of items in the enumeration sequence.
+ ///
+ /// The number of items to be skipped.
+ public void Skip(uint count);
+
+ ///
+ /// Resets the enumeration sequence to the beginning.
+ ///
+ public void Reset();
+
+ ///
+ /// Creates a new enumerator that contains the same enumeration state as the current one.
+ ///
+ /// A clone of the current enumerator.
+ [return: MarshalAs(UnmanagedType.Interface)]
+ public IEnumSTATSTG Clone();
+}
+
+///
+/// Provides methods for creating and managing the root storage object, child storage objects, and stream objects.
+/// Represents the COM IStorage interface.
+///
+[ComImport]
+[Guid("0000000b-0000-0000-C000-000000000046")]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+public interface IStorage
+{
+ ///
+ /// Creates a new stream object in this storage object with the specified name and access mode.
+ ///
+ /// Name of the stream to create.
+ /// STGM flags that specify access and creation options.
+ /// Reserved; must be zero.
+ /// Reserved; must be zero.
+ /// Receives the created instance.
+ public void CreateStream(string name, STGM mode, uint reserved1, uint reserved2, out IStream stream);
+
+ ///
+ /// Opens the specified stream object in this storage object and returns a pointer to the interface.
+ ///
+ /// Name of the stream to open.
+ /// Reserved pointer passed to native call (typically IntPtr.Zero).
+ /// STGM flags that specify access mode.
+ /// Reserved; must be zero.
+ /// Receives the opened instance.
+ public void OpenStream(string name, nint reserved1, STGM mode, uint reserved2, out IStream stream);
+
+ ///
+ /// Creates a new storage object (nested storage) with the specified name and access mode.
+ ///
+ /// Name of the nested storage to create.
+ /// STGM flags that specify access and creation options.
+ /// Reserved; must be zero.
+ /// Reserved; must be zero.
+ /// Receives the created instance.
+ public void CreateStorage(string name, STGM mode, uint reserved1, uint reserved2, out IStorage storage);
+
+ ///
+ /// Opens an existing nested storage object by name and returns a pointer to the interface.
+ ///
+ /// Name of the nested storage to open.
+ /// Optional priority storage used by the native API.
+ /// STGM flags that specify access mode.
+ /// Reserved exclude names mask.
+ /// Reserved; must be zero.
+ /// Receives the opened instance.
+ public void OpenStorage(string name, IStorage priorityStorage, STGM mode, nint excludeNames, uint reserved, out IStorage storage);
+
+ ///
+ /// Copies the specified elements and interfaces from this storage object to another storage object.
+ ///
+ /// Number of interface IDs in the excluded list.
+ /// GUID identifying interfaces to exclude (native signature uses a pointer/array).
+ /// Reserved exclude names mask.
+ /// Destination storage that receives the copied elements.
+ public void CopyTo(uint excludedInterfaceCount, Guid excludedInterfaceIds, nint excludeNames, IStorage destStorage);
+
+ ///
+ /// Moves or renames an element from this storage to a destination storage.
+ ///
+ /// The current name of the element to move.
+ /// Destination storage to receive the element.
+ /// New name for the element in the destination storage.
+ /// Flags that control the operation.
+ public void MoveElementTo(string name, IStorage destStorage, string newName, uint flags);
+
+ ///
+ /// Commits changes made to this storage object to the underlying storage medium.
+ ///
+ /// Flags that control commit behavior.
+ public void Commit(uint commitFlags);
+
+ ///
+ /// Discards changes that have been made to this storage object since the last commit.
+ ///
+ public void Revert();
+
+ ///
+ /// Enumerates the elements contained in this storage object.
+ ///
+ /// Reserved value passed to the native API.
+ /// Reserved pointer passed to the native API.
+ /// Reserved value passed to the native API.
+ /// Receives an enumerator for the elements.
+ public void EnumElements(uint reserved1, nint reserved2, uint reserved3, out IEnumSTATSTG enumStat);
+
+ ///
+ /// Destroys the specified element (stream or storage) within this storage object.
+ ///
+ /// Name of the element to destroy.
+ public void DestroyElement(string name);
+
+ ///
+ /// Renames an existing element within this storage object.
+ ///
+ /// Current name of the element.
+ /// New name for the element.
+ public void RenameElement(string oldName, string newName);
+
+ ///
+ /// Sets the creation, access and modification times for the specified element.
+ ///
+ /// Name of the element whose timestamps will be updated.
+ /// Creation time to set.
+ /// Last access time to set.
+ /// Last modification time to set.
+ public void SetElementTimes(string name, FILETIME creationTime, FILETIME accessTime, FILETIME modificationTime);
+
+ ///
+ /// Sets the class identifier (CLSID) for this storage object.
+ ///
+ /// CLSID to associate with the storage.
+ public void SetClass(Guid clsid);
+
+ ///
+ /// Sets state bits for this storage object.
+ ///
+ /// State bits to set.
+ /// Mask specifying which bits to change.
+ public void SetStateBits(uint stateBits, uint mask);
+
+ ///
+ /// Retrieves the STATSTG structure that contains statistical information about this storage object or an element.
+ ///
+ /// Receives the STATSTG structure.
+ /// Specifies whether the name is returned and/or other options (see ).
+ public void Stat(out STATSTG statStg, uint statFlag);
+}
+
+///
+/// The STGM constants are flags that indicate conditions for creating and deleting the object and access modes for the object.
+/// These are passed to storage and stream creation/opening methods.
+///
+[Flags]
+public enum STGM : int
+{
+ DIRECT = 0x00000000,
+ TRANSACTED = 0x00010000,
+ SIMPLE = 0x08000000,
+ READ = 0x00000000,
+ WRITE = 0x00000001,
+ READWRITE = 0x00000002,
+ SHARE_DENY_NONE = 0x00000040,
+ SHARE_DENY_READ = 0x00000030,
+ SHARE_DENY_WRITE = 0x00000020,
+ SHARE_EXCLUSIVE = 0x00000010,
+ PRIORITY = 0x00040000,
+ DELETEONRELEASE = 0x04000000,
+ NOSCRATCH = 0x00100000,
+ CREATE = 0x00001000,
+ CONVERT = 0x00020000,
+ FAILIFTHERE = 0x00000000,
+ NOSNAPSHOT = 0x00200000,
+ DIRECT_SWMR = 0x00400000,
+}
+
+///
+/// Specifies whether the STATSTG structure contains the name of the storage object.
+///
+public enum STATFLAG : uint
+{
+ STATFLAG_DEFAULT = 0,
+ STATFLAG_NONAME = 1,
+ STATFLAG_NOOPEN = 2,
+}
+
+///
+/// The STGTY enumeration values specify the type of a storage object.
+/// STGTY_STORAGE represents a nested storage (similar to a directory).
+/// STGTY_STREAM represents a stream (similar to a file) inside a storage.
+///
+public enum STGTY : int
+{
+ ///
+ /// A nested storage object (treat as a directory when extracting).
+ ///
+ STGTY_STORAGE = 1,
+
+ ///
+ /// A stream object (treat as a file when extracting).
+ ///
+ STGTY_STREAM = 2,
+
+ STGTY_LOCKBYTES = 3,
+ STGTY_PROPERTY = 4,
+}
+
+///
+/// The STGFMT enumeration values specify the format of a storage object.
+///
+public enum STGFMT : int
+{
+ STGFMT_STORAGE = 0,
+ STGFMT_FILE = 3,
+ STGFMT_ANY = 4,
+ STGFMT_DOCFILE = 5,
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct STGOPTIONS
+{
+ public ushort usVersion;
+ public ushort reserved;
+ public uint ulSectorSize;
+}
+
+file static class Ole32
+{
+ ///
+ /// Determines whether the given path is a structured storage file.
+ ///
+ [DllImport("ole32.dll")]
+ public static extern int StgIsStorageFile([MarshalAs(UnmanagedType.LPWStr)] string filePath);
+
+ ///
+ /// Opens an existing compound file and returns an IStorage interface.
+ /// This is the managed signature for the native StgOpenStorage function.
+ ///
+ [DllImport("ole32.dll")]
+ public static extern int StgOpenStorage(
+ [MarshalAs(UnmanagedType.LPWStr)] string filePath,
+ IStorage priorityStorage,
+ STGM mode,
+ nint excludeNames,
+ uint reserved,
+ out IStorage openStorage);
+
+ ///
+ /// Creates a new structured storage file. Managed signature for StgCreateStorageEx.
+ ///
+ [DllImport("ole32.dll")]
+ public static extern int StgCreateStorageEx(
+ [MarshalAs(UnmanagedType.LPWStr)] string filePath,
+ STGM mode,
+ STGFMT format,
+ uint attrs,
+ ref STGOPTIONS options,
+ nint securityDescriptor,
+ ref Guid riid,
+ out IStorage openObject);
+}
+
+file static class HRESULT
+{
+ ///
+ /// Success HRESULT code.
+ ///
+ public const int S_OK = 0;
+}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/CompoundFileBinary/CompoundFileExtractor.cs b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/CompoundFileBinary/CompoundFileExtractor.cs
new file mode 100644
index 0000000..23837f6
--- /dev/null
+++ b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/CompoundFileBinary/CompoundFileExtractor.cs
@@ -0,0 +1,127 @@
+// Copyright © 2017-2025 QL-Win Contributors
+//
+// This file is part of QuickLook program.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices.ComTypes;
+
+namespace QuickLook.Plugin.ArchiveViewer.CompoundFileBinary;
+
+///
+/// Utility class to extract streams and storages from a COM compound file (IStorage) into the file system.
+/// This is a thin managed wrapper that enumerates entries inside the compound file and writes streams to disk.
+///
+public static class CompoundFileExtractor
+{
+ ///
+ /// Extracts all streams and storages from the compound file at
+ /// into the specified . Directory structure inside the compound
+ /// file is preserved.
+ ///
+ /// Path to the compound file (OLE compound file / structured storage).
+ /// Destination directory to write extracted files and directories to. If it does not exist it will be created.
+ public static void ExtractToDirectory(string compoundFilePath, string destinationDirectory)
+ {
+ if (!Directory.Exists(destinationDirectory))
+ {
+ Directory.CreateDirectory(destinationDirectory);
+ }
+
+ // Open the compound file as an IStorage implementation wrapped by DisposableIStorage.
+ using DisposableIStorage storage = new(compoundFilePath, STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE, IntPtr.Zero);
+ IEnumerator enumerator = storage.EnumElements();
+
+ // Enumerate all elements (streams and storages) at the root of the compound file.
+ while (enumerator.MoveNext())
+ {
+ STATSTG entryStat = enumerator.Current;
+
+ // STGTY_STREAM indicates the element is a stream (treat as a file).
+ if (entryStat.type == (int)STGTY.STGTY_STREAM)
+ {
+ ExtractStreamToDirectory(storage, entryStat.pwcsName, destinationDirectory);
+ }
+ // STGTY_STORAGE indicates the element is a nested storage (treat as a directory).
+ else if (entryStat.type == (int)STGTY.STGTY_STORAGE)
+ {
+ ExtractStorageToDirectory(storage, entryStat.pwcsName, destinationDirectory);
+ }
+ }
+ }
+
+ ///
+ /// Extracts a single stream from the provided and writes it to .
+ ///
+ /// The parent storage that contains the stream.
+ /// Name of the stream inside the compound file.
+ /// Directory to write the extracted stream to.
+ private static void ExtractStreamToDirectory(DisposableIStorage storage, string entryName, string destinationDirectory)
+ {
+ // Build target file path for the stream extraction.
+ string outputPath = Path.Combine(destinationDirectory, entryName);
+
+ // Open the stream for reading from the compound file.
+ using DisposableIStream stream = storage.OpenStream(entryName, IntPtr.Zero, STGM.READ | STGM.SHARE_EXCLUSIVE);
+
+ // Query stream statistics to determine its size.
+ STATSTG streamStat = stream.Stat((int)STATFLAG.STATFLAG_DEFAULT);
+
+ // Allocate a buffer exactly the size of the stream and read it in one call.
+ // Note: cbSize is an unsigned 64-bit value in the native struct; the wrapper exposes it as an int/long depending on implementation.
+ byte[] buffer = new byte[streamStat.cbSize];
+ stream.Read(buffer, buffer.Length);
+
+ // Write the stream contents to disk. This will overwrite existing files.
+ File.WriteAllBytes(outputPath, buffer);
+ }
+
+ ///
+ /// Extracts a nested storage (directory) recursively. Creates a corresponding directory on disk and
+ /// extracts its child streams and storages.
+ ///
+ /// The parent storage that contains the nested storage.
+ /// Name of the nested storage inside the compound file.
+ /// Directory on disk that will contain the created directory for this nested storage.
+ private static void ExtractStorageToDirectory(DisposableIStorage storage, string entryName, string parentDirectory)
+ {
+ string currentDirectory = Path.Combine(parentDirectory, entryName);
+ if (!Directory.Exists(currentDirectory))
+ {
+ Directory.CreateDirectory(currentDirectory);
+ }
+
+ // Open the nested storage and enumerate its elements recursively.
+ using DisposableIStorage subStorage = storage.OpenStorage(entryName, null, STGM.READ | STGM.SHARE_EXCLUSIVE, IntPtr.Zero);
+ IEnumerator enumerator = subStorage.EnumElements();
+ while (enumerator.MoveNext())
+ {
+ STATSTG entryStat = enumerator.Current;
+
+ // STGTY_STREAM indicates the element is a stream (treat as a file).
+ if (entryStat.type == (int)STGTY.STGTY_STREAM)
+ {
+ ExtractStreamToDirectory(subStorage, entryStat.pwcsName, currentDirectory);
+ }
+ // STGTY_STORAGE indicates the element is a nested storage (treat as a directory).
+ else if (entryStat.type == (int)STGTY.STGTY_STORAGE)
+ {
+ ExtractStorageToDirectory(subStorage, entryStat.pwcsName, currentDirectory);
+ }
+ }
+ }
+}