7 public function __construct($source) {
8 $this->source = $source;
11 public function applyPatch($patch) {
12 $this->patch = $patch;
13 $this->patchCursor = 0;
14 $this->outputOffset = 0;
15 $this->sourceRelativeOffset = 0;
16 $this->targetRelativeOffset = 0;
19 while ($this->hasAction()) {
20 $this->handleAction();
23 $sourceCrc = $this->read32();
24 $targetCrc = $this->read32();
25 $patchCrc = $this->read32();
27 if (crc32($this->source) != $sourceCrc) {
28 throw new Exception('source checksum mismatch');
30 if (crc32($this->target) != $targetCrc) {
31 throw new Exception('target checksum mismatch');
33 if (crc32(substr($this->patch, 0, -4)) != $patchCrc) {
34 throw new Exception('patch checksum mismatch');
41 private function readHeader() {
42 $header = $this->readString(4);
43 if ($header != 'BPS1') {
44 throw new Exception('invalid header: '.$header);
47 $sourceSize = $this->readNumber();
48 $targetSize = $this->readNumber();
49 $metadataSize = $this->readNumber();
50 $this->metadata = $this->readString($metadataSize);
51 if ($sourceSize != strlen($this->source)) {
52 throw new Exception('source size mismatch. source: '.strlen($this->source).', patch: '.$sourceSize);
54 $this->target = str_repeat("\0", $targetSize);
57 private function hasAction() {
58 return $this->patchCursor < strlen($this->patch) - 12;
61 private function handleAction() {
62 $data = $this->readNumber();
64 $length = ($data >> 2) + 1;
67 $this->handleSourceRead($length);
70 $this->handleTargetRead($length);
73 $this->handleSourceCopy($length);
76 $this->handleTargetCopy($length);
81 private function handleSourceRead($length) {
83 $this->target[$this->outputOffset] = $this->source[$this->outputOffset];
84 ++$this->outputOffset;
88 private function handleTargetRead($length) {
90 $this->target[$this->outputOffset++] = $this->readByte();
94 private function handleSourceCopy($length) {
95 $this->sourceRelativeOffset += $this->readSignedNumber();
97 $this->target[$this->outputOffset++] = $this->source[$this->sourceRelativeOffset++];
101 private function handleTargetCopy($length) {
102 $this->targetRelativeOffset += $this->readSignedNumber();
104 $this->target[$this->outputOffset++] = $this->target[$this->targetRelativeOffset++];
108 private function read32() {
109 $a = ord($this->readByte());
110 $b = ord($this->readByte());
111 $c = ord($this->readByte());
112 $d = ord($this->readByte());
113 return $a | ($b << 8) | ($c << 16) | ($d << 24);
116 private function readByte() {
117 return $this->patch[$this->patchCursor++];
120 private function readNumber() {
123 for ($i = 0; $i < 16; ++$i) {
124 $x = ord($this->readByte());
125 $data += ($x & 0x7f) * $shift;
126 if ($x & 0x80) break;
133 private function readSignedNumber() {
134 $data = $this->readNumber();
135 return ($data & 1 ? -1 : 1) * ($data >> 1);
138 private function readString($length) {
139 $string = substr($this->patch, $this->patchCursor, $length);
140 $this->patchCursor += $length;
147 private $target = '';
148 private $metadata = '';
150 private $patchCursor = 0;
151 private $outputOffset = 0;
152 private $sourceRelativeOffset = 0;
153 private $targetRelativeOffset = 0;