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