From ec40e759a9b994bb5fb3bc4477afb0228c22c2f5 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 21 Sep 2023 22:59:46 -0400 Subject: [PATCH] Start clean MSZIP implementation (nw) --- MSZIP/Decompressor.cs | 184 ++++++++++++++++++++++++++++++++++++++++++ MSZIP/HuffmanNode.cs | 9 +++ 2 files changed, 193 insertions(+) create mode 100644 MSZIP/Decompressor.cs create mode 100644 MSZIP/HuffmanNode.cs diff --git a/MSZIP/Decompressor.cs b/MSZIP/Decompressor.cs new file mode 100644 index 0000000..e2a7622 --- /dev/null +++ b/MSZIP/Decompressor.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.IO; +using SabreTools.Models.Compression.MSZIP; + +namespace SabreTools.Compression.MSZIP +{ + public class Decompressor + { + /// + /// Decompress a stream into a byte array + /// + /// Stream to decompress + /// Byte array containing the decompressed data on success, null on error + /// + public static byte[] Decompress(Stream input) + { + // If we have an invalid stream + if (input == null || !input.CanRead || !input.CanSeek) + return null; + + // Wrap the stream in a BitStream + var bitStream = new BitStream(input); + + // Try to read the header + var blockHeader = ReadBlockHeader(bitStream); + if (blockHeader.Signature != 0x4B43) + return null; + + // Loop and read the internal blocks + var bytes = new List(); + while (true) + { + // Try to read the deflate block header + var deflateBlockHeader = ReadDeflateBlockHeader(bitStream); + if (deflateBlockHeader.BTYPE == CompressionType.Reserved) + break; + + // If stored with no compression + if (deflateBlockHeader.BTYPE == CompressionType.NoCompression) + { + // Skip any remaining bits in current partially processed byte + bitStream.Discard(); + + // Read LEN and NLEN + var nonCompressedBlockHeader = ReadNonCompressedBlockHeader(bitStream); + if (nonCompressedBlockHeader.LEN == 0 && nonCompressedBlockHeader.NLEN == 0) + break; + + // Copy LEN bytes of data to output + byte[] uncompressed = bitStream.ReadBytes(nonCompressedBlockHeader.LEN); + bytes.AddRange(uncompressed); + } + + // Otherwise + else + { + // If compressed with dynamic Huffman codes + if (deflateBlockHeader.BTYPE == CompressionType.DynamicHuffman) + { + // Read representation of code trees + } + + // Loop (until end of block code recognized) + while (true) + { + /* + decode literal/length value from input stream + if value < 256 + copy value (literal byte) to output stream + otherwise + if value = end of block (256) + break from loop + otherwise (value = 257..285) + decode distance from input stream + + move backwards distance bytes in the output + stream, and copy length bytes from this + position to the output stream. + */ + } + } + } + + throw new NotImplementedException(); + } + + /// + /// Read a BlockHeader from the input stream + /// + private static BlockHeader ReadBlockHeader(BitStream input) + { + var header = new BlockHeader(); + header.Signature = input.ReadUInt16() ?? 0; + return header; + } + + /// + /// Read a DeflateBlockHeader from the input stream + /// + private static DeflateBlockHeader ReadDeflateBlockHeader(BitStream input) + { + var header = new DeflateBlockHeader(); + header.BFINAL = input.ReadBitLSB() != 0x01; + byte btype = input.ReadBitLSB() ?? 0x01; + btype <<= 1; + btype |= input.ReadBitLSB() ?? 0x01; + header.BTYPE = (CompressionType)btype; + return header; + } + + /// + /// Read a NonCompressedBlockHeader from the input stream + /// + private static NonCompressedBlockHeader ReadNonCompressedBlockHeader(BitStream input) + { + var header = new NonCompressedBlockHeader(); + header.LEN = input.ReadUInt16() ?? 0; + header.NLEN = input.ReadUInt16() ?? 0; + return header; + } + + /// + /// The Huffman codes for the two alphabets are fixed, and are not + /// represented explicitly in the data. + /// + private int[] CreateFixedLiteralLengthLengths() + { + int[] lengths = new int[288]; + for (int i = 0; i <= 143; i++) + { + lengths[i] = 8; + } + for (int i = 144; i <= 255; i++) + { + lengths[i] = 9; + } + for (int i = 256; i <= 279; i++) + { + lengths[i] = 7; + } + for (int i = 280; i <= 287; i++) + { + lengths[i] = 8; + } + return lengths; + } + + private void CreateCodeValues(int[] lengths, int max_bits, int max_code, HuffmanNode[] tree) + { + // Count the number of codes for each code length + int[] bl_count = new int[max_bits]; + for (int i = 0; i < lengths.Length; i++) + { + int length = lengths[i]; + bl_count[length]++; + } + + // Find the numerical value of the smalles code for each code length + int[] next_code = new int[max_bits + 1]; + 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 <= max_code; n++) + { + int len = tree[n].Len; + if (len != 0) + { + tree[n].Code = next_code[len]; + next_code[len]++; + } + } + } + } +} \ No newline at end of file diff --git a/MSZIP/HuffmanNode.cs b/MSZIP/HuffmanNode.cs new file mode 100644 index 0000000..92543f7 --- /dev/null +++ b/MSZIP/HuffmanNode.cs @@ -0,0 +1,9 @@ +namespace SabreTools.Compression.MSZIP +{ + public class HuffmanNode + { + public int Len { get; set; } + + public int Code { get; set; } + } +} \ No newline at end of file