]> git.localhorst.tv Git - alttp.git/blob - app/Beat/Decoder.php
switch SNES default port
[alttp.git] / app / Beat / Decoder.php
1 <?php
2
3 namespace App\Beat;
4
5 class Decoder {
6
7         public function __construct($source) {
8                 $this->source = $source;
9         }
10
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;
17
18                 $this->readHeader();
19                 while ($this->hasAction()) {
20                         $this->handleAction();
21                 }
22
23                 $sourceCrc = $this->read32();
24                 $targetCrc = $this->read32();
25                 $patchCrc = $this->read32();
26
27                 if (crc32($this->source) != $sourceCrc) {
28                         throw new Exception('source checksum mismatch');
29                 }
30                 if (crc32($this->target) != $targetCrc) {
31                         throw new Exception('target checksum mismatch');
32                 }
33                 if (crc32(substr($this->patch, 0, -4)) != $patchCrc) {
34                         throw new Exception('patch checksum mismatch');
35                 }
36
37                 return $this->target;
38         }
39
40
41         private function readHeader() {
42                 $header = $this->readString(4);
43                 if ($header != 'BPS1') {
44                         throw new Exception('invalid header: '.$header);
45                 }
46
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);
53                 }
54                 $this->target = str_repeat("\0", $targetSize);
55         }
56
57         private function hasAction() {
58                 return $this->patchCursor < strlen($this->patch) - 12;
59         }
60
61         private function handleAction() {
62                 $data = $this->readNumber();
63                 $command = $data & 3;
64                 $length = ($data >> 2) + 1;
65                 switch ($command) {
66                         case 0:
67                                 $this->handleSourceRead($length);
68                                 break;
69                         case 1:
70                                 $this->handleTargetRead($length);
71                                 break;
72                         case 2:
73                                 $this->handleSourceCopy($length);
74                                 break;
75                         case 3:
76                                 $this->handleTargetCopy($length);
77                                 break;
78                 }
79         }
80
81         private function handleSourceRead($length) {
82                 while ($length--) {
83                         $this->target[$this->outputOffset] = $this->source[$this->outputOffset];
84                         ++$this->outputOffset;
85                 }
86         }
87
88         private function handleTargetRead($length) {
89                 while ($length--) {
90                         $this->target[$this->outputOffset++] = $this->readByte();
91                 }
92         }
93
94         private function handleSourceCopy($length) {
95                 $this->sourceRelativeOffset += $this->readSignedNumber();
96                 while ($length--) {
97                         $this->target[$this->outputOffset++] = $this->source[$this->sourceRelativeOffset++];
98                 }
99         }
100
101         private function handleTargetCopy($length) {
102                 $this->targetRelativeOffset += $this->readSignedNumber();
103                 while ($length--) {
104                         $this->target[$this->outputOffset++] = $this->target[$this->targetRelativeOffset++];
105                 }
106         }
107
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);
114         }
115
116         private function readByte() {
117                 return $this->patch[$this->patchCursor++];
118         }
119
120         private function readNumber() {
121                 $data = 0;
122                 $shift = 1;
123                 for ($i = 0; $i < 16; ++$i) {
124                         $x = ord($this->readByte());
125                         $data += ($x & 0x7f) * $shift;
126                         if ($x & 0x80) break;
127                         $shift <<= 7;
128                         $data += $shift;
129                 }
130                 return $data;
131         }
132
133         private function readSignedNumber() {
134                 $data = $this->readNumber();
135                 return ($data & 1 ? -1 : 1) * ($data >> 1);
136         }
137
138         private function readString($length) {
139                 $string = substr($this->patch, $this->patchCursor, $length);
140                 $this->patchCursor += $length;
141                 return $string;
142         }
143
144
145         private $source;
146         private $patch = '';
147         private $target = '';
148         private $metadata = '';
149
150         private $patchCursor = 0;
151         private $outputOffset = 0;
152         private $sourceRelativeOffset = 0;
153         private $targetRelativeOffset = 0;
154
155 }