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 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 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? 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; } return null; } /// Get the decompressed data. List getBytes() { return output.getBytes() as List; } 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 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 _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 _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 _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 _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 _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 _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 _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 ]; }