From 7968a79fe6b96e411aedbe15ab25f4a43bb4e146 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 7 Jul 2022 16:33:15 -0700 Subject: [PATCH] Add start of decompression --- BurnOutSharp/FileType/MicrosoftCAB.cs | 120 +++++++++++++++++++++----- 1 file changed, 100 insertions(+), 20 deletions(-) diff --git a/BurnOutSharp/FileType/MicrosoftCAB.cs b/BurnOutSharp/FileType/MicrosoftCAB.cs index 6fbc74db..6cc89798 100644 --- a/BurnOutSharp/FileType/MicrosoftCAB.cs +++ b/BurnOutSharp/FileType/MicrosoftCAB.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.IO.Compression; +using System.Linq; using System.Text; using BurnOutSharp.Interfaces; using BurnOutSharp.Tools; @@ -213,11 +214,6 @@ namespace BurnOutSharp.FileType /// public CFFILE[] Files { get; private set; } - /// - /// The actual compressed file data in CFDATA entries. - /// - public CFDATA[] DataBlocks { get; private set; } - #endregion #region Serialization @@ -242,7 +238,7 @@ namespace BurnOutSharp.FileType cabinet.Folders = new CFFOLDER[cabinet.Header.FolderCount]; for (int i = 0; i < cabinet.Header.FolderCount; i++) { - cabinet.Folders[i] = CFFOLDER.Deserialize(data, ref dataPtr, cabinet.Header.FolderReservedSize); + cabinet.Folders[i] = CFFOLDER.Deserialize(data, ref dataPtr, basePtr, cabinet.Header.FolderReservedSize, cabinet.Header.DataReservedSize); if (cabinet.Folders[i] == null) return null; } @@ -259,10 +255,6 @@ namespace BurnOutSharp.FileType return null; } - // TODO: Should we populate the data blocks here? - // TODO: How do we determine the number of data blocks? - // TODO: If the data blocks start right after the CFFILE data, should we just store the data block offset? - return cabinet; } @@ -294,11 +286,22 @@ namespace BurnOutSharp.FileType // Loop through and extract all files foreach (CFFILE file in Files) { + // Create the output path string outputPath = Path.Combine(outputDirectory, file.NameAsString); - } - // TODO: We don't check for other cabinets here yet - // TODO: Read and decompress data blocks + // Get the associated folder, if possible + CFFOLDER folder = null; + if (file.FolderIndex != FolderIndex.CONTINUED_FROM_PREV && file.FolderIndex != FolderIndex.CONTINUED_TO_NEXT && file.FolderIndex != FolderIndex.CONTINUED_PREV_AND_NEXT) + folder = Folders[(int)file.FolderIndex]; + + // If we don't have a folder, we can't continue + if (folder == null) + return false; + + // TODO: We don't keep the stream open or accessible here to seek + // TODO: We don't check for other cabinets here yet + // TODO: Read and decompress data blocks + } return true; } @@ -334,8 +337,20 @@ namespace BurnOutSharp.FileType // Get the file to extract CFFILE file = Files[fileIndex]; + + // Create the output path string outputPath = Path.Combine(outputDirectory, file.NameAsString); + // Get the associated folder, if possible + CFFOLDER folder = null; + if (file.FolderIndex != FolderIndex.CONTINUED_FROM_PREV && file.FolderIndex != FolderIndex.CONTINUED_TO_NEXT && file.FolderIndex != FolderIndex.CONTINUED_PREV_AND_NEXT) + folder = Folders[(int)file.FolderIndex]; + + // If we don't have a folder, we can't continue + if (folder == null) + return false; + + // TODO: We don't keep the stream open or accessible here to seek // TODO: We don't check for other cabinets here yet // TODO: Read and decompress data blocks @@ -749,7 +764,7 @@ namespace BurnOutSharp.FileType } #endregion - } + } [Flags] internal enum HeaderFlags : ushort @@ -825,6 +840,51 @@ namespace BurnOutSharp.FileType /// public byte[] ReservedData { get; private set; } + /// + /// Data blocks associated with this folder + /// + public Dictionary DataBlocks { get; private set; } = new Dictionary(); + + /// + /// Get the uncompressed data associated with this folder, if possible + /// + public byte[] UncompressedData + { + get + { + if (DataBlocks == null || DataBlocks.Count == 0) + return null; + + List data = new List(); + foreach (CFDATA dataBlock in DataBlocks.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value)) + { + byte[] decompressed = null; + switch (CompressionType) + { + case CompressionType.TYPE_NONE: + decompressed = dataBlock.CompressedData; + break; + case CompressionType.TYPE_MSZIP: + decompressed = MSZIPBlock.Deserialize(dataBlock.CompressedData).DecompressBlock(); + break; + case CompressionType.TYPE_QUANTUM: + // TODO: UNIMPLEMENTED + break; + case CompressionType.TYPE_LZX: + // TODO: UNIMPLEMENTED + break; + default: + return null; + } + + if (decompressed != null) + data.AddRange(decompressed); + } + + return data.ToArray(); + } + } + #endregion #region Serialization @@ -832,7 +892,7 @@ namespace BurnOutSharp.FileType /// /// Deserialize at into a CFFOLDER object /// - public static CFFOLDER Deserialize(byte[] data, ref int dataPtr, byte folderReservedSize = 0) + public static CFFOLDER Deserialize(byte[] data, ref int dataPtr, int basePtr, byte folderReservedSize, byte dataReservedSize) { if (data == null || dataPtr < 0) return null; @@ -850,6 +910,22 @@ namespace BurnOutSharp.FileType dataPtr += folderReservedSize; } + // TODO: Fix this - For some reason, when reading the block headers, it's not functioning correctly + // In the case of the test file, the compressed size is `942` which is incomplete and ends up + // throwing an exception when decompressing to MS-ZIP. This only happens on block 2 of 2. The first + // block decompresses to the exact correct size. In the case above, the true block length should + // be `10150` instead + if (folder.CabStartOffset > 0) + { + int blockPtr = basePtr + (int)folder.CabStartOffset; + for (int i = 0; i < folder.DataCount; i++) + { + int offset = blockPtr; + CFDATA dataBlock = CFDATA.Deserialize(data, ref blockPtr, dataReservedSize); + folder.DataBlocks[offset] = dataBlock; + } + } + return folder; } @@ -1201,6 +1277,9 @@ namespace BurnOutSharp.FileType dataBlock.CompressedSize = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; dataBlock.UncompressedSize = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; + if (dataBlock.UncompressedSize != 0 && dataBlock.CompressedSize > dataBlock.UncompressedSize) + return null; + if (dataReservedSize > 0) { dataBlock.ReservedData = new byte[dataReservedSize]; @@ -1292,20 +1371,21 @@ namespace BurnOutSharp.FileType #region Serialization - public static MSZIPBlock Deserialize(byte[] data, ref int dataPtr, int blockSize) + public static MSZIPBlock Deserialize(byte[] data) { - if (data == null || dataPtr < 0 || blockSize <= 0) + if (data == null) return null; MSZIPBlock block = new MSZIPBlock(); + int dataPtr = 0; block.Signature = BitConverter.ToUInt16(data, dataPtr); dataPtr += 2; if (block.Signature != SignatureValue) return null; - block.Data = new byte[blockSize]; - Array.Copy(data, dataPtr, block.Data, 0, blockSize); - dataPtr += blockSize; + block.Data = new byte[data.Length - 2]; + Array.Copy(data, dataPtr, block.Data, 0, data.Length - 2); + dataPtr += data.Length - 2; return block; }