diff --git a/BurnOutSharp.Builder/MicrosoftCabinet.cs b/BurnOutSharp.Builder/MicrosoftCabinet.cs index fdba2f6a..af5139bd 100644 --- a/BurnOutSharp.Builder/MicrosoftCabinet.cs +++ b/BurnOutSharp.Builder/MicrosoftCabinet.cs @@ -9,50 +9,6 @@ namespace BurnOutSharp.Builder // TODO: Add multi-cabinet reading public class MicrosoftCabinet { - #region Constants - - /// - /// Human-readable signature - /// - public static readonly string SignatureString = "MSCF"; - - /// - /// Signature as an unsigned Int32 value - /// - public const uint SignatureValue = 0x4643534D; - - /// - /// Signature as a byte array - /// - public static readonly byte[] SignatureBytes = new byte[] { 0x4D, 0x53, 0x43, 0x46 }; - - /// - /// A maximum uncompressed size of an input file to store in CAB - /// - public const uint MaximumUncompressedFileSize = 0x7FFF8000; - - /// - /// A maximum file COUNT - /// - public const ushort MaximumFileCount = 0xFFFF; - - /// - /// A maximum size of a created CAB (compressed) - /// - public const uint MaximumCabSize = 0x7FFFFFFF; - - /// - /// A maximum CAB-folder COUNT - /// - public const ushort MaximumFolderCount = 0xFFFF; - - /// - /// A maximum uncompressed data size in a CAB-folder - /// - public const uint MaximumUncompressedFolderSize = 0x7FFF8000; - - #endregion - #region Byte Data /// @@ -170,40 +126,18 @@ namespace BurnOutSharp.Builder CFHEADER header = new CFHEADER(); header.Signature = data.ReadUInt32(); - if (header.Signature != SignatureValue) + if (header.Signature != 0x4643534D) return null; header.Reserved1 = data.ReadUInt32(); - if (header.Reserved1 != 0x00000000) - return null; - header.CabinetSize = data.ReadUInt32(); - if (header.CabinetSize > MaximumCabSize) - return null; - header.Reserved2 = data.ReadUInt32(); - if (header.Reserved2 != 0x00000000) - return null; - header.FilesOffset = data.ReadUInt32(); - header.Reserved3 = data.ReadUInt32(); - if (header.Reserved3 != 0x00000000) - return null; - header.VersionMinor = data.ReadByteValue(); header.VersionMajor = data.ReadByteValue(); - if (header.VersionMajor != 0x00000001 || header.VersionMinor != 0x00000003) - return null; - header.FolderCount = data.ReadUInt16(); - if (header.FolderCount > MaximumFolderCount) - return null; - header.FileCount = data.ReadUInt16(); - if (header.FileCount > MaximumFileCount) - return null; - header.Flags = (HeaderFlags)data.ReadUInt16(); header.SetID = data.ReadUInt16(); header.CabinetIndex = data.ReadUInt16(); @@ -322,43 +256,5 @@ namespace BurnOutSharp.Builder } #endregion - - #region Helpers - - /// - /// The computation and verification of checksums found in CFDATA structure entries cabinet files is - /// done by using a function described by the following mathematical notation. When checksums are - /// not supplied by the cabinet file creating application, the checksum field is set to 0 (zero). Cabinet - /// extracting applications do not compute or verify the checksum if the field is set to 0 (zero). - /// - public static class Checksum - { - public static uint ChecksumData(byte[] data) - { - uint[] C = new uint[4] - { - S(data, 1, data.Length), - S(data, 2, data.Length), - S(data, 3, data.Length), - S(data, 4, data.Length), - }; - - return C[0] ^ C[1] ^ C[2] ^ C[3]; - } - - private static uint S(byte[] a, int b, int x) - { - int n = a.Length; - - if (x < 4 && b > n % 4) - return 0; - else if (x < 4 && b <= n % 4) - return a[n - b + 1]; - else // if (x >= 4) - return a[n - x + b] ^ S(a, b, x - 4); - } - } - - #endregion } } diff --git a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/Block.cs b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/Block.cs deleted file mode 100644 index e53b273f..00000000 --- a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/Block.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP -{ - /// - /// Each MSZIP block MUST consist of a 2-byte MSZIP signature and one or more RFC 1951 blocks. The - /// 2-byte MSZIP signature MUST consist of the bytes 0x43 and 0x4B. The MSZIP signature MUST be - /// the first 2 bytes in the MSZIP block. The MSZIP signature is shown in the following packet diagram. - /// - /// - public class Block - { - /// - /// 'CK' - /// - public ushort Signature; - - /// - /// Each MSZIP block is the result of a single deflate compression operation, as defined in [RFC1951]. - /// The compressor that performs the compression operation MUST generate one or more RFC 1951 - /// blocks, as defined in [RFC1951]. The number, deflation mode, and type of RFC 1951 blocks in each - /// MSZIP block is determined by the compressor, as defined in [RFC1951]. The last RFC 1951 block in - /// each MSZIP block MUST be marked as the "end" of the stream(1), as defined by [RFC1951] - /// section 3.2.3. Decoding trees MUST be discarded after each RFC 1951 block, but the history buffer - /// MUST be maintained.Each MSZIP block MUST represent no more than 32 KB of uncompressed data. - /// - /// The maximum compressed size of each MSZIP block is 32 KB + 12 bytes. This enables the MSZIP - /// block to contain 32 KB of data split between two noncompressed RFC 1951 blocks, each of which - /// has a value of BTYPE = 00. - /// - public byte[] Data; - } -} \ No newline at end of file diff --git a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/BlockHeader.cs b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/BlockHeader.cs new file mode 100644 index 00000000..96d143f8 --- /dev/null +++ b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/BlockHeader.cs @@ -0,0 +1,28 @@ +namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP +{ + /// + /// Each MSZIP block MUST consist of a 2-byte MSZIP signature and one or more RFC 1951 blocks. The + /// 2-byte MSZIP signature MUST consist of the bytes 0x43 and 0x4B. The MSZIP signature MUST be + /// the first 2 bytes in the MSZIP block. The MSZIP signature is shown in the following packet diagram. + /// + /// Each MSZIP block is the result of a single deflate compression operation, as defined in [RFC1951]. + /// The compressor that performs the compression operation MUST generate one or more RFC 1951 + /// blocks, as defined in [RFC1951]. The number, deflation mode, and type of RFC 1951 blocks in each + /// MSZIP block is determined by the compressor, as defined in [RFC1951]. The last RFC 1951 block in + /// each MSZIP block MUST be marked as the "end" of the stream(1), as defined by [RFC1951] + /// section 3.2.3. Decoding trees MUST be discarded after each RFC 1951 block, but the history buffer + /// MUST be maintained.Each MSZIP block MUST represent no more than 32 KB of uncompressed data. + /// + /// The maximum compressed size of each MSZIP block is 32 KB + 12 bytes. This enables the MSZIP + /// block to contain 32 KB of data split between two noncompressed RFC 1951 blocks, each of which + /// has a value of BTYPE = 00. + /// + /// + public class BlockHeader + { + /// + /// 'CK' + /// + public ushort Signature; + } +} \ No newline at end of file diff --git a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/CompressedBlock.cs b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/CompressedBlockHeader.cs similarity index 81% rename from BurnOutSharp.Models/MicrosoftCabinet/MSZIP/CompressedBlock.cs rename to BurnOutSharp.Models/MicrosoftCabinet/MSZIP/CompressedBlockHeader.cs index 4a802c10..a872a0c4 100644 --- a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/CompressedBlock.cs +++ b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/CompressedBlockHeader.cs @@ -1,10 +1,10 @@ namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP { /// - /// Base class for compressed blocks + /// Base class for compressed block headers /// /// - public abstract class CompressedBlock : IBlockData + public abstract class CompressedBlockHeader : IBlockDataHeader { /// /// Huffman code lengths for the literal / length alphabet diff --git a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DeflateBlock.cs b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DeflateBlockHeader.cs similarity index 87% rename from BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DeflateBlock.cs rename to BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DeflateBlockHeader.cs index d8e02db7..26b887f3 100644 --- a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DeflateBlock.cs +++ b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DeflateBlockHeader.cs @@ -1,7 +1,7 @@ namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP { /// - public class DeflateBlock + public class DeflateBlockHeader { /// /// Set if and only if this is the last block of the data set. @@ -18,6 +18,6 @@ namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP /// /// Block data as defined by the compression type /// - public IBlockData BlockData { get; set; } + public IBlockDataHeader BlockData { get; set; } } } \ No newline at end of file diff --git a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DynamicHuffmanCompressedBlock.cs b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DynamicHuffmanCompressedBlockHeader.cs similarity index 83% rename from BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DynamicHuffmanCompressedBlock.cs rename to BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DynamicHuffmanCompressedBlockHeader.cs index 14e4155f..b4af64d9 100644 --- a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DynamicHuffmanCompressedBlock.cs +++ b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/DynamicHuffmanCompressedBlockHeader.cs @@ -4,7 +4,7 @@ namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP /// Compression with dynamic Huffman codes (BTYPE=10) /// /// - public class DynamicHuffmanCompressedBlock : CompressedBlock + public class DynamicHuffmanCompressedBlockHeader : CompressedBlockHeader { /// public override int[] LiteralLengths { get; set; } diff --git a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/FixedHuffmanCompressedBlock.cs b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/FixedHuffmanCompressedBlockHeader.cs similarity index 97% rename from BurnOutSharp.Models/MicrosoftCabinet/MSZIP/FixedHuffmanCompressedBlock.cs rename to BurnOutSharp.Models/MicrosoftCabinet/MSZIP/FixedHuffmanCompressedBlockHeader.cs index e310627e..6c7eb51e 100644 --- a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/FixedHuffmanCompressedBlock.cs +++ b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/FixedHuffmanCompressedBlockHeader.cs @@ -7,7 +7,7 @@ namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP /// /// /// - public class FixedHuffmanCompressedBlock : CompressedBlock + public class FixedHuffmanCompressedBlockHeader : CompressedBlockHeader { #region Properties diff --git a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/IBlockData.cs b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/IBlockDataHeader.cs similarity index 62% rename from BurnOutSharp.Models/MicrosoftCabinet/MSZIP/IBlockData.cs rename to BurnOutSharp.Models/MicrosoftCabinet/MSZIP/IBlockDataHeader.cs index 5bef0b4d..7825b965 100644 --- a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/IBlockData.cs +++ b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/IBlockDataHeader.cs @@ -1,8 +1,8 @@ namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP { /// - /// Empty interface defining block types + /// Empty interface defining block header types /// /// - public interface IBlockData { } + public interface IBlockDataHeader { } } \ No newline at end of file diff --git a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/NonCompressedBlock.cs b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/NonCompressedBlockHeader.cs similarity index 75% rename from BurnOutSharp.Models/MicrosoftCabinet/MSZIP/NonCompressedBlock.cs rename to BurnOutSharp.Models/MicrosoftCabinet/MSZIP/NonCompressedBlockHeader.cs index 18ec4e12..642e74fa 100644 --- a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/NonCompressedBlock.cs +++ b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/NonCompressedBlockHeader.cs @@ -4,7 +4,7 @@ namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP /// Non-compressed blocks (BTYPE=00) /// /// - public class NonCompressedBlock : IBlockData + public class NonCompressedBlockHeader : IBlockDataHeader { /// /// The number of data bytes in the block @@ -17,10 +17,5 @@ namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP /// /// Bytes 2-3 public ushort NLEN; - - /// - /// bytes of literal data - /// - public byte[] Data; } } \ No newline at end of file diff --git a/BurnOutSharp.Wrappers/MicrosoftCabinet.cs b/BurnOutSharp.Wrappers/MicrosoftCabinet.cs index 06f18110..d73de356 100644 --- a/BurnOutSharp.Wrappers/MicrosoftCabinet.cs +++ b/BurnOutSharp.Wrappers/MicrosoftCabinet.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using BurnOutSharp.Utilities; namespace BurnOutSharp.Wrappers { @@ -111,7 +112,7 @@ namespace BurnOutSharp.Wrappers /// /// Create a Microsoft Cabinet from a byte array and offset /// - /// Byte array representing the executable + /// Byte array representing the cabinet /// Offset within the array to parse /// A cabinet wrapper on success, null on failure public static MicrosoftCabinet Create(byte[] data, int offset) @@ -132,7 +133,7 @@ namespace BurnOutSharp.Wrappers /// /// Create a Microsoft Cabinet from a Stream /// - /// Stream representing the executable + /// Stream representing the cabinet /// A cabinet wrapper on success, null on failure public static MicrosoftCabinet Create(Stream data) { @@ -155,6 +156,562 @@ namespace BurnOutSharp.Wrappers #endregion + #region Checksumming + + /// + /// The computation and verification of checksums found in CFDATA structure entries cabinet files is + /// done by using a function described by the following mathematical notation. When checksums are + /// not supplied by the cabinet file creating application, the checksum field is set to 0 (zero). Cabinet + /// extracting applications do not compute or verify the checksum if the field is set to 0 (zero). + /// + private static class Checksum + { + public static uint ChecksumData(byte[] data) + { + uint[] C = new uint[4] + { + S(data, 1, data.Length), + S(data, 2, data.Length), + S(data, 3, data.Length), + S(data, 4, data.Length), + }; + + return C[0] ^ C[1] ^ C[2] ^ C[3]; + } + + private static uint S(byte[] a, int b, int x) + { + int n = a.Length; + + if (x < 4 && b > n % 4) + return 0; + else if (x < 4 && b <= n % 4) + return a[n - b + 1]; + else // if (x >= 4) + return a[n - x + b] ^ S(a, b, x - 4); + } + } + + #endregion + + #region Compression + + #region LZX + + // TODO: Implement LZX decompression + + #endregion + + #region MSZIP + + #region Constants + + /// + /// Maximum Huffman code bit count + /// + private const int MAX_BITS = 16; + + #endregion + + #region Properties + + /// + /// Match lengths for literal codes 257..285 + /// + /// Each value here is the lower bound for lengths represented + private static Dictionary LiteralLengths + { + get + { + // If we have cached length mappings, use those + if (_literalLengths != null) + return _literalLengths; + + // Otherwise, build it from scratch + _literalLengths = new Dictionary + { + [257] = 3, + [258] = 4, + [259] = 5, + [260] = 6, + [261] = 7, + [262] = 8, + [263] = 9, + [264] = 10, + [265] = 11, // 11,12 + [266] = 13, // 13,14 + [267] = 15, // 15,16 + [268] = 17, // 17,18 + [269] = 19, // 19-22 + [270] = 23, // 23-26 + [271] = 27, // 27-30 + [272] = 31, // 31-34 + [273] = 35, // 35-42 + [274] = 43, // 43-50 + [275] = 51, // 51-58 + [276] = 59, // 59-66 + [277] = 67, // 67-82 + [278] = 83, // 83-98 + [279] = 99, // 99-114 + [280] = 115, // 115-130 + [281] = 131, // 131-162 + [282] = 163, // 163-194 + [283] = 195, // 195-226 + [284] = 227, // 227-257 + [285] = 258, + }; + + return _literalLengths; + } + } + + /// + /// Extra bits for literal codes 257..285 + /// + private static Dictionary LiteralExtraBits + { + get + { + // If we have cached bit mappings, use those + if (_literalExtraBits != null) + return _literalExtraBits; + + // Otherwise, build it from scratch + _literalExtraBits = new Dictionary(); + + // Literal Value 257 - 264, 0 bits + for (int i = 257; i < 265; i++) + _literalExtraBits[i] = 0; + + // Literal Value 265 - 268, 1 bit + for (int i = 265; i < 269; i++) + _literalExtraBits[i] = 1; + + // Literal Value 269 - 272, 2 bits + for (int i = 269; i < 273; i++) + _literalExtraBits[i] = 2; + + // Literal Value 273 - 276, 3 bits + for (int i = 273; i < 277; i++) + _literalExtraBits[i] = 3; + + // Literal Value 277 - 280, 4 bits + for (int i = 277; i < 281; i++) + _literalExtraBits[i] = 4; + + // Literal Value 281 - 284, 5 bits + for (int i = 281; i < 285; i++) + _literalExtraBits[i] = 5; + + // Literal Value 285, 0 bits + _literalExtraBits[285] = 0; + + return _literalExtraBits; + } + } + + /// + /// Match offsets for distance codes 0..29 + /// + /// Each value here is the lower bound for lengths represented + public static readonly int[] DistanceOffsets = new int[30] + { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, + 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, + 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, + }; + + /// + /// Extra bits for distance codes 0..29 + /// + private static readonly int[] DistanceExtraBits = new int[30] + { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, + 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, + }; + + /// + /// The order of the bit length Huffman code lengths + /// + private static readonly int[] BitLengthOrder = new int[19] + { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15, + }; + + #endregion + + #region Instance Variables + + /// + /// Match lengths for literal codes 257..285 + /// + private static Dictionary _literalLengths = null; + + /// + /// Extra bits for literal codes 257..285 + /// + private static Dictionary _literalExtraBits = null; + + #endregion + + #region Parsing + + /// + /// Read the block header from the block data, if possible + /// + /// BitStream representing the block + /// Offset within the array to parse + /// Filled block header on success, null on error + private static Models.MicrosoftCabinet.MSZIP.BlockHeader AsBlockHeader(BitStream data) + { + // If the data is invalid + if (data == null) + return null; + + var header = new Models.MicrosoftCabinet.MSZIP.BlockHeader(); + + header.Signature = data.ReadBits(2 * 8).AsUInt16(); + if (header.Signature != 0x4B43) + return null; + + return header; + } + + /// + /// Read the deflate block header from the block data, if possible + /// + /// Byte array representing the block + /// Offset within the array to parse + /// Filled deflate block header on success, null on error + private static Models.MicrosoftCabinet.MSZIP.DeflateBlockHeader AsDeflateBlockHeader(BitStream data) + { + // If the data is invalid + if (data == null) + return null; + + var header = new Models.MicrosoftCabinet.MSZIP.DeflateBlockHeader(); + + header.BFINAL = data.ReadBits(1)[0]; + header.BTYPE = (Models.MicrosoftCabinet.DeflateCompressionType)data.ReadBits(2).AsByte(); + + return header; + } + + /// + /// Read the block header from the block data, if possible + /// + /// Byte array representing the block + /// Offset within the array to parse + /// Filled dynamic Huffman compressed block header on success, null on error + private static Models.MicrosoftCabinet.MSZIP.DynamicHuffmanCompressedBlockHeader AsDynamicHuffmanCompressedBlockHeader(BitStream data) + { + // If the data is invalid + if (data == null) + return null; + + var header = new Models.MicrosoftCabinet.MSZIP.DynamicHuffmanCompressedBlockHeader(); + + // # of Literal/Length codes - 257 + ushort HLIT = (ushort)(data.ReadBits(5).AsUInt16() + 257); + + // # of Distance codes - 1 + byte HDIST = (byte)(data.ReadBits(5).AsByte() + 1); + + // HCLEN, # of Code Length codes - 4 + byte HCLEN = (byte)(data.ReadBits(5).AsByte() + 4); + + // (HCLEN + 4) x 3 bits: code lengths for the code length + // alphabet given just above + // + // These code lengths are interpreted as 3-bit integers + // (0-7); as above, a code length of 0 means the + // corresponding symbol (literal/ length or distance code + // length) is not used. + int[] codeLengthAlphabet = new int[19]; + for (ulong i = 0; i < HCLEN; i++) + codeLengthAlphabet[BitLengthOrder[i]] = data.ReadBits(3).AsByte(); + + for (ulong i = HCLEN; i < 19; i++) + codeLengthAlphabet[BitLengthOrder[i]] = 0; + + // Code length Huffman code + int[] codeLengthHuffmanCode = CreateTable(codeLengthAlphabet); + + // HLIT + 257 code lengths for the literal/length alphabet, + // encoded using the code length Huffman code + header.LiteralLengths = BuildHuffmanTree(data, HLIT, codeLengthHuffmanCode); + + // HDIST + 1 code lengths for the distance alphabet, + // encoded using the code length Huffman code + header.DistanceCodes = BuildHuffmanTree(data, HDIST, codeLengthHuffmanCode); + + return header; + } + + /// + /// Read the block header from the block data, if possible + /// + /// Byte array representing the block + /// Offset within the array to parse + /// Filled non-compressed block header on success, null on error + private static Models.MicrosoftCabinet.MSZIP.NonCompressedBlockHeader AsNonCompressedBlockHeader(BitStream data) + { + // If the data is invalid + if (data == null) + return null; + + var header = new Models.MicrosoftCabinet.MSZIP.NonCompressedBlockHeader(); + + header.LEN = data.ReadBits(2 * 8).AsUInt16(); + header.NLEN = data.ReadBits(2 * 8).AsUInt16(); + // TODO: Confirm NLEN is 1's compliment of LEN + + return header; + } + + #endregion + + #region Decompression + + /// + /// The decoding algorithm for the actual data + /// + private static void MSZIPDecode(BitStream data) + { + // Create the output byte array + List decodedBytes = new List(); + + // Create the loop variable block + Models.MicrosoftCabinet.MSZIP.DeflateBlockHeader block; + + do + { + block = AsDeflateBlockHeader(data); + + // We should never get a reserved block + if (block.BTYPE == Models.MicrosoftCabinet.DeflateCompressionType.Reserved) + throw new Exception(); + + // If stored with no compression + if (block.BTYPE == Models.MicrosoftCabinet.DeflateCompressionType.NoCompression) + { + // Skip any remaining bits in current partially processed byte + data.DiscardBuffer(); + + // Read the block header + block.BlockData = AsNonCompressedBlockHeader(data); + + // Copy LEN bytes of data to output + var header = block.BlockData as Models.MicrosoftCabinet.MSZIP.NonCompressedBlockHeader; + ushort length = header.LEN; + decodedBytes.AddRange(data.ReadBytes(length)); + } + + // Otherwise + else + { + // If compressed with dynamic Huffman codes + // read representation of code trees + block.BlockData = block.BTYPE == Models.MicrosoftCabinet.DeflateCompressionType.DynamicHuffman + ? (Models.MicrosoftCabinet.MSZIP.IBlockDataHeader)AsDynamicHuffmanCompressedBlockHeader(data) + : (Models.MicrosoftCabinet.MSZIP.IBlockDataHeader)new Models.MicrosoftCabinet.MSZIP.FixedHuffmanCompressedBlockHeader(); + + var header = block.BlockData as Models.MicrosoftCabinet.MSZIP.CompressedBlockHeader; + + // 9 bits per entry, 288 max symbols + int[] literalDecodeTable = CreateTable(header.LiteralLengths); + + // 6 bits per entry, 32 max symbols + int[] distanceDecodeTable = CreateTable(header.DistanceCodes); + + // Loop until end of block code recognized + while (true) + { + // Decode literal/length value from input stream + int symbol = literalDecodeTable[data.ReadBits(9).AsUInt64()]; + + // Copy value (literal byte) to output stream + if (symbol < 256) + { + decodedBytes.Add((byte)symbol); + } + // End of block (256) + else if (symbol == 256) + { + break; + } + else + { + // Decode distance from input stream + ulong length = data.ReadBits(LiteralExtraBits[symbol]).AsUInt64(); + length += (ulong)LiteralLengths[symbol]; + + int code = distanceDecodeTable[length]; + + ulong distance = data.ReadBits(DistanceExtraBits[code]).AsUInt64(); + distance += (ulong)DistanceOffsets[code]; + + + // Move backwards distance bytes in the output + // stream, and copy length bytes from this + // position to the output stream. + } + } + } + } while (!block.BFINAL); + + /* + Note that a duplicated string reference may refer to a string + in a previous block; i.e., the backward distance may cross one + or more block boundaries. However a distance cannot refer past + the beginning of the output stream. (An application using a + preset dictionary might discard part of the output stream; a + distance can refer to that part of the output stream anyway) + Note also that the referenced string may overlap the current + position; for example, if the last 2 bytes decoded have values + X and Y, a string reference with + adds X,Y,X,Y,X to the output stream. + */ + } + + #endregion + + #region Helpers + + /// + /// The alphabet for code lengths is as follows + /// + private static int[] BuildHuffmanTree(BitStream data, ushort codeCount, int[] codeLengths) + { + // Setup the huffman tree + int[] tree = new int[codeCount]; + + // Setup the loop variables + int lastCode = 0, repeatLength = 0; + for (ulong i = 0; i < codeCount; i++) + { + int code = codeLengths[(int)data.ReadBits(7).AsUInt64()]; + + // Represent code lengths of 0 - 15 + if (code > 0 && code <= 15) + { + lastCode = code; + tree[i] = code; + } + + // Copy the previous code length 3 - 6 times. + // The next 2 bits indicate repeat length (0 = 3, ... , 3 = 6) + // Example: Codes 8, 16 (+2 bits 11), 16 (+2 bits 10) will expand to 12 code lengths of 8 (1 + 6 + 5) + else if (code == 16) + { + repeatLength = (int)data.ReadBits(2).AsUInt64(); + repeatLength += 2; + code = lastCode; + } + + // Repeat a code length of 0 for 3 - 10 times. + // (3 bits of length) + else if (code == 17) + { + repeatLength = (int)data.ReadBits(3).AsUInt64(); + repeatLength += 3; + code = 0; + } + + // Repeat a code length of 0 for 11 - 138 times + // (7 bits of length) + else if (code == 18) + { + repeatLength = (int)data.ReadBits(7).AsUInt64(); + repeatLength += 11; + code = 0; + } + + // Everything else + else + { + throw new ArgumentOutOfRangeException(); + } + + // If we had a repeat length + for (; repeatLength > 0; repeatLength--) + { + tree[i++] = code; + } + } + + return tree; + } + + /// + /// Given this rule, we can define the Huffman code for an alphabet + /// just by giving the bit lengths of the codes for each symbol of + /// the alphabet in order; this is sufficient to determine the + /// actual codes. In our example, the code is completely defined + /// by the sequence of bit lengths (2, 1, 3, 3). The following + /// algorithm generates the codes as integers, intended to be read + /// from most- to least-significant bit. The code lengths are + /// initially in tree[I].Len; the codes are produced in + /// tree[I].Code. + /// + private static int[] CreateTable(int[] lengths) + { + // Count the number of codes for each code length. Let + // bl_count[N] be the number of codes of length N, N >= 1. + var bl_count = new Dictionary(); + for (int i = 0; i < lengths.Length; i++) + { + if (!bl_count.ContainsKey(lengths[i])) + bl_count[lengths[i]] = 0; + + bl_count[lengths[i]]++; + } + + // Find the numerical value of the smallest code for each + // code length: + var next_code = new Dictionary(); + int code = 0; + bl_count[0] = 0; + for (int bits = 1; bits <= MAX_BITS; bits++) + { + code = (code + bl_count[bits - 1]) << 1; + next_code[bits] = code; + } + + // Assign numerical values to all codes, using consecutive + // values for all codes of the same length with the base + // values determined at step 2. Codes that are never used + // (which have a bit length of zero) must not be assigned a + // value. + int[] distances = new int[lengths.Length]; + for (int n = 0; n <= lengths.Length; n++) + { + int len = lengths[n]; + if (len != 0) + { + distances[n] = next_code[len]; + next_code[len]++; + } + } + + return distances; + } + + #endregion + + // TODO: Implement MSZIP decompression + + #endregion + + #region Quantum + + // TODO: Implement Quantum decompression + + #endregion + + #endregion + #region Folders /// diff --git a/BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs b/BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs deleted file mode 100644 index c42a4846..00000000 --- a/BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs +++ /dev/null @@ -1,539 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using BurnOutSharp.Models.MicrosoftCabinet; -using BurnOutSharp.Models.MicrosoftCabinet.MSZIP; -using BurnOutSharp.Utilities; - -/// -/// -namespace BurnOutSharp.FileType -{ - public static class MSZIPBlockBuilder - { - public static Block Create(byte[] data) - { - if (data == null) - return null; - - Block block = new Block(); - int offset = 0; - - block.Signature = data.ReadUInt16(ref offset); - if (block.Signature != 0x4B43) - return null; - - block.Data = data.ReadBytes(ref offset, data.Length - 2); - - return block; - } - } - - public static class MSZIPDeflateBlockBuilder - { - public static DeflateBlock Create(ulong data) - { - DeflateBlock deflateBlock = new DeflateBlock(); - - deflateBlock.BFINAL = (data & 0b100) != 0; - deflateBlock.BTYPE = (DeflateCompressionType)(data & 0b011); - - return deflateBlock; - } - } - - public static class MSZIPDynamicHuffmanCompressedBlockBuilder - { - public static DynamicHuffmanCompressedBlock Create(BitStream stream) - { - DynamicHuffmanCompressedBlock dynamicHuffmanCompressedBlock = new DynamicHuffmanCompressedBlock(); - - // # of Literal/Length codes - 257 - ulong HLIT = stream.ReadBits(5).AsUInt64() + 257; - - // # of Distance codes - 1 - ulong HDIST = stream.ReadBits(5).AsUInt64() + 1; - - // HCLEN, # of Code Length codes - 4 - ulong HCLEN = stream.ReadBits(5).AsUInt64() + 4; - - // (HCLEN + 4) x 3 bits: code lengths for the code length - // alphabet given just above - // - // These code lengths are interpreted as 3-bit integers - // (0-7); as above, a code length of 0 means the - // corresponding symbol (literal/ length or distance code - // length) is not used. - int[] codeLengthAlphabet = new int[19]; - for (ulong i = 0; i < HCLEN; i++) - codeLengthAlphabet[MSZIPDeflate.BitLengthOrder[i]] = (int)stream.ReadBits(3).AsUInt64(); - - for (ulong i = HCLEN; i < 19; i++) - codeLengthAlphabet[MSZIPDeflate.BitLengthOrder[i]] = 0; - - // Code length Huffman code - int[] codeLengthHuffmanCode = MSZIPDeflate.CreateTable(codeLengthAlphabet); - - // HLIT + 257 code lengths for the literal/length alphabet, - // encoded using the code length Huffman code - dynamicHuffmanCompressedBlock.LiteralLengths = BuildHuffmanTree(stream, HLIT, codeLengthHuffmanCode); - - // HDIST + 1 code lengths for the distance alphabet, - // encoded using the code length Huffman code - dynamicHuffmanCompressedBlock.DistanceCodes = BuildHuffmanTree(stream, HDIST, codeLengthHuffmanCode); - - return dynamicHuffmanCompressedBlock; - } - - /// - /// The alphabet for code lengths is as follows - /// - private static int[] BuildHuffmanTree(BitStream stream, ulong codeCount, int[] codeLengths) - { - // Setup the huffman tree - int[] tree = new int[codeCount]; - - // Setup the loop variables - int lastCode = 0, repeatLength = 0; - for (ulong i = 0; i < codeCount; i++) - { - int code = codeLengths[(int)stream.ReadBits(7).AsUInt64()]; - - // Represent code lengths of 0 - 15 - if (code > 0 && code <= 15) - { - lastCode = code; - tree[i] = code; - } - - // Copy the previous code length 3 - 6 times. - // The next 2 bits indicate repeat length (0 = 3, ... , 3 = 6) - // Example: Codes 8, 16 (+2 bits 11), 16 (+2 bits 10) will expand to 12 code lengths of 8 (1 + 6 + 5) - else if (code == 16) - { - repeatLength = (int)stream.ReadBits(2).AsUInt64(); - repeatLength += 2; - code = lastCode; - } - - // Repeat a code length of 0 for 3 - 10 times. - // (3 bits of length) - else if (code == 17) - { - repeatLength = (int)stream.ReadBits(3).AsUInt64(); - repeatLength += 3; - code = 0; - } - - // Repeat a code length of 0 for 11 - 138 times - // (7 bits of length) - else if (code == 18) - { - repeatLength = (int)stream.ReadBits(7).AsUInt64(); - repeatLength += 11; - code = 0; - } - - // Everything else - else - { - throw new ArgumentOutOfRangeException(); - } - - // If we had a repeat length - for (; repeatLength > 0; repeatLength--) - { - tree[i++] = code; - } - } - - return tree; - } - } - - public static class MSZIPNonCompressedBlockBuilder - { - public static NonCompressedBlock Create(byte[] data) - { - // If we have invalid header data - if (data == null || data.Length < 4) - throw new ArgumentException(); - - NonCompressedBlock nonCompressedBlock = new NonCompressedBlock(); - int offset = 0; - - nonCompressedBlock.LEN = data.ReadUInt16(ref offset); - nonCompressedBlock.NLEN = data.ReadUInt16(ref offset); - // TODO: Confirm NLEN is 1's compliment of LEN - - return nonCompressedBlock; - } - } - - #region Deflate Implementation - - /// - public class MSZIPDeflate - { - #region Constants - - /// - /// Maximum Huffman code bit count - /// - public const int MAX_BITS = 16; - - #endregion - - #region Properties - - /// - /// Match lengths for literal codes 257..285 - /// - /// Each value here is the lower bound for lengths represented - public static Dictionary LiteralLengths - { - get - { - // If we have cached length mappings, use those - if (_literalLengths != null) - return _literalLengths; - - // Otherwise, build it from scratch - _literalLengths = new Dictionary - { - [257] = 3, - [258] = 4, - [259] = 5, - [260] = 6, - [261] = 7, - [262] = 8, - [263] = 9, - [264] = 10, - [265] = 11, // 11,12 - [266] = 13, // 13,14 - [267] = 15, // 15,16 - [268] = 17, // 17,18 - [269] = 19, // 19-22 - [270] = 23, // 23-26 - [271] = 27, // 27-30 - [272] = 31, // 31-34 - [273] = 35, // 35-42 - [274] = 43, // 43-50 - [275] = 51, // 51-58 - [276] = 59, // 59-66 - [277] = 67, // 67-82 - [278] = 83, // 83-98 - [279] = 99, // 99-114 - [280] = 115, // 115-130 - [281] = 131, // 131-162 - [282] = 163, // 163-194 - [283] = 195, // 195-226 - [284] = 227, // 227-257 - [285] = 258, - }; - - return _literalLengths; - } - } - - /// - /// Extra bits for literal codes 257..285 - /// - public static Dictionary LiteralExtraBits - { - get - { - // If we have cached bit mappings, use those - if (_literalExtraBits != null) - return _literalExtraBits; - - // Otherwise, build it from scratch - _literalExtraBits = new Dictionary(); - - // Literal Value 257 - 264, 0 bits - for (int i = 257; i < 265; i++) - _literalExtraBits[i] = 0; - - // Literal Value 265 - 268, 1 bit - for (int i = 265; i < 269; i++) - _literalExtraBits[i] = 1; - - // Literal Value 269 - 272, 2 bits - for (int i = 269; i < 273; i++) - _literalExtraBits[i] = 2; - - // Literal Value 273 - 276, 3 bits - for (int i = 273; i < 277; i++) - _literalExtraBits[i] = 3; - - // Literal Value 277 - 280, 4 bits - for (int i = 277; i < 281; i++) - _literalExtraBits[i] = 4; - - // Literal Value 281 - 284, 5 bits - for (int i = 281; i < 285; i++) - _literalExtraBits[i] = 5; - - // Literal Value 285, 0 bits - _literalExtraBits[285] = 0; - - return _literalExtraBits; - } - } - - /// - /// Match offsets for distance codes 0..29 - /// - /// Each value here is the lower bound for lengths represented - public static readonly int[] DistanceOffsets = new int[30] - { - 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, - 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, - 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, - }; - - /// - /// Extra bits for distance codes 0..29 - /// - public static readonly int[] DistanceExtraBits = new int[30] - { - 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, - 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, - 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, - }; - - /// - /// The order of the bit length Huffman code lengths - /// - public static readonly int[] BitLengthOrder = new int[19] - { - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15, - }; - - #endregion - - #region Instance Variables - - /// - /// Match lengths for literal codes 257..285 - /// - private static Dictionary _literalLengths = null; - - /// - /// Extra bits for literal codes 257..285 - /// - private static Dictionary _literalExtraBits = null; - - #endregion - - /// - /// The decoding algorithm for the actual data - /// - public static void Decode(BitStream data) - { - // Create the output byte array - List decodedBytes = new List(); - - // Create the loop variable block - DeflateBlock block; - - do - { - ulong header = data.ReadBits(3).AsUInt64(); - block = MSZIPDeflateBlockBuilder.Create(header); - - // We should never get a reserved block - if (block.BTYPE == DeflateCompressionType.Reserved) - throw new Exception(); - - // If stored with no compression - if (block.BTYPE == DeflateCompressionType.NoCompression) - { - // Skip any remaining bits in current partially processed byte - data.DiscardBuffer(); - - // Read LEN and NLEN - byte[] nonCompressedHeader = data.ReadBytes(4); - block.BlockData = MSZIPNonCompressedBlockBuilder.Create(nonCompressedHeader); - - // Copy LEN bytes of data to output - ushort length = ((NonCompressedBlock)block.BlockData).LEN; - ((NonCompressedBlock)block.BlockData).Data = data.ReadBytes(length); - decodedBytes.AddRange(((NonCompressedBlock)block.BlockData).Data); - } - - // Otherwise - else - { - // If compressed with dynamic Huffman codes - // read representation of code trees - block.BlockData = block.BTYPE == DeflateCompressionType.DynamicHuffman - ? (IBlockData)MSZIPDynamicHuffmanCompressedBlockBuilder.Create(data) - : (IBlockData)new FixedHuffmanCompressedBlock(); - - var compressedBlock = (block.BlockData as CompressedBlock); - - // 9 bits per entry, 288 max symbols - int[] literalDecodeTable = CreateTable(compressedBlock.LiteralLengths); - - // 6 bits per entry, 32 max symbols - int[] distanceDecodeTable = CreateTable(compressedBlock.DistanceCodes); - - // Loop until end of block code recognized - while (true) - { - // Decode literal/length value from input stream - int symbol = literalDecodeTable[data.ReadBits(9).AsUInt64()]; - - // Copy value (literal byte) to output stream - if (symbol < 256) - { - decodedBytes.Add((byte)symbol); - } - // End of block (256) - else if (symbol == 256) - { - break; - } - else - { - // Decode distance from input stream - ulong length = data.ReadBits(LiteralExtraBits[symbol]).AsUInt64(); - length += (ulong)LiteralLengths[symbol]; - - int code = distanceDecodeTable[length]; - - ulong distance = data.ReadBits(DistanceExtraBits[code]).AsUInt64(); - distance += (ulong)DistanceOffsets[code]; - - - // Move backwards distance bytes in the output - // stream, and copy length bytes from this - // position to the output stream. - } - } - } - } while (!block.BFINAL); - - /* - Note that a duplicated string reference may refer to a string - in a previous block; i.e., the backward distance may cross one - or more block boundaries. However a distance cannot refer past - the beginning of the output stream. (An application using a - preset dictionary might discard part of the output stream; a - distance can refer to that part of the output stream anyway) - Note also that the referenced string may overlap the current - position; for example, if the last 2 bytes decoded have values - X and Y, a string reference with - adds X,Y,X,Y,X to the output stream. - */ - } - - /// - /// Given this rule, we can define the Huffman code for an alphabet - /// just by giving the bit lengths of the codes for each symbol of - /// the alphabet in order; this is sufficient to determine the - /// actual codes. In our example, the code is completely defined - /// by the sequence of bit lengths (2, 1, 3, 3). The following - /// algorithm generates the codes as integers, intended to be read - /// from most- to least-significant bit. The code lengths are - /// initially in tree[I].Len; the codes are produced in - /// tree[I].Code. - /// - public static void CreateTable(CompressedBlock tree) - { - // Count the number of codes for each code length. Let - // bl_count[N] be the number of codes of length N, N >= 1. - var bl_count = new Dictionary(); - for (int i = 0; i < tree.LiteralLengths.Length; i++) - { - if (!bl_count.ContainsKey(tree.LiteralLengths[i])) - bl_count[tree.LiteralLengths[i]] = 0; - - bl_count[tree.LiteralLengths[i]]++; - } - - // Find the numerical value of the smallest code for each - // code length: - var next_code = new Dictionary(); - int code = 0; - bl_count[0] = 0; - for (int bits = 1; bits <= MAX_BITS; bits++) - { - code = (code + bl_count[bits - 1]) << 1; - next_code[bits] = code; - } - - // Assign numerical values to all codes, using consecutive - // values for all codes of the same length with the base - // values determined at step 2. Codes that are never used - // (which have a bit length of zero) must not be assigned a - // value. - for (int n = 0; n <= tree.LiteralLengths.Length; n++) - { - int len = tree.LiteralLengths[n]; - if (len != 0) - { - tree.DistanceCodes[n] = next_code[len]; - next_code[len]++; - } - } - } - - /// - /// Given this rule, we can define the Huffman code for an alphabet - /// just by giving the bit lengths of the codes for each symbol of - /// the alphabet in order; this is sufficient to determine the - /// actual codes. In our example, the code is completely defined - /// by the sequence of bit lengths (2, 1, 3, 3). The following - /// algorithm generates the codes as integers, intended to be read - /// from most- to least-significant bit. The code lengths are - /// initially in tree[I].Len; the codes are produced in - /// tree[I].Code. - /// - public static int[] CreateTable(int[] lengths) - { - // Count the number of codes for each code length. Let - // bl_count[N] be the number of codes of length N, N >= 1. - var bl_count = new Dictionary(); - for (int i = 0; i < lengths.Length; i++) - { - if (!bl_count.ContainsKey(lengths[i])) - bl_count[lengths[i]] = 0; - - bl_count[lengths[i]]++; - } - - // Find the numerical value of the smallest code for each - // code length: - var next_code = new Dictionary(); - int code = 0; - bl_count[0] = 0; - for (int bits = 1; bits <= MAX_BITS; bits++) - { - code = (code + bl_count[bits - 1]) << 1; - next_code[bits] = code; - } - - // Assign numerical values to all codes, using consecutive - // values for all codes of the same length with the base - // values determined at step 2. Codes that are never used - // (which have a bit length of zero) must not be assigned a - // value. - int[] distances = new int[lengths.Length]; - for (int n = 0; n <= lengths.Length; n++) - { - int len = lengths[n]; - if (len != 0) - { - distances[n] = next_code[len]; - next_code[len]++; - } - } - - return distances; - } - } - - #endregion -}