From 8ff205843d4dd13f07fdffae53fcb0e9c85e212d Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 13 Dec 2024 14:55:20 -0500 Subject: [PATCH] Enable partial decompression of MS-ZIP on all platforms --- BinaryObjectScanner/FileType/MicrosoftCAB.cs | 121 +++++++++++++++++-- ExtractionTool/Program.cs | 8 +- 2 files changed, 116 insertions(+), 13 deletions(-) diff --git a/BinaryObjectScanner/FileType/MicrosoftCAB.cs b/BinaryObjectScanner/FileType/MicrosoftCAB.cs index c3d81d73..cb9dc9ce 100644 --- a/BinaryObjectScanner/FileType/MicrosoftCAB.cs +++ b/BinaryObjectScanner/FileType/MicrosoftCAB.cs @@ -1,7 +1,10 @@ -using System.IO; +using System; +using System.IO; using BinaryObjectScanner.Interfaces; #if (NET40_OR_GREATER || NETCOREAPP) && WIN using LibMSPackN; +#else +using SabreTools.Models.MicrosoftCabinet; #endif namespace BinaryObjectScanner.FileType @@ -27,9 +30,111 @@ namespace BinaryObjectScanner.FileType public bool Extract(Stream? stream, string file, string outDir, bool includeDebug) { #if NET20 || NET35 || !WIN - // Not supported for old .NET due to feature requirements - // Not supported in non-Windows builds due to DLL requirements - return false; + try + { + if (!File.Exists(file)) + return false; + + // Create the wrapper + var cabArchive = SabreTools.Serialization.Wrappers.MicrosoftCabinet.Create(stream); + if (cabArchive?.Model?.Folders == null || cabArchive.Model.Folders.Length == 0) + return false; + + // Loop through the folders + for (int f = 0; f < cabArchive!.Model.Folders.Length; f++) + { + // Ensure data blocks + var folder = cabArchive.Model.Folders[f]; + if (folder?.DataBlocks == null || folder.DataBlocks.Length == 0) + continue; + + // Loop through the data blocks + var ms = new MemoryStream(); + foreach (var db in folder.DataBlocks) + { + if (db?.CompressedData == null) + continue; + + // Uncompressed data + if ((folder.CompressionType & CompressionType.TYPE_NONE) != 0) + { + ms.Write(db.CompressedData); + ms.Flush(); + } + + // MS-ZIP + else if ((folder.CompressionType & CompressionType.TYPE_MSZIP) != 0) + { + var decomp = SabreTools.Compression.MSZIP.Decompressor.Create(); + decomp.CopyTo(db.CompressedData, ms); + } + + // Quantum + else if ((folder.CompressionType & CompressionType.TYPE_QUANTUM) != 0) + { + uint windowBits = (uint)(((ushort)folder.CompressionType >> 8) & 0x1f); + var decomp = SabreTools.Compression.Quantum.Decompressor.Create(db.CompressedData, windowBits); + byte[] data = decomp.Process(); + ms.Write(data); + ms.Flush(); + } + + // LZX + else if ((folder.CompressionType & CompressionType.TYPE_LZX) != 0) + { + // TODO: Unsupported + continue; + } + + // Unknown + else + { + continue; + } + } + + // If no data was read + if (ms.Length == 0) + continue; + + // Ensure files + if (cabArchive?.Model?.Files == null || cabArchive.Model.Files.Length == 0) + continue; + + // Loop through the files + foreach (var compressedFile in cabArchive.Model.Files) + { + if (compressedFile?.Name == null || compressedFile.FolderIndex != (FolderIndex)f) + continue; + + try + { + byte[] fileData = new byte[compressedFile.FileSize]; + Array.Copy(ms.ToArray(), compressedFile.FolderStartOffset, fileData, 0, compressedFile.FileSize); + + string tempFile = Path.Combine(outDir, compressedFile.Name); + var directoryName = Path.GetDirectoryName(tempFile); + if (directoryName != null && !Directory.Exists(directoryName)) + Directory.CreateDirectory(directoryName); + + using var of = File.OpenWrite(tempFile); + of.Write(fileData); + of.Flush(); + } + catch (Exception ex) + { + if (includeDebug) Console.WriteLine(ex); + } + } + } + + return true; + } + catch (Exception ex) + { + if (includeDebug) Console.WriteLine(ex); + return false; + } #else try { @@ -49,17 +154,17 @@ namespace BinaryObjectScanner.FileType compressedFile.ExtractTo(tempFile); } - catch (System.Exception ex) + catch (Exception ex) { - if (includeDebug) System.Console.WriteLine(ex); + if (includeDebug) Console.WriteLine(ex); } } return true; } - catch (System.Exception ex) + catch (Exception ex) { - if (includeDebug) System.Console.WriteLine(ex); + if (includeDebug) Console.WriteLine(ex); return false; } #endif diff --git a/ExtractionTool/Program.cs b/ExtractionTool/Program.cs index 5f945cd2..03bf3331 100644 --- a/ExtractionTool/Program.cs +++ b/ExtractionTool/Program.cs @@ -300,16 +300,14 @@ namespace ExtractionTool { // Build the cabinet information Console.WriteLine("Extracting MS-CAB contents"); +#if NET20 || NET35 || !WIN + Console.WriteLine("WARNING: LZX compression not supported so some files may be skipped!"); +#endif Console.WriteLine(); -#if NET20 || NET35 || !WIN - Console.WriteLine("Extraction is not supported for this framework!"); - Console.WriteLine(); -#else // Extract using the FileType var mscab = new MicrosoftCAB(); mscab.Extract(stream, file, outputDirectory, includeDebug: true); -#endif } // MoPaQ (MPQ) archive