From 27f3571951b5d9b8fafc9450940a790ba9bff8ee Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Wed, 2 Sep 2020 16:14:56 -0600 Subject: [PATCH] 7zip extraction improvements --- .../FileSignatures/Definitions/bsasigs.txt | 4 +- Wabbajack.Common/FileSignatures/Signatures.cs | 8 + Wabbajack.Common/FileSignatures/Signatures.tt | 10 +- Wabbajack.Server/Services/DiscordWebHook.cs | 2 +- Wabbajack.VirtualFileSystem/Context.cs | 2 +- Wabbajack.VirtualFileSystem/FileExtractor.cs | 61 +-- .../SevenZipExtractor/ArchiveFile.cs | 257 ++++++++++ .../SevenZipExtractor/ArchiveFileCallback.cs | 57 +++ .../ArchiveStreamCallback.cs | 45 ++ .../ArchiveStreamsCallback.cs | 58 +++ .../SevenZipExtractor/Entry.cs | 119 +++++ .../SevenZipExtractor/Formats.cs | 122 +++++ .../IArchiveExtractCallback.cs | 23 + .../SevenZipExtractor/Kernel32Dll.cs | 20 + .../SevenZipExtractor/SafeLibraryHandle.cs | 21 + .../SevenZipExtractor/SevenZipException.cs | 24 + .../SevenZipExtractor/SevenZipFormat.cs | 285 +++++++++++ .../SevenZipExtractor/SevenZipHandle.cs | 68 +++ .../SevenZipExtractor/SevenZipInterface.cs | 459 ++++++++++++++++++ 19 files changed, 1588 insertions(+), 57 deletions(-) create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveFile.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveFileCallback.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveStreamCallback.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveStreamsCallback.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/Entry.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/Formats.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/IArchiveExtractCallback.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/Kernel32Dll.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/SafeLibraryHandle.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipException.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipFormat.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipHandle.cs create mode 100644 Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipInterface.cs diff --git a/Wabbajack.Common/FileSignatures/Definitions/bsasigs.txt b/Wabbajack.Common/FileSignatures/Definitions/bsasigs.txt index 44262d68..ecf143d8 100644 --- a/Wabbajack.Common/FileSignatures/Definitions/bsasigs.txt +++ b/Wabbajack.Common/FileSignatures/Definitions/bsasigs.txt @@ -1,4 +1,6 @@ Morrowind BSA,00 01 00 00,TES3, null, null, 0, null TES 4-5 and FO 3 BSA,42 53 41 00,BSA FO4 BSA,42 54 44 58,BA2 -Relaxed RAR format,52 61 72 21,RAR \ No newline at end of file +Relaxed RAR format,52 61 72 21,RAR +RAR5 or newer, 52 61 72 21 1A 07 01 00,RAR_NEW +RAR4 or older, 52 61 72 21 1A 07 00,RAR_OLD \ No newline at end of file diff --git a/Wabbajack.Common/FileSignatures/Signatures.cs b/Wabbajack.Common/FileSignatures/Signatures.cs index b21247b9..cd9049b8 100644 --- a/Wabbajack.Common/FileSignatures/Signatures.cs +++ b/Wabbajack.Common/FileSignatures/Signatures.cs @@ -307,6 +307,8 @@ namespace Wabbajack.Common.FileSignatures { RA, RAM, RAR, + RAR_NEW, + RAR_OLD, RBI, RDATA, REG, @@ -443,6 +445,12 @@ namespace Wabbajack.Common.FileSignatures { // Relaxed RAR format (FileType.RAR, new byte[] {0x52, 0x61, 0x72, 0x21}), + // RAR5 or newer + (FileType.RAR_NEW, new byte[] {0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00}), + + // RAR4 or older + (FileType.RAR_OLD, new byte[] {0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00}), + // JPEG2000 image files (FileType.JP2, new byte[] {0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20}), diff --git a/Wabbajack.Common/FileSignatures/Signatures.tt b/Wabbajack.Common/FileSignatures/Signatures.tt index e273a80e..57720e82 100644 --- a/Wabbajack.Common/FileSignatures/Signatures.tt +++ b/Wabbajack.Common/FileSignatures/Signatures.tt @@ -7,10 +7,12 @@ <# - byte[] StringToByteArray(string hex) { - return Enumerable.Range(0, hex.Length) - .Where(x => x % 2 == 0) - .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + byte[] StringToByteArray(string hex) + { + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); } var files = new string[] {"bsasigs.txt", "file_sigs_RAW.txt"}; diff --git a/Wabbajack.Server/Services/DiscordWebHook.cs b/Wabbajack.Server/Services/DiscordWebHook.cs index 1c7b794b..8571b544 100644 --- a/Wabbajack.Server/Services/DiscordWebHook.cs +++ b/Wabbajack.Server/Services/DiscordWebHook.cs @@ -54,7 +54,7 @@ namespace Wabbajack.Server.Services } catch (Exception ex) { - _logger.LogError(ex, ex.ToJson()); + _logger.LogError(ex, ex.ToString()); } } diff --git a/Wabbajack.VirtualFileSystem/Context.cs b/Wabbajack.VirtualFileSystem/Context.cs index b3d63d02..eb1a29dc 100644 --- a/Wabbajack.VirtualFileSystem/Context.cs +++ b/Wabbajack.VirtualFileSystem/Context.cs @@ -212,7 +212,7 @@ namespace Wabbajack.VirtualFileSystem foreach (var group in grouped) { var only = group.Select(f => f.RelativeName); - var extracted = await group.Key.StagedFile.ExtractAll(Queue, only); + var extracted = await group.Key.StagedFile.ExtractAll(Queue, only, true); paths.Add(extracted); foreach (var file in group) file.StagedFile = extracted[file.RelativeName]; diff --git a/Wabbajack.VirtualFileSystem/FileExtractor.cs b/Wabbajack.VirtualFileSystem/FileExtractor.cs index da5ab02a..c78d95c4 100644 --- a/Wabbajack.VirtualFileSystem/FileExtractor.cs +++ b/Wabbajack.VirtualFileSystem/FileExtractor.cs @@ -11,6 +11,7 @@ using Wabbajack.Common.StatusFeed; using Wabbajack.Common.StatusFeed.Errors; using Wabbajack.Common; using Wabbajack.Common.FileSignatures; +using Wabbajack.VirtualFileSystem.SevenZipExtractor; using Utils = Wabbajack.Common.Utils; @@ -29,6 +30,8 @@ namespace Wabbajack.VirtualFileSystem public static async Task ExtractAll(WorkQueue queue, AbsolutePath source, IEnumerable OnlyFiles = null, bool throwOnError = true) { + OnlyFiles ??= new RelativePath[0]; + try { var sig = await archiveSigs.MatchesAsync(source); @@ -56,6 +59,7 @@ namespace Wabbajack.VirtualFileSystem if (!throwOnError) return new ExtractedFiles(await TempFolder.Create()); + Utils.Log(ex.ToString()); Utils.ErrorThrow(ex, $"Error while extracting {source}"); throw new Exception(); } @@ -150,66 +154,23 @@ namespace Wabbajack.VirtualFileSystem private static async Task ExtractAllWith7Zip(AbsolutePath source, IEnumerable onlyFiles) { - TempFile tmpFile = null; var dest = await TempFolder.Create(); Utils.Log(new GenericInfo($"Extracting {(string)source.FileName}", $"The contents of {(string)source.FileName} are being extracted to {(string)source.FileName} using 7zip.exe")); - var process = new ProcessHelper - { - Path = @"Extractors\7z.exe".RelativeTo(AbsolutePath.EntryPoint), - - }; + var files = onlyFiles.ToHashSet(); - if (onlyFiles != null) + using var archive = await ArchiveFile.Open(source); + if (files.Count > 0) { - //It's stupid that we have to do this, but 7zip's file pattern matching isn't very fuzzy - IEnumerable AllVariants(string input) + await archive.Extract(path => { - yield return $"\"{input}\""; - yield return $"\"\\{input}\""; - } - - tmpFile = new TempFile(); - await tmpFile.Path.WriteAllLinesAsync(onlyFiles.SelectMany(f => AllVariants((string)f)).ToArray()); - process.Arguments = new object[] - { - "x", "-bsp1", "-y", $"-o\"{dest.Dir}\"", source, $"@\"{tmpFile.Path}\"", "-mmt=off" - }; - } - else - { - process.Arguments = new object[] {"x", "-bsp1", "-y", $"-o\"{dest.Dir}\"", source, "-mmt=off"}; - } - - - var result = process.Output.Where(d => d.Type == ProcessHelper.StreamType.Output) - .ForEachAsync(p => - { - var (_, line) = p; - if (line == null) - return; - - if (line.Length <= 4 || line[3] != '%') return; - - int.TryParse(line.Substring(0, 3), out var percentInt); - Utils.Status($"Extracting {(string)source.FileName} - {line.Trim()}", Percent.FactoryPutInRange(percentInt / 100d)); + Utils.Log($"Extract file {path} {files.Contains(path)} {dest.Dir.Combine(path)}"); + return files.Contains(path) ? dest.Dir.Combine(path) : default; }); - - var exitCode = await process.Start(); - - - if (exitCode != 0) - { - Utils.ErrorThrow(new _7zipReturnError(exitCode, source, dest.Dir, "")); } else { - Utils.Status($"Extracting {source.FileName} - done", Percent.One, alsoLog: true); - } - - if (tmpFile != null) - { - await tmpFile.DisposeAsync(); + await archive.Extract(path => dest.Dir.Combine(path)); } return new ExtractedFiles(dest); diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveFile.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveFile.cs new file mode 100644 index 00000000..7de4369c --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveFile.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Wabbajack.Common; +using Wabbajack.Common.FileSignatures; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + public class ArchiveFile : IDisposable + { + private SevenZipHandle _sevenZipHandle; + private IInArchive _archive; + private InStreamWrapper _archiveStream; + private IList _entries; + + private static readonly AbsolutePath LibraryFilePath = @"Extractors\7z.dll".RelativeTo(AbsolutePath.EntryPoint); + private static SignatureChecker _checker = new SignatureChecker(Formats.FileTypeGuidMapping.Keys.ToArray()); + + public static async Task Open(AbsolutePath archiveFilePath) + { + var self = new ArchiveFile(); + + self.InitializeAndValidateLibrary(); + + if (!archiveFilePath.IsFile) + { + throw new SevenZipException("Archive file not found"); + } + + var format = await _checker.MatchesAsync(archiveFilePath); + + if (format == null) + { + throw new SevenZipException($"Unknown format for {archiveFilePath}"); + } + + self._archive = self._sevenZipHandle.CreateInArchive(Formats.FileTypeGuidMapping[format.Value]); + self._archiveStream = new InStreamWrapper(await archiveFilePath.OpenRead()); + return self; + } + + public static async Task Open(Stream archiveStream, SevenZipFormat? format = null, string libraryFilePath = null) + { + var self = new ArchiveFile(); + self.InitializeAndValidateLibrary(); + self._archive = self._sevenZipHandle.CreateInArchive(Formats.FormatGuidMapping[SevenZipFormat.SevenZip]); + self._archiveStream = new InStreamWrapper(archiveStream); + return self; + } + + public async Task Extract(AbsolutePath outputFolder, bool overwrite = false) + { + await this.Extract(entry => + { + var fileName = outputFolder.Combine(entry.FileName); + + if (!fileName.Exists || overwrite) + { + return fileName; + } + + return default; + }); + } + + public async Task Extract(Func getOutputPath) + { + IList fileStreams = new List(); + + try + { + foreach (Entry entry in Entries) + { + + AbsolutePath outputPath = entry.IsFolder ? default : getOutputPath((RelativePath)entry.FileName); + + if (outputPath == default) // getOutputPath = null means SKIP + { + fileStreams.Add(null); + continue; + } + + if (entry.IsFolder) + { + outputPath.CreateDirectory(); + fileStreams.Add(null); + continue; + } + + var directoryName = outputPath.Parent; + directoryName.CreateDirectory(); + + fileStreams.Add(await outputPath.Create()); + } + + this._archive.Extract(null, 0xFFFFFFFF, 0, new ArchiveStreamsCallback(fileStreams)); + } + finally + { + foreach (Stream stream in fileStreams) + { + if (stream == null) continue; + var tsk = stream?.DisposeAsync(); + await tsk.Value; + } + } + } + + public IList Entries + { + get + { + if (this._entries != null) + { + return this._entries; + } + + ulong checkPos = 32 * 1024; + int open = this._archive.Open(this._archiveStream, ref checkPos, null); + + if (open != 0) + { + throw new SevenZipException("Unable to open archive"); + } + + uint itemsCount = this._archive.GetNumberOfItems(); + + this._entries = new List(); + + for (uint fileIndex = 0; fileIndex < itemsCount; fileIndex++) + { + string fileName = this.GetProperty(fileIndex, ItemPropId.kpidPath); + bool isFolder = this.GetProperty(fileIndex, ItemPropId.kpidIsFolder); + bool isEncrypted = this.GetProperty(fileIndex, ItemPropId.kpidEncrypted); + ulong size = this.GetProperty(fileIndex, ItemPropId.kpidSize); + ulong packedSize = this.GetProperty(fileIndex, ItemPropId.kpidPackedSize); + DateTime creationTime = this.GetPropertySafe(fileIndex, ItemPropId.kpidCreationTime); + DateTime lastWriteTime = this.GetPropertySafe(fileIndex, ItemPropId.kpidLastWriteTime); + DateTime lastAccessTime = this.GetPropertySafe(fileIndex, ItemPropId.kpidLastAccessTime); + uint crc = this.GetPropertySafe(fileIndex, ItemPropId.kpidCRC); + uint attributes = this.GetPropertySafe(fileIndex, ItemPropId.kpidAttributes); + string comment = this.GetPropertySafe(fileIndex, ItemPropId.kpidComment); + string hostOS = this.GetPropertySafe(fileIndex, ItemPropId.kpidHostOS); + string method = this.GetPropertySafe(fileIndex, ItemPropId.kpidMethod); + + bool isSplitBefore = this.GetPropertySafe(fileIndex, ItemPropId.kpidSplitBefore); + bool isSplitAfter = this.GetPropertySafe(fileIndex, ItemPropId.kpidSplitAfter); + + this._entries.Add(new Entry(this._archive, fileIndex) + { + FileName = fileName, + IsFolder = isFolder, + IsEncrypted = isEncrypted, + Size = size, + PackedSize = packedSize, + CreationTime = creationTime, + LastWriteTime = lastWriteTime, + LastAccessTime = lastAccessTime, + CRC = crc, + Attributes = attributes, + Comment = comment, + HostOS = hostOS, + Method = method, + IsSplitBefore = isSplitBefore, + IsSplitAfter = isSplitAfter + }); + } + + return this._entries; + } + } + + private T GetPropertySafe(uint fileIndex, ItemPropId name) + { + try + { + return this.GetProperty(fileIndex, name); + } + catch (InvalidCastException) + { + return default(T); + } + } + + private T GetProperty(uint fileIndex, ItemPropId name) + { + PropVariant propVariant = new PropVariant(); + this._archive.GetProperty(fileIndex, name, ref propVariant); + object value = propVariant.GetObject(); + + if (propVariant.VarType == VarEnum.VT_EMPTY) + { + propVariant.Clear(); + return default(T); + } + + propVariant.Clear(); + + if (value == null) + { + return default(T); + } + + Type type = typeof(T); + bool isNullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + Type underlyingType = isNullable ? Nullable.GetUnderlyingType(type) : type; + + T result = (T)Convert.ChangeType(value.ToString(), underlyingType); + + return result; + } + + private void InitializeAndValidateLibrary() + { + try + { + this._sevenZipHandle = new SevenZipHandle((string)LibraryFilePath); + } + catch (Exception e) + { + throw new SevenZipException("Unable to initialize SevenZipHandle", e); + } + } + + ~ArchiveFile() + { + this.Dispose(false); + } + + protected void Dispose(bool disposing) + { + if (this._archiveStream != null) + { + this._archiveStream.Dispose(); + } + + if (this._archive != null) + { + Marshal.ReleaseComObject(this._archive); + } + + if (this._sevenZipHandle != null) + { + this._sevenZipHandle.Dispose(); + } + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveFileCallback.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveFileCallback.cs new file mode 100644 index 00000000..18ae341e --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveFileCallback.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + internal class ArchiveFileCallback : IArchiveExtractCallback + { + private readonly string fileName; + private readonly uint fileNumber; + private OutStreamWrapper fileStream; // to be removed + + public ArchiveFileCallback(uint fileNumber, string fileName) + { + this.fileNumber = fileNumber; + this.fileName = fileName; + } + + public void SetTotal(ulong total) + { + } + + public void SetCompleted(ref ulong completeValue) + { + } + + public int GetStream(uint index, out ISequentialOutStream outStream, AskMode askExtractMode) + { + if ((index != this.fileNumber) || (askExtractMode != AskMode.kExtract)) + { + outStream = null; + return 0; + } + + string fileDir = Path.GetDirectoryName(this.fileName); + + if (!string.IsNullOrEmpty(fileDir)) + { + Directory.CreateDirectory(fileDir); + } + + this.fileStream = new OutStreamWrapper(File.Create(this.fileName)); + + outStream = this.fileStream; + + return 0; + } + + public void PrepareOperation(AskMode askExtractMode) + { + } + + public void SetOperationResult(OperationResult resultEOperationResult) + { + this.fileStream.Dispose(); + } + } +} diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveStreamCallback.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveStreamCallback.cs new file mode 100644 index 00000000..ecba71e3 --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveStreamCallback.cs @@ -0,0 +1,45 @@ +using System.IO; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + internal class ArchiveStreamCallback : IArchiveExtractCallback + { + private readonly uint fileNumber; + private readonly Stream stream; + + public ArchiveStreamCallback(uint fileNumber, Stream stream) + { + this.fileNumber = fileNumber; + this.stream = stream; + } + + public void SetTotal(ulong total) + { + } + + public void SetCompleted(ref ulong completeValue) + { + } + + public int GetStream(uint index, out ISequentialOutStream outStream, AskMode askExtractMode) + { + if ((index != this.fileNumber) || (askExtractMode != AskMode.kExtract)) + { + outStream = null; + return 0; + } + + outStream = new OutStreamWrapper(this.stream); + + return 0; + } + + public void PrepareOperation(AskMode askExtractMode) + { + } + + public void SetOperationResult(OperationResult resultEOperationResult) + { + } + } +} diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveStreamsCallback.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveStreamsCallback.cs new file mode 100644 index 00000000..1cf9feed --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/ArchiveStreamsCallback.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.IO; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + internal class ArchiveStreamsCallback : IArchiveExtractCallback + { + private readonly IList streams; + + public ArchiveStreamsCallback(IList streams) + { + this.streams = streams; + } + + public void SetTotal(ulong total) + { + } + + public void SetCompleted(ref ulong completeValue) + { + } + + public int GetStream(uint index, out ISequentialOutStream outStream, AskMode askExtractMode) + { + if (askExtractMode != AskMode.kExtract) + { + outStream = null; + return 0; + } + + if (this.streams == null) + { + outStream = null; + return 0; + } + + Stream stream = this.streams[(int) index]; + + if (stream == null) + { + outStream = null; + return 0; + } + + outStream = new OutStreamWrapper(stream); + + return 0; + } + + public void PrepareOperation(AskMode askExtractMode) + { + } + + public void SetOperationResult(OperationResult resultEOperationResult) + { + } + } +} diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/Entry.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/Entry.cs new file mode 100644 index 00000000..ff6b8e20 --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/Entry.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + public class Entry + { + private readonly IInArchive archive; + private readonly uint index; + + internal Entry(IInArchive archive, uint index) + { + this.archive = archive; + this.index = index; + } + + /// + /// Name of the file with its relative path within the archive + /// + public string FileName { get; internal set; } + /// + /// True if entry is a folder, false if it is a file + /// + public bool IsFolder { get; internal set; } + /// + /// Original entry size + /// + public ulong Size { get; internal set; } + /// + /// Entry size in a archived state + /// + public ulong PackedSize { get; internal set; } + + /// + /// Date and time of the file (entry) creation + /// + public DateTime CreationTime { get; internal set; } + + /// + /// Date and time of the last change of the file (entry) + /// + public DateTime LastWriteTime { get; internal set; } + + /// + /// Date and time of the last access of the file (entry) + /// + public DateTime LastAccessTime { get; internal set; } + + /// + /// CRC hash of the entry + /// + public UInt32 CRC { get; internal set; } + + /// + /// Attributes of the entry + /// + public UInt32 Attributes { get; internal set; } + + /// + /// True if entry is encrypted, otherwise false + /// + public bool IsEncrypted { get; internal set; } + + /// + /// Comment of the entry + /// + public string Comment { get; internal set; } + + /// + /// Compression method of the entry + /// + public string Method { get; internal set; } + + /// + /// Host operating system of the entry + /// + public string HostOS { get; internal set; } + + /// + /// True if there are parts of this file in previous split archive parts + /// + public bool IsSplitBefore { get; set; } + + /// + /// True if there are parts of this file in next split archive parts + /// + public bool IsSplitAfter { get; set; } + + public void Extract(string fileName, bool preserveTimestamp = true) + { + if (this.IsFolder) + { + Directory.CreateDirectory(fileName); + return; + } + + string directoryName = Path.GetDirectoryName(fileName); + + if (!string.IsNullOrWhiteSpace(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + + using (FileStream fileStream = File.Create(fileName)) + { + this.Extract(fileStream); + } + + if (preserveTimestamp) + { + File.SetLastWriteTime(fileName, this.LastWriteTime); + } + } + public void Extract(Stream stream) + { + this.archive.Extract(new[] { this.index }, 1, 0, new ArchiveStreamCallback(this.index, stream)); + } + } +} diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/Formats.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/Formats.cs new file mode 100644 index 00000000..a44d180a --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/Formats.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using Wabbajack.Common.FileSignatures; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + public class Formats + { + internal static readonly Dictionary ExtensionFormatMapping = new Dictionary + { + {"7z", SevenZipFormat.SevenZip}, + {"gz", SevenZipFormat.GZip}, + {"tar", SevenZipFormat.Tar}, + {"rar", SevenZipFormat.Rar}, + {"zip", SevenZipFormat.Zip}, + {"lzma", SevenZipFormat.Lzma}, + {"lzh", SevenZipFormat.Lzh}, + {"arj", SevenZipFormat.Arj}, + {"bz2", SevenZipFormat.BZip2}, + {"cab", SevenZipFormat.Cab}, + {"chm", SevenZipFormat.Chm}, + {"deb", SevenZipFormat.Deb}, + {"iso", SevenZipFormat.Iso}, + {"rpm", SevenZipFormat.Rpm}, + {"wim", SevenZipFormat.Wim}, + {"udf", SevenZipFormat.Udf}, + {"mub", SevenZipFormat.Mub}, + {"xar", SevenZipFormat.Xar}, + {"hfs", SevenZipFormat.Hfs}, + {"dmg", SevenZipFormat.Dmg}, + {"z", SevenZipFormat.Lzw}, + {"xz", SevenZipFormat.XZ}, + {"flv", SevenZipFormat.Flv}, + {"swf", SevenZipFormat.Swf}, + {"exe", SevenZipFormat.PE}, + {"dll", SevenZipFormat.PE}, + {"vhd", SevenZipFormat.Vhd} + }; + + internal static Dictionary FormatGuidMapping = new Dictionary + { + {SevenZipFormat.SevenZip, new Guid("23170f69-40c1-278a-1000-000110070000")}, + {SevenZipFormat.Arj, new Guid("23170f69-40c1-278a-1000-000110040000")}, + {SevenZipFormat.BZip2, new Guid("23170f69-40c1-278a-1000-000110020000")}, + {SevenZipFormat.Cab, new Guid("23170f69-40c1-278a-1000-000110080000")}, + {SevenZipFormat.Chm, new Guid("23170f69-40c1-278a-1000-000110e90000")}, + {SevenZipFormat.Compound, new Guid("23170f69-40c1-278a-1000-000110e50000")}, + {SevenZipFormat.Cpio, new Guid("23170f69-40c1-278a-1000-000110ed0000")}, + {SevenZipFormat.Deb, new Guid("23170f69-40c1-278a-1000-000110ec0000")}, + {SevenZipFormat.GZip, new Guid("23170f69-40c1-278a-1000-000110ef0000")}, + {SevenZipFormat.Iso, new Guid("23170f69-40c1-278a-1000-000110e70000")}, + {SevenZipFormat.Lzh, new Guid("23170f69-40c1-278a-1000-000110060000")}, + {SevenZipFormat.Lzma, new Guid("23170f69-40c1-278a-1000-0001100a0000")}, + {SevenZipFormat.Nsis, new Guid("23170f69-40c1-278a-1000-000110090000")}, + {SevenZipFormat.Rar, new Guid("23170f69-40c1-278a-1000-000110030000")}, + {SevenZipFormat.Rar5, new Guid("23170f69-40c1-278a-1000-000110CC0000")}, + {SevenZipFormat.Rpm, new Guid("23170f69-40c1-278a-1000-000110eb0000")}, + {SevenZipFormat.Split, new Guid("23170f69-40c1-278a-1000-000110ea0000")}, + {SevenZipFormat.Tar, new Guid("23170f69-40c1-278a-1000-000110ee0000")}, + {SevenZipFormat.Wim, new Guid("23170f69-40c1-278a-1000-000110e60000")}, + {SevenZipFormat.Lzw, new Guid("23170f69-40c1-278a-1000-000110050000")}, + {SevenZipFormat.Zip, new Guid("23170f69-40c1-278a-1000-000110010000")}, + {SevenZipFormat.Udf, new Guid("23170f69-40c1-278a-1000-000110E00000")}, + {SevenZipFormat.Xar, new Guid("23170f69-40c1-278a-1000-000110E10000")}, + {SevenZipFormat.Mub, new Guid("23170f69-40c1-278a-1000-000110E20000")}, + {SevenZipFormat.Hfs, new Guid("23170f69-40c1-278a-1000-000110E30000")}, + {SevenZipFormat.Dmg, new Guid("23170f69-40c1-278a-1000-000110E40000")}, + {SevenZipFormat.XZ, new Guid("23170f69-40c1-278a-1000-0001100C0000")}, + {SevenZipFormat.Mslz, new Guid("23170f69-40c1-278a-1000-000110D50000")}, + {SevenZipFormat.PE, new Guid("23170f69-40c1-278a-1000-000110DD0000")}, + {SevenZipFormat.Elf, new Guid("23170f69-40c1-278a-1000-000110DE0000")}, + {SevenZipFormat.Swf, new Guid("23170f69-40c1-278a-1000-000110D70000")}, + {SevenZipFormat.Vhd, new Guid("23170f69-40c1-278a-1000-000110DC0000")}, + {SevenZipFormat.Flv, new Guid("23170f69-40c1-278a-1000-000110D60000")}, + {SevenZipFormat.SquashFS, new Guid("23170f69-40c1-278a-1000-000110D20000")}, + {SevenZipFormat.Lzma86, new Guid("23170f69-40c1-278a-1000-0001100B0000")}, + {SevenZipFormat.Ppmd, new Guid("23170f69-40c1-278a-1000-0001100D0000")}, + {SevenZipFormat.TE, new Guid("23170f69-40c1-278a-1000-000110CF0000")}, + {SevenZipFormat.UEFIc, new Guid("23170f69-40c1-278a-1000-000110D00000")}, + {SevenZipFormat.UEFIs, new Guid("23170f69-40c1-278a-1000-000110D10000")}, + {SevenZipFormat.CramFS, new Guid("23170f69-40c1-278a-1000-000110D30000")}, + {SevenZipFormat.APM, new Guid("23170f69-40c1-278a-1000-000110D40000")}, + {SevenZipFormat.Swfc, new Guid("23170f69-40c1-278a-1000-000110D80000")}, + {SevenZipFormat.Ntfs, new Guid("23170f69-40c1-278a-1000-000110D90000")}, + {SevenZipFormat.Fat, new Guid("23170f69-40c1-278a-1000-000110DA0000")}, + {SevenZipFormat.Mbr, new Guid("23170f69-40c1-278a-1000-000110DB0000")}, + {SevenZipFormat.MachO, new Guid("23170f69-40c1-278a-1000-000110DF0000")} + }; + + internal static Dictionary FileTypeGuidMapping = new Dictionary + { + {Definitions.FileType._7Z, new Guid("23170f69-40c1-278a-1000-000110070000")}, + {Definitions.FileType.BZ2, new Guid("23170f69-40c1-278a-1000-000110020000")}, + {Definitions.FileType.RAR_NEW, new Guid("23170f69-40c1-278a-1000-000110030000")}, + {Definitions.FileType.RAR_OLD, new Guid("23170f69-40c1-278a-1000-000110CC0000")}, + {Definitions.FileType.ZIP, new Guid("23170f69-40c1-278a-1000-000110010000")}, + }; + + internal static Dictionary FileSignatures = new Dictionary + { + {SevenZipFormat.Rar5, new byte[] {0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00}}, + {SevenZipFormat.Rar, new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00 }}, + {SevenZipFormat.Vhd, new byte[] { 0x63, 0x6F, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x78 }}, + {SevenZipFormat.Deb, new byte[] { 0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E }}, + {SevenZipFormat.Dmg, new byte[] { 0x78, 0x01, 0x73, 0x0D, 0x62, 0x62, 0x60 }}, + {SevenZipFormat.SevenZip, new byte[] { 0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C }}, + {SevenZipFormat.Tar, new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72 }}, + {SevenZipFormat.Iso, new byte[] { 0x43, 0x44, 0x30, 0x30, 0x31 }}, + {SevenZipFormat.Cab, new byte[] { 0x4D, 0x53, 0x43, 0x46 }}, + {SevenZipFormat.Rpm, new byte[] { 0xed, 0xab, 0xee, 0xdb }}, + {SevenZipFormat.Xar, new byte[] { 0x78, 0x61, 0x72, 0x21 }}, + {SevenZipFormat.Chm, new byte[] { 0x49, 0x54, 0x53, 0x46 }}, + {SevenZipFormat.BZip2, new byte[] { 0x42, 0x5A, 0x68 }}, + {SevenZipFormat.Flv, new byte[] { 0x46, 0x4C, 0x56 }}, + {SevenZipFormat.Swf, new byte[] { 0x46, 0x57, 0x53 }}, + {SevenZipFormat.GZip, new byte[] { 0x1f, 0x0b }}, + {SevenZipFormat.Zip, new byte[] { 0x50, 0x4b }}, + {SevenZipFormat.Arj, new byte[] { 0x60, 0xEA }}, + {SevenZipFormat.Lzh, new byte[] { 0x2D, 0x6C, 0x68 }} + }; + } +} diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/IArchiveExtractCallback.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/IArchiveExtractCallback.cs new file mode 100644 index 00000000..8f95cc80 --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/IArchiveExtractCallback.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + [ComImport] + [Guid("23170F69-40C1-278A-0000-000600200000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IArchiveExtractCallback //: IProgress + { + void SetTotal(ulong total); + void SetCompleted([In] ref ulong completeValue); + + [PreserveSig] + int GetStream( + uint index, + [MarshalAs(UnmanagedType.Interface)] out ISequentialOutStream outStream, + AskMode askExtractMode); + // GetStream OUT: S_OK - OK, S_FALSE - skeep this file + + void PrepareOperation(AskMode askExtractMode); + void SetOperationResult(OperationResult resultEOperationResult); + } +} \ No newline at end of file diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/Kernel32Dll.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/Kernel32Dll.cs new file mode 100644 index 00000000..79d62a90 --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/Kernel32Dll.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + internal static class Kernel32Dll + { + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern SafeLibraryHandle LoadLibrary([MarshalAs(UnmanagedType.LPTStr)] string lpFileName); + + [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + internal static extern IntPtr GetProcAddress(SafeLibraryHandle hModule, [MarshalAs(UnmanagedType.LPStr)] string procName); + + [SuppressUnmanagedCodeSecurity] + [DllImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool FreeLibrary(IntPtr hModule); + } +} \ No newline at end of file diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/SafeLibraryHandle.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/SafeLibraryHandle.cs new file mode 100644 index 00000000..ed38dd21 --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/SafeLibraryHandle.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.ConstrainedExecution; +using Microsoft.Win32.SafeHandles; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + internal sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid + { + public SafeLibraryHandle() : base(true) + { + } + + /// Release library handle + /// true if the handle was released + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + protected override bool ReleaseHandle() + { + return Kernel32Dll.FreeLibrary(this.handle); + } + } +} \ No newline at end of file diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipException.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipException.cs new file mode 100644 index 00000000..9fd9665c --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + public class SevenZipException : Exception + { + public SevenZipException() + { + } + + public SevenZipException(string message) : base(message) + { + } + + public SevenZipException(string message, Exception innerException) : base(message, innerException) + { + } + + protected SevenZipException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipFormat.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipFormat.cs new file mode 100644 index 00000000..6ca1ed4b --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipFormat.cs @@ -0,0 +1,285 @@ +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + /// + /// + /// + public enum SevenZipFormat + { + // Default invalid format value + Undefined = 0, + + /// + /// Open 7-zip archive format. + /// + /// Wikipedia information + SevenZip, + + /// + /// Proprietary Arj archive format. + /// + /// Wikipedia information + Arj, + + /// + /// Open Bzip2 archive format. + /// + /// Wikipedia information + BZip2, + + /// + /// Microsoft cabinet archive format. + /// + /// Wikipedia information + Cab, + + /// + /// Microsoft Compiled HTML Help file format. + /// + /// Wikipedia information + Chm, + + /// + /// Microsoft Compound file format. + /// + /// Wikipedia information + Compound, + + /// + /// Open Cpio archive format. + /// + /// Wikipedia information + Cpio, + + /// + /// Open Debian software package format. + /// + /// Wikipedia information + Deb, + + /// + /// Open Gzip archive format. + /// + /// Wikipedia information + GZip, + + /// + /// Open ISO disk image format. + /// + /// Wikipedia information + Iso, + + /// + /// Open Lzh archive format. + /// + /// Wikipedia information + Lzh, + + /// + /// Open core 7-zip Lzma raw archive format. + /// + /// Wikipedia information + Lzma, + + /// + /// Nullsoft installation package format. + /// + /// Wikipedia information + Nsis, + + /// + /// RarLab Rar archive format. + /// + /// Wikipedia information + Rar, + + /// + /// RarLab Rar archive format, version 5. + /// + /// Wikipedia information + Rar5, + + /// + /// Open Rpm software package format. + /// + /// Wikipedia information + Rpm, + + /// + /// Open split file format. + /// + /// Wikipedia information + Split, + + /// + /// Open Tar archive format. + /// + /// Wikipedia information + Tar, + + /// + /// Microsoft Windows Imaging disk image format. + /// + /// Wikipedia information + Wim, + + /// + /// Open LZW archive format; implemented in "compress" program; also known as "Z" archive format. + /// + /// Wikipedia information + Lzw, + + /// + /// Open Zip archive format. + /// + /// Wikipedia information + Zip, + + /// + /// Open Udf disk image format. + /// + Udf, + + /// + /// Xar open source archive format. + /// + /// Wikipedia information + Xar, + + /// + /// Mub + /// + Mub, + + /// + /// Macintosh Disk Image on CD. + /// + /// Wikipedia information + Hfs, + + /// + /// Apple Mac OS X Disk Copy Disk Image format. + /// + Dmg, + + /// + /// Open Xz archive format. + /// + /// Wikipedia information + XZ, + + /// + /// MSLZ archive format. + /// + Mslz, + + /// + /// Flash video format. + /// + /// Wikipedia information + Flv, + + /// + /// Shockwave Flash format. + /// + /// Wikipedia information + Swf, + + /// + /// Windows PE executable format. + /// + /// Wikipedia information + PE, + + /// + /// Linux executable Elf format. + /// + /// Wikipedia information + Elf, + + /// + /// Windows Installer Database. + /// + /// Wikipedia information + Msi, + + /// + /// Microsoft virtual hard disk file format. + /// + /// Wikipedia information + Vhd, + + /// + /// SquashFS file system format. + /// + /// Wikipedia information + SquashFS, + + /// + /// Lzma86 file format. + /// + Lzma86, + + /// + /// Prediction by Partial Matching by Dmitry algorithm. + /// + /// Wikipedia information + Ppmd, + + /// + /// TE format. + /// + TE, + + /// + /// UEFIc format. + /// + /// Wikipedia information + UEFIc, + + /// + /// UEFIs format. + /// + /// Wikipedia information + UEFIs, + + /// + /// Compressed ROM file system format. + /// + /// Wikipedia information + CramFS, + + /// + /// APM format. + /// + APM, + + /// + /// Swfc format. + /// + Swfc, + + /// + /// NTFS file system format. + /// + /// Wikipedia information + Ntfs, + + /// + /// FAT file system format. + /// + /// Wikipedia information + Fat, + + /// + /// MBR format. + /// + /// Wikipedia information + Mbr, + + /// + /// Mach-O file format. + /// + /// Wikipedia information + MachO + } +} \ No newline at end of file diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipHandle.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipHandle.cs new file mode 100644 index 00000000..f8fb73a7 --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipHandle.cs @@ -0,0 +1,68 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + internal class SevenZipHandle : IDisposable + { + private SafeLibraryHandle sevenZipSafeHandle; + + public SevenZipHandle(string sevenZipLibPath) + { + this.sevenZipSafeHandle = Kernel32Dll.LoadLibrary(sevenZipLibPath); + + if (this.sevenZipSafeHandle.IsInvalid) + { + throw new Win32Exception(); + } + + IntPtr functionPtr = Kernel32Dll.GetProcAddress(this.sevenZipSafeHandle, "GetHandlerProperty"); + + // Not valid dll + if (functionPtr == IntPtr.Zero) + { + this.sevenZipSafeHandle.Close(); + throw new ArgumentException(); + } + } + + ~SevenZipHandle() + { + this.Dispose(false); + } + + protected void Dispose(bool disposing) + { + if ((this.sevenZipSafeHandle != null) && !this.sevenZipSafeHandle.IsClosed) + { + this.sevenZipSafeHandle.Close(); + } + + this.sevenZipSafeHandle = null; + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + public IInArchive CreateInArchive(Guid classId) + { + if (this.sevenZipSafeHandle == null) + { + throw new ObjectDisposedException("SevenZipHandle"); + } + + IntPtr procAddress = Kernel32Dll.GetProcAddress(this.sevenZipSafeHandle, "CreateObject"); + CreateObjectDelegate createObject = (CreateObjectDelegate) Marshal.GetDelegateForFunctionPointer(procAddress, typeof (CreateObjectDelegate)); + + object result; + Guid interfaceId = typeof (IInArchive).GUID; + createObject(ref classId, ref interfaceId, out result); + + return result as IInArchive; + } + } +} \ No newline at end of file diff --git a/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipInterface.cs b/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipInterface.cs new file mode 100644 index 00000000..91666547 --- /dev/null +++ b/Wabbajack.VirtualFileSystem/SevenZipExtractor/SevenZipInterface.cs @@ -0,0 +1,459 @@ +// Version 1.5 + +using System; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Permissions; +using System.Threading; + +namespace Wabbajack.VirtualFileSystem.SevenZipExtractor +{ + [StructLayout(LayoutKind.Sequential)] + internal struct PropArray + { + uint length; + IntPtr pointerValues; + } + + [StructLayout(LayoutKind.Explicit)] + internal struct PropVariant + { + [DllImport("ole32.dll")] + private static extern int PropVariantClear(ref PropVariant pvar); + + [FieldOffset(0)] public ushort vt; + [FieldOffset(8)] public IntPtr pointerValue; + [FieldOffset(8)] public byte byteValue; + [FieldOffset(8)] public long longValue; + [FieldOffset(8)] public System.Runtime.InteropServices.ComTypes.FILETIME filetime; + [FieldOffset(8)] public PropArray propArray; + + public VarEnum VarType + { + get + { + return (VarEnum) this.vt; + } + } + + public void Clear() + { + switch (this.VarType) + { + case VarEnum.VT_EMPTY: + break; + + case VarEnum.VT_NULL: + case VarEnum.VT_I2: + case VarEnum.VT_I4: + case VarEnum.VT_R4: + case VarEnum.VT_R8: + case VarEnum.VT_CY: + case VarEnum.VT_DATE: + case VarEnum.VT_ERROR: + case VarEnum.VT_BOOL: + //case VarEnum.VT_DECIMAL: + case VarEnum.VT_I1: + case VarEnum.VT_UI1: + case VarEnum.VT_UI2: + case VarEnum.VT_UI4: + case VarEnum.VT_I8: + case VarEnum.VT_UI8: + case VarEnum.VT_INT: + case VarEnum.VT_UINT: + case VarEnum.VT_HRESULT: + case VarEnum.VT_FILETIME: + this.vt = 0; + break; + + default: + PropVariantClear(ref this); + break; + } + } + + public object GetObject() + { + switch (this.VarType) + { + case VarEnum.VT_EMPTY: + return null; + + case VarEnum.VT_FILETIME: + return DateTime.FromFileTime(this.longValue); + + default: + GCHandle PropHandle = GCHandle.Alloc(this, GCHandleType.Pinned); + + try + { + return Marshal.GetObjectForNativeVariant(PropHandle.AddrOfPinnedObject()); + } + finally + { + PropHandle.Free(); + } + } + } + } + + [ComImport] + [Guid("23170F69-40C1-278A-0000-000000050000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IProgress + { + void SetTotal(ulong total); + void SetCompleted([In] ref ulong completeValue); + } + + [ComImport] + [Guid("23170F69-40C1-278A-0000-000600100000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IArchiveOpenCallback + { + // ref ulong replaced with IntPtr because handlers ofter pass null value + // read actual value with Marshal.ReadInt64 + void SetTotal( + IntPtr files, // [In] ref ulong files, can use 'ulong* files' but it is unsafe + IntPtr bytes); // [In] ref ulong bytes + + void SetCompleted( + IntPtr files, // [In] ref ulong files + IntPtr bytes); // [In] ref ulong bytes + } + + [ComImport] + [Guid("23170F69-40C1-278A-0000-000500100000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface ICryptoGetTextPassword + { + [PreserveSig] + int CryptoGetTextPassword( + [MarshalAs(UnmanagedType.BStr)] out string password); + + //[return : MarshalAs(UnmanagedType.BStr)] + //string CryptoGetTextPassword(); + } + + internal enum AskMode : int + { + kExtract = 0, + kTest, + kSkip + } + + internal enum OperationResult : int + { + kOK = 0, + kUnSupportedMethod, + kDataError, + kCRCError + } + + [ComImport] + [Guid("23170F69-40C1-278A-0000-000600300000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IArchiveOpenVolumeCallback + { + void GetProperty( + ItemPropId propID, // PROPID + IntPtr value); // PROPVARIANT + + [PreserveSig] + int GetStream( + [MarshalAs(UnmanagedType.LPWStr)] string name, + [MarshalAs(UnmanagedType.Interface)] out IInStream inStream); + } + + [ComImport] + [Guid("23170F69-40C1-278A-0000-000600400000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IInArchiveGetStream + { + [return: MarshalAs(UnmanagedType.Interface)] + ISequentialInStream GetStream(uint index); + } + + [ComImport] + [Guid("23170F69-40C1-278A-0000-000300010000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface ISequentialInStream + { + //[PreserveSig] + //int Read( + // [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] data, + // uint size, + // IntPtr processedSize); // ref uint processedSize + + uint Read( + [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] data, + uint size); + + /* + Out: if size != 0, return_value = S_OK and (*processedSize == 0), + then there are no more bytes in stream. + if (size > 0) && there are bytes in stream, + this function must read at least 1 byte. + This function is allowed to read less than number of remaining bytes in stream. + You must call Read function in loop, if you need exact amount of data + */ + } + + [ComImport] + [Guid("23170F69-40C1-278A-0000-000300020000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface ISequentialOutStream + { + [PreserveSig] + int Write( + [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] data, + uint size, + IntPtr processedSize); // ref uint processedSize + /* + if (size > 0) this function must write at least 1 byte. + This function is allowed to write less than "size". + You must call Write function in loop, if you need to write exact amount of data + */ + } + + [ComImport] + [Guid("23170F69-40C1-278A-0000-000300030000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IInStream //: ISequentialInStream + { + //[PreserveSig] + //int Read( + // [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] data, + // uint size, + // IntPtr processedSize); // ref uint processedSize + + uint Read( + [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] data, + uint size); + + //[PreserveSig] + void Seek( + long offset, + uint seekOrigin, + IntPtr newPosition); // ref long newPosition + } + + [ComImport] + [Guid("23170F69-40C1-278A-0000-000300040000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IOutStream //: ISequentialOutStream + { + [PreserveSig] + int Write( + [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] data, + uint size, + IntPtr processedSize); // ref uint processedSize + + //[PreserveSig] + void Seek( + long offset, + uint seekOrigin, + IntPtr newPosition); // ref long newPosition + + [PreserveSig] + int SetSize(long newSize); + } + + internal enum ItemPropId : uint + { + kpidNoProperty = 0, + + kpidHandlerItemIndex = 2, + kpidPath, + kpidName, + kpidExtension, + kpidIsFolder, + kpidSize, + kpidPackedSize, + kpidAttributes, + kpidCreationTime, + kpidLastAccessTime, + kpidLastWriteTime, + kpidSolid, + kpidCommented, + kpidEncrypted, + kpidSplitBefore, + kpidSplitAfter, + kpidDictionarySize, + kpidCRC, + kpidType, + kpidIsAnti, + kpidMethod, + kpidHostOS, + kpidFileSystem, + kpidUser, + kpidGroup, + kpidBlock, + kpidComment, + kpidPosition, + kpidPrefix, + + kpidTotalSize = 0x1100, + kpidFreeSpace, + kpidClusterSize, + kpidVolumeName, + + kpidLocalName = 0x1200, + kpidProvider, + + kpidUserDefined = 0x10000 + } + + + [ComImport] + [Guid("23170F69-40C1-278A-0000-000600600000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + //[AutomationProxy(true)] + internal interface IInArchive + { + [PreserveSig] + int Open( + IInStream stream, + /*[MarshalAs(UnmanagedType.U8)]*/ [In] ref ulong maxCheckStartPosition, + [MarshalAs(UnmanagedType.Interface)] IArchiveOpenCallback openArchiveCallback); + + void Close(); + //void GetNumberOfItems([In] ref uint numItem); + uint GetNumberOfItems(); + + void GetProperty( + uint index, + ItemPropId propID, // PROPID + ref PropVariant value); // PROPVARIANT + + [PreserveSig] + int Extract( + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] uint[] indices, //[In] ref uint indices, + uint numItems, + int testMode, + [MarshalAs(UnmanagedType.Interface)] IArchiveExtractCallback extractCallback); + + // indices must be sorted + // numItems = 0xFFFFFFFF means all files + // testMode != 0 means "test files operation" + + void GetArchiveProperty( + uint propID, // PROPID + ref PropVariant value); // PROPVARIANT + + //void GetNumberOfProperties([In] ref uint numProperties); + uint GetNumberOfProperties(); + + void GetPropertyInfo( + uint index, + [MarshalAs(UnmanagedType.BStr)] out string name, + out ItemPropId propID, // PROPID + out ushort varType); //VARTYPE + + //void GetNumberOfArchiveProperties([In] ref uint numProperties); + uint GetNumberOfArchiveProperties(); + + void GetArchivePropertyInfo( + uint index, + [MarshalAs(UnmanagedType.BStr)] string name, + ref uint propID, // PROPID + ref ushort varType); //VARTYPE + } + + internal enum ArchivePropId : uint + { + kName = 0, + kClassID, + kExtension, + kAddExtension, + kUpdate, + kKeepName, + kStartSignature, + kFinishSignature, + kAssociate + } + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal delegate int CreateObjectDelegate( + [In] ref Guid classID, + [In] ref Guid interfaceID, + //out IntPtr outObject); + [MarshalAs(UnmanagedType.Interface)] out object outObject); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal delegate int GetHandlerPropertyDelegate( + ArchivePropId propID, + ref PropVariant value); // PROPVARIANT + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal delegate int GetNumberOfFormatsDelegate(out uint numFormats); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal delegate int GetHandlerProperty2Delegate( + uint formatIndex, + ArchivePropId propID, + ref PropVariant value); // PROPVARIANT + + internal class StreamWrapper : IDisposable + { + protected Stream BaseStream; + + protected StreamWrapper(Stream baseStream) + { + this.BaseStream = baseStream; + } + + public void Dispose() + { + this.BaseStream.Close(); + } + + public virtual void Seek(long offset, uint seekOrigin, IntPtr newPosition) + { + long Position = (uint) this.BaseStream.Seek(offset, (SeekOrigin) seekOrigin); + + if (newPosition != IntPtr.Zero) + { + Marshal.WriteInt64(newPosition, Position); + } + } + } + + internal class InStreamWrapper : StreamWrapper, ISequentialInStream, IInStream + { + public InStreamWrapper(Stream baseStream) : base(baseStream) + { + } + + public uint Read(byte[] data, uint size) + { + return (uint) this.BaseStream.Read(data, 0, (int) size); + } + } + + internal class OutStreamWrapper : StreamWrapper, ISequentialOutStream, IOutStream + { + public OutStreamWrapper(Stream baseStream) : base(baseStream) + { + } + + public int SetSize(long newSize) + { + this.BaseStream.SetLength(newSize); + return 0; + } + + public int Write(byte[] data, uint size, IntPtr processedSize) + { + this.BaseStream.Write(data, 0, (int) size); + + if (processedSize != IntPtr.Zero) + { + Marshal.WriteInt32(processedSize, (int) size); + } + + return 0; + } + } +} \ No newline at end of file