| import 'dart:typed_data'; |
| import '../util/input_stream.dart'; |
| import '../util/output_stream.dart'; |
| import 'huffman_table.dart'; |
| |
| class Inflate { |
| late InputStreamBase input; |
| bool inputSet = false; |
| dynamic output; |
| |
| Inflate(List<int> bytes, [int? uncompressedSize]) |
| : input = InputStream(bytes), |
| output = OutputStream(size: uncompressedSize) { |
| inputSet = true; |
| _inflate(); |
| } |
| |
| Inflate.buffer(this.input, [int? uncompressedSize]) |
| : output = OutputStream(size: uncompressedSize) { |
| inputSet = true; |
| _inflate(); |
| } |
| |
| Inflate.stream([InputStreamBase? input, dynamic outputStream]) |
| : output = outputStream ?? OutputStream() { |
| if (input != null) { |
| this.input = input; |
| inputSet = true; |
| } |
| _inflate(); |
| } |
| |
| void streamInput(List<int> bytes) { |
| if (inputSet && input is InputStream) { |
| var i = input as InputStream; |
| i.offset = _blockPos; |
| final inputLen = input.length; |
| final newLen = inputLen + bytes.length; |
| final newBytes = Uint8List(newLen); |
| newBytes.setRange(0, inputLen, i.buffer, i.offset); |
| newBytes.setRange(inputLen, newLen, bytes, 0); |
| |
| input = InputStream(newBytes); |
| } else { |
| input = InputStream(bytes); |
| } |
| inputSet = true; |
| } |
| |
| List<int>? inflateNext() { |
| _bitBuffer = 0; |
| _bitBufferLen = 0; |
| if (output is OutputStream) { |
| output.clear(); |
| } |
| if (!inputSet || input.isEOS) { |
| return null; |
| } |
| |
| try { |
| if (input is InputStream) { |
| var i = input as InputStream; |
| _blockPos = i.offset; |
| } |
| _parseBlock(); |
| // If it didn't finish reading the block, it will have thrown an exception |
| _blockPos = 0; |
| } catch (e) { |
| return null; |
| } |
| |
| if (output is OutputStream) { |
| return output.getBytes() as List<int>; |
| } |
| return null; |
| } |
| |
| /// Get the decompressed data. |
| List<int> getBytes() { |
| return output.getBytes() as List<int>; |
| } |
| |
| void _inflate() { |
| _bitBuffer = 0; |
| _bitBufferLen = 0; |
| if (!inputSet) { |
| return; |
| } |
| |
| while (!input.isEOS) { |
| if (!_parseBlock()) { |
| break; |
| } |
| } |
| } |
| |
| /// Parse deflated block. Returns true if there is more to read, false |
| /// if we're done. |
| bool _parseBlock() { |
| if (input.isEOS) { |
| return false; |
| } |
| |
| // Each block has a 3-bit header |
| final blockHeader = _readBits(3); |
| |
| // BFINAL (is this the final block)? |
| final finalBlock = (blockHeader & 0x1) != 0; |
| |
| // BTYPE (the type of block) |
| final blockType = blockHeader >> 1; |
| switch (blockType) { |
| case 0: // Uncompressed block |
| if (_parseUncompressedBlock() == -1) { |
| return false; |
| } |
| break; |
| case 1: // Fixed huffman block |
| if (_parseFixedHuffmanBlock() == -1) { |
| return false; |
| } |
| break; |
| case 2: // Dynamic huffman block |
| if (_parseDynamicHuffmanBlock() == -1) { |
| return false; |
| } |
| break; |
| default: |
| return false; |
| } |
| |
| // Continue while not the final block |
| return !finalBlock; |
| } |
| |
| /// Read a number of bits from the input stream. |
| int _readBits(int length) { |
| if (length == 0) { |
| return 0; |
| } |
| |
| // not enough buffer |
| while (_bitBufferLen < length) { |
| if (input.isEOS) { |
| return -1; |
| } |
| |
| // input byte |
| final octet = input.readByte(); |
| |
| // concat octet |
| _bitBuffer |= octet << _bitBufferLen; |
| _bitBufferLen += 8; |
| } |
| |
| // output byte |
| final octet = _bitBuffer & ((1 << length) - 1); |
| _bitBuffer >>= length; |
| _bitBufferLen -= length; |
| |
| return octet; |
| } |
| |
| /// Read huffman code using [table]. |
| int _readCodeByTable(HuffmanTable table) { |
| final codeTable = table.table; |
| final maxCodeLength = table.maxCodeLength; |
| |
| // Not enough buffer |
| while (_bitBufferLen < maxCodeLength) { |
| if (input.isEOS) { |
| return -1; |
| } |
| |
| final octet = input.readByte(); |
| |
| _bitBuffer |= octet << _bitBufferLen; |
| _bitBufferLen += 8; |
| } |
| |
| // read max length |
| final codeWithLength = codeTable[_bitBuffer & ((1 << maxCodeLength) - 1)]; |
| final codeLength = codeWithLength >> 16; |
| |
| _bitBuffer >>= codeLength; |
| _bitBufferLen -= codeLength; |
| |
| return codeWithLength & 0xffff; |
| } |
| |
| int _parseUncompressedBlock() { |
| // skip buffered header bits |
| _bitBuffer = 0; |
| _bitBufferLen = 0; |
| |
| final len = _readBits(16); |
| final nlen = _readBits(16) ^ 0xffff; |
| |
| // Make sure the block size checksum is valid. |
| if (len != 0 && len != nlen) { |
| return -1; |
| } |
| |
| // check size |
| if (len > input.length) { |
| return -1; |
| } |
| |
| output.writeInputStream(input.readBytes(len)); |
| return 0; |
| } |
| |
| int _parseFixedHuffmanBlock() { |
| return _decodeHuffman(_fixedLiteralLengthTable, _fixedDistanceTable); |
| } |
| |
| int _parseDynamicHuffmanBlock() { |
| // number of literal and length codes. |
| var numLitLengthCodes = _readBits(5); |
| if (numLitLengthCodes == -1) { |
| return -1; |
| } |
| numLitLengthCodes += 257; |
| if (numLitLengthCodes > 288) { |
| return - 1; |
| } |
| // number of distance codes. |
| var numDistanceCodes = _readBits(5); |
| if (numDistanceCodes == -1) { |
| return -1; |
| } |
| numDistanceCodes += 1; |
| if (numDistanceCodes > 32) { |
| return -1; |
| } |
| // number of code lengths. |
| var numCodeLengths = _readBits(4); |
| if (numCodeLengths == -1) { |
| return -1; |
| } |
| numCodeLengths += 4; |
| if (numCodeLengths > 19) { |
| return -1; |
| } |
| |
| // decode code lengths |
| final codeLengths = Uint8List(_order.length); |
| for (var i = 0; i < numCodeLengths; ++i) { |
| final len = _readBits(3); |
| if (len == -1) { |
| return -1; |
| } |
| codeLengths[_order[i]] = len; |
| } |
| |
| final codeLengthsTable = HuffmanTable(codeLengths); |
| |
| final litLenDistLengths = Uint8List(numLitLengthCodes + numDistanceCodes); |
| |
| // literal and length code |
| final litlenLengths = Uint8List.view(litLenDistLengths.buffer, 0, numLitLengthCodes); |
| |
| // distance code |
| final distLengths = Uint8List.view(litLenDistLengths.buffer, numLitLengthCodes, numDistanceCodes); |
| |
| if (_decode(litLenDistLengths.length, codeLengthsTable, litLenDistLengths) == -1) { |
| return -1; |
| } |
| |
| return _decodeHuffman(HuffmanTable(litlenLengths), HuffmanTable(distLengths)); |
| } |
| |
| int _decodeHuffman(HuffmanTable litlen, HuffmanTable dist) { |
| while (true) { |
| final code = _readCodeByTable(litlen); |
| if (code < 0 || code > 285) { |
| return -1; |
| } |
| |
| // 256 - End of Huffman block |
| if (code == 256) { |
| break; |
| } |
| |
| // [0, 255] - Literal |
| if (code < 256) { |
| output.writeByte(code & 0xff); |
| continue; |
| } |
| |
| // [257, 285] Dictionary Lookup |
| // length code |
| final ti = code - 257; |
| |
| var codeLength = |
| _lengthCodeTable[ti] + _readBits(_lengthExtraTable[ti]); |
| |
| // distance code |
| final distCode = _readCodeByTable(dist); |
| if (distCode < 0 || distCode > 29) { |
| return -1; |
| } |
| final distance = |
| _distCodeTable[distCode] + _readBits(_distExtraTable[distCode]); |
| |
| // lz77 decode |
| while (codeLength > distance) { |
| output.writeBytes(output.subset(-distance)); |
| codeLength -= distance; |
| } |
| |
| if (codeLength == distance) { |
| output.writeBytes(output.subset(-distance)); |
| } else { |
| output.writeBytes(output.subset(-distance, codeLength - distance)); |
| } |
| } |
| |
| while (_bitBufferLen >= 8) { |
| _bitBufferLen -= 8; |
| input.rewind(1); |
| } |
| |
| return 0; |
| } |
| |
| int _decode(int num, HuffmanTable table, List<int> codeLengths) { |
| var prev = 0; |
| var i = 0; |
| while (i < num) { |
| final code = _readCodeByTable(table); |
| if (code == -1) { |
| return -1; |
| } |
| switch (code) { |
| case 16: |
| // Repeat last code |
| var repeat = _readBits(2); |
| if (repeat == -1) { |
| return -1; |
| } |
| repeat += 3; |
| while (repeat-- > 0) { |
| codeLengths[i++] = prev; |
| } |
| break; |
| case 17: |
| // Repeat 0 |
| var repeat = _readBits(3); |
| if (repeat == -1) { |
| return -1; |
| } |
| repeat += 3; |
| while (repeat-- > 0) { |
| codeLengths[i++] = 0; |
| } |
| prev = 0; |
| break; |
| case 18: |
| // Repeat lots of 0s. |
| var repeat = _readBits(7); |
| if (repeat == -1) { |
| return - 1; |
| } |
| repeat += 11; |
| while (repeat-- > 0) { |
| codeLengths[i++] = 0; |
| } |
| prev = 0; |
| break; |
| default: // [0, 15] |
| // Literal bitlength for this code. |
| if (code < 0 || code > 15) { |
| return -1; |
| } |
| codeLengths[i++] = code; |
| prev = code; |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int _bitBuffer = 0; |
| int _bitBufferLen = 0; |
| int _blockPos = 0; |
| |
| /// Fixed huffman length code table |
| static const List<int> _fixedLiteralLengths = [ |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 9, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 7, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8, |
| 8 |
| ]; |
| final HuffmanTable _fixedLiteralLengthTable = |
| HuffmanTable(_fixedLiteralLengths); |
| |
| /// Fixed huffman distance code table |
| static const List<int> _fixedDistanceTableData = [ |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5, |
| 5 |
| ]; |
| final HuffmanTable _fixedDistanceTable = HuffmanTable(_fixedDistanceTableData); |
| |
| /// Huffman order |
| static const List<int> _order = [ |
| 16, |
| 17, |
| 18, |
| 0, |
| 8, |
| 7, |
| 9, |
| 6, |
| 10, |
| 5, |
| 11, |
| 4, |
| 12, |
| 3, |
| 13, |
| 2, |
| 14, |
| 1, |
| 15 |
| ]; |
| |
| /// Huffman length code table. |
| static const List<int> _lengthCodeTable = [ |
| 3, |
| 4, |
| 5, |
| 6, |
| 7, |
| 8, |
| 9, |
| 10, |
| 11, |
| 13, |
| 15, |
| 17, |
| 19, |
| 23, |
| 27, |
| 31, |
| 35, |
| 43, |
| 51, |
| 59, |
| 67, |
| 83, |
| 99, |
| 115, |
| 131, |
| 163, |
| 195, |
| 227, |
| 258 |
| ]; |
| |
| /// Huffman length extra-bits table. |
| static const List<int> _lengthExtraTable = [ |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 1, |
| 1, |
| 1, |
| 1, |
| 2, |
| 2, |
| 2, |
| 2, |
| 3, |
| 3, |
| 3, |
| 3, |
| 4, |
| 4, |
| 4, |
| 4, |
| 5, |
| 5, |
| 5, |
| 5, |
| 0, |
| 0, |
| 0 |
| ]; |
| |
| /// Huffman dist code table. |
| static const List<int> _distCodeTable = [ |
| 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 |
| ]; |
| |
| /// Huffman dist extra-bits table. |
| static const List<int> _distExtraTable = [ |
| 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 |
| ]; |
| } |