using System; using System.Collections.Generic; using System.IO; using BinaryObjectScanner.Interfaces; namespace BinaryObjectScanner.FileType { /// /// Microsoft cabinet file /// /// Specification available at /// public class MicrosoftCAB : IExtractable { /// #if NET48 public string Extract(string file, bool includeDebug) #else public string? Extract(string file, bool includeDebug) #endif { if (!File.Exists(file)) return null; using (var fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read)) { return Extract(fs, file, includeDebug); } } /// #if NET48 public string Extract(Stream stream, string file, bool includeDebug) #else public string? Extract(Stream stream, string file, bool includeDebug) #endif { try { // TODO: Fix/re-enable/do ANYTHING to get this working again return null; // Open the cab file var cabFile = SabreTools.Serialization.Wrappers.MicrosoftCabinet.Create(stream); if (cabFile == null) return null; // Create a temp output directory string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempPath); // If entry extraction fails bool success = ExtractAll(cabFile, tempPath); if (!success) return null; return tempPath; } catch (Exception ex) { if (includeDebug) Console.WriteLine(ex); return null; } } #region Folders /// /// Get the uncompressed data associated with a folder /// /// Folder index to check /// Byte array representing the data, null on error /// All but uncompressed are unimplemented #if NET48 public static byte[] GetUncompressedData(SabreTools.Serialization.Wrappers.MicrosoftCabinet item, int folderIndex) #else public static byte[]? GetUncompressedData(SabreTools.Serialization.Wrappers.MicrosoftCabinet item, int folderIndex) #endif { // If we have an invalid folder index if (folderIndex < 0 || item.Model.Folders == null || folderIndex >= item.Model.Folders.Length) return null; // Get the folder header var folder = item.Model.Folders[folderIndex]; if (folder == null) return null; // If we have invalid data blocks if (folder.DataBlocks == null || folder.DataBlocks.Length == 0) return null; // Setup LZX decompression var lzx = new Compression.LZX.State(); Compression.LZX.Decompressor.Init(((ushort)folder.CompressionType >> 8) & 0x1f, lzx); // Setup MS-ZIP decompression Compression.MSZIP.State mszip = new Compression.MSZIP.State(); // Setup Quantum decompression var qtm = new Compression.Quantum.State(); Compression.Quantum.Decompressor.InitState(qtm, folder); List data = new List(); foreach (var dataBlock in folder.DataBlocks) { if (dataBlock == null) continue; #if NET48 byte[] decompressed = new byte[dataBlock.UncompressedSize]; #else byte[]? decompressed = new byte[dataBlock.UncompressedSize]; #endif switch (folder.CompressionType & SabreTools.Models.MicrosoftCabinet.CompressionType.MASK_TYPE) { case SabreTools.Models.MicrosoftCabinet.CompressionType.TYPE_NONE: decompressed = dataBlock.CompressedData; break; case SabreTools.Models.MicrosoftCabinet.CompressionType.TYPE_MSZIP: decompressed = new byte[SabreTools.Models.Compression.MSZIP.Constants.ZIPWSIZE]; Compression.MSZIP.Decompressor.Decompress(mszip, dataBlock.CompressedSize, dataBlock.CompressedData, dataBlock.UncompressedSize, decompressed); Array.Resize(ref decompressed, dataBlock.UncompressedSize); break; case SabreTools.Models.MicrosoftCabinet.CompressionType.TYPE_QUANTUM: Compression.Quantum.Decompressor.Decompress(qtm, dataBlock.CompressedSize, dataBlock.CompressedData, dataBlock.UncompressedSize, decompressed); break; case SabreTools.Models.MicrosoftCabinet.CompressionType.TYPE_LZX: Compression.LZX.Decompressor.Decompress(state: lzx, dataBlock.CompressedSize, dataBlock.CompressedData, dataBlock.UncompressedSize, decompressed); break; default: return null; } if (decompressed != null) data.AddRange(decompressed); } return data.ToArray(); } #endregion #region Files /// /// Extract all files from the MS-CAB to an output directory /// /// Output directory to write to /// True if all filez extracted, false otherwise public static bool ExtractAll(SabreTools.Serialization.Wrappers.MicrosoftCabinet item, string outputDirectory) { // If we have no files if (item.Model.Files == null || item.Model.Files.Length == 0) return false; // Loop through and extract all files to the output bool allExtracted = true; for (int i = 0; i < item.Model.Files.Length; i++) { allExtracted &= ExtractFile(item, i, outputDirectory); } return allExtracted; } /// /// Extract a file from the MS-CAB to an output directory by index /// /// File index to extract /// Output directory to write to /// True if the file extracted, false otherwise public static bool ExtractFile(SabreTools.Serialization.Wrappers.MicrosoftCabinet item, int index, string outputDirectory) { // If we have an invalid file index if (index < 0 || item.Model.Files == null || index >= item.Model.Files.Length) return false; // If we have an invalid output directory if (string.IsNullOrWhiteSpace(outputDirectory)) return false; // Ensure the directory exists Directory.CreateDirectory(outputDirectory); // Get the file header var file = item.Model.Files[index]; if (file == null || file.FileSize == 0) return false; // Create the output filename string fileName = Path.Combine(outputDirectory, file.Name ?? $"file{index}"); // Get the file data, if possible #if NET48 byte[] fileData = GetFileData(item, index); #else byte[]? fileData = GetFileData(item, index); #endif if (fileData == null) return false; // Write the file data using (FileStream fs = File.OpenWrite(fileName)) { fs.Write(fileData, 0, fileData.Length); } return true; } /// /// Get the uncompressed data associated with a file /// /// File index to check /// Byte array representing the data, null on error #if NET48 public static byte[] GetFileData(SabreTools.Serialization.Wrappers.MicrosoftCabinet item, int fileIndex) #else public static byte[]? GetFileData(SabreTools.Serialization.Wrappers.MicrosoftCabinet item, int fileIndex) #endif { // If we have an invalid file index if (fileIndex < 0 || item.Model.Files == null || fileIndex >= item.Model.Files.Length) return null; // Get the file header var file = item.Model.Files[fileIndex]; if (file == null || file.FileSize == 0) return null; // Get the parent folder data #if NET48 byte[] folderData = GetUncompressedData(item, (int)file.FolderIndex); #else byte[]? folderData = GetUncompressedData(item, (int)file.FolderIndex); #endif if (folderData == null || folderData.Length == 0) return null; // Create the output file data byte[] fileData = new byte[file.FileSize]; if (folderData.Length < file.FolderStartOffset + file.FileSize) return null; // Get the segment that represents this file Array.Copy(folderData, file.FolderStartOffset, fileData, 0, file.FileSize); return fileData; } #endregion } }