source = $source; } public function createPatch($target, $metadata = '') { $this->target = $target; $this->patch = ''; $this->sourceCursor = 0; $this->targetCursor = 0; $this->writeString('BPS1'); $this->writeNumber(strlen($this->source)); $this->writeNumber(strlen($this->target)); $this->writeNumber(strlen($metadata)); $this->writeString($metadata); $lastKnownChange = 0; $targetCopyPos = 0; while ($this->targetCursor < strlen($this->target)) { $numUnchanged = 0; while ($this->sourceLeft($numUnchanged) && $this->sourceEqual($numUnchanged, $numUnchanged, 1)) { ++$numUnchanged; } if ($numUnchanged > 1 || $numUnchanged == (strlen($this->target) - $this->targetCursor)) { $this->writeNumber(($numUnchanged - 1) << 2); $this->sourceCursor += $numUnchanged; $this->targetCursor += $numUnchanged; } $numChanged = 0; if ($lastKnownChange > $this->targetCursor) { $numChanged = $lastKnownChange - $this->targetCursor; } while ((!$this->sourceLeft($numChanged) || !$this->sourceEqual($numChanged, $numChanged, 3)) && $this->targetLeft($numChanged) ) { ++$numChanged; if (!$this->sourceLeft($numChanged)) { $numChanged = strlen($this->target) - $this->targetCursor; } } $lastKnownChange = $this->targetCursor + $numChanged; if ($numChanged) { $rle1Start = $this->targetCursor == 0 ? 1 : 0; while (true) { if ($this->targetEqual($rle1Start - 1, $rle1Start, 4) || $this->targetEqual($rle1Start - 2, $rle1Start, 5) ) { $numChanged = $rle1Start; break; } if ($rle1Start + 3 > $numChanged) { break; } ++$rle1Start; } if ($numChanged) { $this->writeNumber(($numChanged - 1) << 2 | 1); $this->writeString(substr($this->target, $this->targetCursor, $numChanged)); $this->sourceCursor += $numChanged; $this->targetCursor += $numChanged; } if ($this->targetEqual(-2, 0, 3)) { $rleLen = 0; while ($this->targetLeft($rleLen) && $this->targetEqual(0, $rleLen, 2)) { $rleLen += 2; } $this->writeNumber(($rleLen - 1) << 2 | 3); $this->writeNumber(($this->targetCursor - $targetCopyPos - 2) << 1); $this->sourceCursor += $rleLen; $this->targetCursor += $rleLen; $targetCopyPos = $this->targetCursor - 2; } else if ($this->targetEqual(-1, 0, 2)) { $rleLen = 0; while ($this->targetLeft($rleLen) && $this->targetEqual(0, $rleLen, 1)) { $rleLen += 1; } $this->writeNumber(($rleLen - 1) << 2 | 3); $this->writeNumber(($this->targetCursor - $targetCopyPos - 1) << 1); $this->sourceCursor += $rleLen; $this->targetCursor += $rleLen; $targetCopyPos = $this->targetCursor - 1; } } } $this->write32(crc32($this->source)); $this->write32(crc32($this->target)); $this->write32(crc32($this->patch)); return $this->patch; } private function sourceChar($offset = 0) { return $this->source[$this->sourceCursor + $offset]; } private function sourceEqual($aOff, $bOff, $len) { $aStr = substr($this->source, $this->sourceCursor + $aOff, $len); $bStr = substr($this->target, $this->targetCursor + $bOff, $len); return $aStr == $bStr; } private function sourceLeft($num) { return $this->sourceCursor + $num < strlen($this->source); } private function targetChar($offset = 0) { return $this->target[$this->targetCursor + $offset]; } private function targetEqual($aOff, $bOff, $len) { $aStr = substr($this->target, $this->targetCursor + $aOff, $len); $bStr = substr($this->target, $this->targetCursor + $bOff, $len); return $aStr == $bStr; } private function targetLeft($num) { return $this->targetCursor + $num < strlen($this->target); } private function write32($val) { $this->writeByte($val & 0xFF); $this->writeByte(($val >> 8) & 0xFF); $this->writeByte(($val >> 16) & 0xFF); $this->writeByte(($val >> 24) & 0xFF); } private function writeByte($val) { $this->patch .= chr($val); } private function writeNumber($val) { $tmpval = $val; for ($i = 0; $i < 16; ++$i) { $tmpbyte = $tmpval & 0x7f; $tmpval >>= 7; if (!$tmpval) { $this->writeByte($tmpbyte | 0x80); break; } $this->writeByte($tmpbyte); --$tmpval; } } private function writeString($str) { $this->patch .= $str; } private $source; private $target = ''; private $patch = ''; private $sourceCursor = 0; private $targetCursor = 0; }