/** * Uncompress bytes. * * @param bool $reset Reset the LXZ state? * @param BitReader $reader The reader that provides the data. * @param int $numberOfBytes The number of decompressed bytes to retrieve. * * @throws Exception Throws an Exception in case of errors. * * @return string */ public function inflate($reset, BitReader $reader, $numberOfBytes) { if ($reset) { $this->r2 = $this->r1 = $this->r0 = 1; $this->headerRead = false; $this->framesRead = 0; $this->remainingInBlock = 0; $this->blockType = null; $this->intelCurrentPosition = 0; $this->intelStarted = false; $this->windowPosition = 0; $this->mainTree->clear(); $this->lengthTree->clear(); } if ($this->headerRead === false) { if ($reader->readLE(1) > 0) { $this->intelFilesize = $reader->readLE(16) << 16 | $reader->readLE(16); } $this->headerRead = true; } $togo = $numberOfBytes; while ($togo > 0) { if ($this->remainingInBlock === 0) { if ($this->blockType === static::BLOCKTYPE_UNCOMPRESSED) { if (($this->blockLength & 1) !== 0) { $reader->skip(1); } } $this->blockType = $reader->readLE(3); $this->remainingInBlock = $this->blockLength = $reader->readLE(16) << 8 | $reader->readLE(8); switch ($this->blockType) { case static::BLOCKTYPE_ALIGNED: $this->alignedTree->readAlignLengthTable($reader); $this->alignedTree->makeSymbolTable(); /* @noinspection PhpMissingBreakStatementInspection */ /* @noinspection PhpMissingBreakStatementInspection */ case static::BLOCKTYPE_VERBATIM: $this->mainTree->readLengthTable($reader, 0, static::NUM_CHARS); $this->mainTree->readLengthTable($reader, static::NUM_CHARS, $this->mainElements); $this->mainTree->makeSymbolTable(); if ($this->mainTree->isIntel()) { $this->intelStarted = true; } $this->lengthTree->readLengthTable($reader, 0, static::NUM_SECONDARY_LENGTHS); $this->lengthTree->makeSymbolTable(); break; case static::BLOCKTYPE_UNCOMPRESSED: $this->intelStarted = true; if ($reader->ensure(16) > 16) { $reader->skip(-2); } $this->r0 = $reader->readUInt32(); $this->r1 = $reader->readUInt32(); $this->r2 = $reader->readUInt32(); break; default: throw new Exception('Unexpected block type ' . $this->blockType); } } while (($thisRun = $this->remainingInBlock) > 0 && $togo > 0) { if ($thisRun > $togo) { $thisRun = $togo; } $togo -= $thisRun; $this->remainingInBlock -= $thisRun; $this->windowPosition %= $this->windowSize; if ($this->windowPosition + $thisRun > $this->windowSize) { throw new Exception('Trying to read more that window size bytes'); } if ($this->blockType === static::BLOCKTYPE_UNCOMPRESSED) { $this->window = $reader->readFully($this->window, $this->windowPosition, $thisRun); $this->windowPosition += $thisRun; } else { while ($thisRun > 0) { $mainElement = $this->mainTree->readHuffmanSymbol($reader); if ($mainElement < static::NUM_CHARS) { $this->window[$this->windowPosition++] = $mainElement; --$thisRun; } else { $mainElement -= static::NUM_CHARS; $matchLength = $mainElement & static::NUM_PRIMARY_LENGTHS; if ($matchLength === static::NUM_PRIMARY_LENGTHS) { $matchLength += $this->lengthTree->readHuffmanSymbol($reader); } $matchLength += static::MIN_MATCH; $matchOffset = $mainElement >> 3; switch ($matchOffset) { case 0: $matchOffset = $this->r0; break; case 1: $matchOffset = $this->r1; $this->r1 = $this->r0; $this->r0 = $matchOffset; break; case 2: $matchOffset = $this->r2; $this->r2 = $this->r0; $this->r0 = $matchOffset; break; default: switch ($this->blockType) { case static::BLOCKTYPE_VERBATIM: if ($matchOffset !== 3) { $extra = static::$EXTRA_BITS[$matchOffset]; $matchOffset = static::$POSITION_BASE[$matchOffset] - 2 + $reader->readLE($extra); } else { $matchOffset = 1; } break; case static::BLOCKTYPE_ALIGNED: $extra = static::$EXTRA_BITS[$matchOffset]; $matchOffset = static::$POSITION_BASE[$matchOffset] - 2; switch ($extra) { case 0: $matchOffset = 1; break; case 1: case 2: // verbatim bits only $matchOffset += $reader->readLE($extra); break; case 3: // aligned bits only $matchOffset += $this->alignedTree->readHuffmanSymbol($reader); break; default: // verbatim and aligned bits $extra -= 3; $matchOffset += $reader->readLE($extra) << 3; $matchOffset += $this->alignedTree->readHuffmanSymbol($reader); break; } break; default: throw new Exception('Unexpected block type ' + $this->blockType); } $this->r2 = $this->r1; $this->r1 = $this->r0; $this->r0 = $matchOffset; break; } $runSrc = 0; $runDest = $this->windowPosition; $thisRun -= $matchLength; // copy any wrapped around source data if ($this->windowPosition >= $matchOffset) { // no wrap $runSrc = $runDest - $matchOffset; } else { // wrap around $runSrc = $runDest + ($this->windowSize - $matchOffset); $copyLength = $matchOffset - $this->windowPosition; if ($copyLength < $matchLength) { $matchLength -= $copyLength; $this->windowPosition += $copyLength; while ($copyLength-- > 0) { $this->window[$runDest++] = $this->window[$runSrc++]; } $runSrc = 0; } } $this->windowPosition += $matchLength; // copy match data - no worries about destination wraps while ($matchLength-- > 0) { $this->window[$runDest++] = $this->window[$runSrc++]; } } } } } } if ($togo !== 0) { throw new Exception('should never happens'); } $result = array_slice($this->window, ($this->windowPosition === 0 ? $this->windowSize : $this->windowPosition) - $numberOfBytes, $numberOfBytes); // Intel E8 decoding if ($this->intelFilesize !== 0) { if ($this->framesRead++ < 32768) { if ($numberOfBytes <= 6 || $this->intelStarted === false) { $this->intelCurrentPosition += $numberOfBytes; } else { $currentPosition = $this->intelCurrentPosition; $this->intelCurrentPosition += $numberOfBytes; for ($i = 0; $i < $numberOfBytes - 10;) { if ($result[$i++] !== 0xe8) { ++$currentPosition; } else { $absoluteOffset = $result[$i] & 0xff | ($result[$i + 1] & 0xff) << 8 | ($result[$i + 2] & 0xff) << 16 | ($result[$i + 3] & 0xff) << 24; if ($absoluteOffset >= -$currentPosition && $absoluteOffset < $this->intelFilesize) { $referenceOffset = $absoluteOffset >= 0 ? $absoluteOffset - $currentPosition : $absoluteOffset + $this->intelFilesize; $result[$i] = $referenceOffset; $result[$i + 1] = $referenceOffset >> 8; $result[$i + 2] = $referenceOffset >> 16; $result[$i + 3] = $referenceOffset >> 24; } $i += 4; $currentPosition += 5; } } } } } return implode('', array_map('chr', $result)); }