yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
bps.cc
Go to the documentation of this file.
1#include "bps.h"
2
3#include <vector>
4
5#include "absl/status/status.h"
6#include "absl/strings/str_format.h"
7
8namespace yaze {
9namespace util {
10
11namespace {
12
13uint32_t CalculateCrc32(const uint8_t* data, size_t size) {
14 uint32_t crc = 0xFFFFFFFF;
15 for (size_t i = 0; i < size; ++i) {
16 crc ^= data[i];
17 for (int bit = 0; bit < 8; ++bit) {
18 if (crc & 1) {
19 crc = (crc >> 1) ^ 0xEDB88320u;
20 } else {
21 crc >>= 1;
22 }
23 }
24 }
25 return ~crc;
26}
27
28uint32_t CalculateCrc32(const std::vector<uint8_t>& data) {
29 return CalculateCrc32(data.data(), data.size());
30}
31
32void WriteVariableLength(std::vector<uint8_t>& output, uint64_t value) {
33 while (true) {
34 uint8_t byte = value & 0x7F;
35 value >>= 7;
36 if (value == 0) {
37 output.push_back(byte | 0x80);
38 break;
39 }
40 output.push_back(byte);
41 value--;
42 }
43}
44
45uint64_t ReadVariableLength(const uint8_t* data, size_t size, size_t& offset) {
46 uint64_t result = 0;
47 uint64_t shift = 1;
48 while (offset < size) {
49 uint8_t byte = data[offset++];
50 result += (byte & 0x7F) * shift;
51 if (byte & 0x80)
52 break;
53 shift <<= 7;
54 result += shift;
55 }
56 return result;
57}
58
59uint32_t ReadLE32(const uint8_t* data) {
60 return static_cast<uint32_t>(data[0]) |
61 (static_cast<uint32_t>(data[1]) << 8) |
62 (static_cast<uint32_t>(data[2]) << 16) |
63 (static_cast<uint32_t>(data[3]) << 24);
64}
65
66void WriteLE32(std::vector<uint8_t>& output, uint32_t value) {
67 output.push_back(value & 0xFF);
68 output.push_back((value >> 8) & 0xFF);
69 output.push_back((value >> 16) & 0xFF);
70 output.push_back((value >> 24) & 0xFF);
71}
72
73} // namespace
74
75absl::Status ApplyBpsPatch(const std::vector<uint8_t>& source,
76 const std::vector<uint8_t>& patch,
77 std::vector<uint8_t>& output) {
78 if (source.empty()) {
79 return absl::InvalidArgumentError("Source data is empty");
80 }
81 if (patch.size() < 16) {
82 return absl::InvalidArgumentError("Patch data is too small to be valid");
83 }
84
85 // Verify BPS1 header
86 if (patch[0] != 'B' || patch[1] != 'P' || patch[2] != 'S' ||
87 patch[3] != '1') {
88 return absl::InvalidArgumentError(
89 "Invalid BPS patch header (expected BPS1)");
90 }
91
92 // Verify patch CRC32 (last 4 bytes are the patch checksum, computed over
93 // everything before them)
94 size_t patch_data_size = patch.size() - 4;
95 uint32_t stored_patch_crc = ReadLE32(&patch[patch_data_size]);
96 uint32_t computed_patch_crc = CalculateCrc32(patch.data(), patch_data_size);
97 if (stored_patch_crc != computed_patch_crc) {
98 return absl::DataLossError("Patch CRC32 mismatch (corrupt patch file)");
99 }
100
101 // Read header fields
102 size_t offset = 4; // skip "BPS1"
103 uint64_t source_size = ReadVariableLength(patch.data(), patch.size(), offset);
104 uint64_t target_size = ReadVariableLength(patch.data(), patch.size(), offset);
105 uint64_t metadata_size =
106 ReadVariableLength(patch.data(), patch.size(), offset);
107
108 // Skip metadata
109 offset += metadata_size;
110
111 // Validate source size
112 if (source.size() != source_size) {
113 return absl::InvalidArgumentError(
114 absl::StrFormat("Source size mismatch: expected %llu, got %zu",
115 source_size, source.size()));
116 }
117
118 // Verify source CRC32
119 uint32_t stored_source_crc = ReadLE32(&patch[patch_data_size - 8]);
120 uint32_t computed_source_crc = CalculateCrc32(source);
121 if (stored_source_crc != computed_source_crc) {
122 return absl::DataLossError("Source ROM CRC32 mismatch");
123 }
124
125 // Prepare output
126 output.resize(target_size);
127 size_t output_offset = 0;
128 int64_t source_relative_offset = 0;
129 int64_t target_relative_offset = 0;
130
131 // The last 12 bytes are: source CRC32 (4) + target CRC32 (4) + patch CRC32
132 // (4)
133 size_t actions_end = patch.size() - 12;
134
135 while (offset < actions_end) {
136 uint64_t data = ReadVariableLength(patch.data(), patch.size(), offset);
137 uint64_t command = data & 3;
138 uint64_t length = (data >> 2) + 1;
139
140 switch (command) {
141 case 0: {
142 // SourceRead: copy length bytes from source at outputOffset
143 for (uint64_t i = 0; i < length; ++i) {
144 if (output_offset >= target_size) {
145 return absl::InternalError("SourceRead: output overflow");
146 }
147 if (output_offset < source.size()) {
148 output[output_offset] = source[output_offset];
149 } else {
150 output[output_offset] = 0;
151 }
152 output_offset++;
153 }
154 break;
155 }
156 case 1: {
157 // TargetRead: copy length literal bytes from patch data
158 for (uint64_t i = 0; i < length; ++i) {
159 if (output_offset >= target_size || offset >= patch.size()) {
160 return absl::InternalError("TargetRead: overflow");
161 }
162 output[output_offset++] = patch[offset++];
163 }
164 break;
165 }
166 case 2: {
167 // SourceCopy: copy length bytes from source at sourceRelativeOffset
168 uint64_t offset_data =
169 ReadVariableLength(patch.data(), patch.size(), offset);
170 int64_t relative = (offset_data & 1)
171 ? -(static_cast<int64_t>(offset_data >> 1) + 1)
172 : static_cast<int64_t>(offset_data >> 1);
173 source_relative_offset += relative;
174 for (uint64_t i = 0; i < length; ++i) {
175 if (output_offset >= target_size) {
176 return absl::InternalError("SourceCopy: output overflow");
177 }
178 if (source_relative_offset >= 0 &&
179 static_cast<size_t>(source_relative_offset) < source.size()) {
180 output[output_offset] = source[source_relative_offset];
181 } else {
182 output[output_offset] = 0;
183 }
184 output_offset++;
185 source_relative_offset++;
186 }
187 break;
188 }
189 case 3: {
190 // TargetCopy: copy length bytes from output at targetRelativeOffset
191 uint64_t offset_data =
192 ReadVariableLength(patch.data(), patch.size(), offset);
193 int64_t relative = (offset_data & 1)
194 ? -(static_cast<int64_t>(offset_data >> 1) + 1)
195 : static_cast<int64_t>(offset_data >> 1);
196 target_relative_offset += relative;
197 for (uint64_t i = 0; i < length; ++i) {
198 if (output_offset >= target_size) {
199 return absl::InternalError("TargetCopy: output overflow");
200 }
201 if (target_relative_offset >= 0 &&
202 static_cast<size_t>(target_relative_offset) < output_offset) {
203 output[output_offset] = output[target_relative_offset];
204 } else {
205 output[output_offset] = 0;
206 }
207 output_offset++;
208 target_relative_offset++;
209 }
210 break;
211 }
212 default:
213 return absl::InternalError(
214 absl::StrFormat("Unknown BPS command: %llu", command));
215 }
216 }
217
218 // Verify target CRC32
219 uint32_t stored_target_crc = ReadLE32(&patch[patch_data_size - 4]);
220 uint32_t computed_target_crc = CalculateCrc32(output);
221 if (stored_target_crc != computed_target_crc) {
222 return absl::DataLossError("Target CRC32 mismatch after patch application");
223 }
224
225 return absl::OkStatus();
226}
227
228absl::Status CreateBpsPatch(const std::vector<uint8_t>& source,
229 const std::vector<uint8_t>& target,
230 std::vector<uint8_t>& patch) {
231 if (source.empty()) {
232 return absl::InvalidArgumentError("Source data is empty");
233 }
234 if (target.empty()) {
235 return absl::InvalidArgumentError("Target data is empty");
236 }
237
238 patch.clear();
239
240 // BPS header "BPS1"
241 patch.push_back('B');
242 patch.push_back('P');
243 patch.push_back('S');
244 patch.push_back('1');
245
246 // Source size
247 WriteVariableLength(patch, source.size());
248 // Target size
249 WriteVariableLength(patch, target.size());
250 // Metadata size (0 = no metadata)
251 WriteVariableLength(patch, 0);
252
253 // Generate patch actions using SourceRead and TargetRead
254 //
255 // BPS action types:
256 // 0 = SourceRead: copy n bytes from source at current outputOffset
257 // 1 = TargetRead: copy n literal bytes from the patch stream
258 // 2 = SourceCopy: copy n bytes from source at sourceRelativeOffset
259 // 3 = TargetCopy: copy n bytes from target at targetRelativeOffset
260 //
261 // We use a straightforward algorithm:
262 // - Where source and target match at the same offset, use SourceRead
263 // - Where they differ, use TargetRead with literal bytes
264
265 size_t output_offset = 0;
266
267 while (output_offset < target.size()) {
268 // Check how many bytes match at the current position (SourceRead)
269 size_t source_read_len = 0;
270 if (output_offset < source.size()) {
271 while (output_offset + source_read_len < target.size() &&
272 output_offset + source_read_len < source.size() &&
273 source[output_offset + source_read_len] ==
274 target[output_offset + source_read_len]) {
275 ++source_read_len;
276 }
277 }
278
279 if (source_read_len >= 4 ||
280 (source_read_len > 0 &&
281 output_offset + source_read_len >= target.size())) {
282 // Use SourceRead for a run of matching bytes
283 uint64_t action = ((source_read_len - 1) << 2) | 0;
284 WriteVariableLength(patch, action);
285 output_offset += source_read_len;
286 } else {
287 // Use TargetRead for literal bytes until the next good SourceRead match
288 size_t target_read_len = 1;
289 while (output_offset + target_read_len < target.size()) {
290 // Check if the next position has a good SourceRead match
291 if (output_offset + target_read_len < source.size()) {
292 size_t match_len = 0;
293 while (output_offset + target_read_len + match_len < target.size() &&
294 output_offset + target_read_len + match_len < source.size() &&
295 source[output_offset + target_read_len + match_len] ==
296 target[output_offset + target_read_len + match_len]) {
297 ++match_len;
298 }
299 if (match_len >= 4) {
300 break; // Found a good match ahead, stop the literal run
301 }
302 }
303 ++target_read_len;
304 }
305
306 // Write TargetRead action
307 uint64_t action = ((target_read_len - 1) << 2) | 1;
308 WriteVariableLength(patch, action);
309
310 // Write the literal bytes
311 for (size_t i = 0; i < target_read_len; ++i) {
312 patch.push_back(target[output_offset + i]);
313 }
314 output_offset += target_read_len;
315 }
316 }
317
318 // Write checksums (CRC32, little-endian)
319 WriteLE32(patch, CalculateCrc32(source)); // source CRC32
320 WriteLE32(patch, CalculateCrc32(target)); // target CRC32
321 WriteLE32(patch,
322 CalculateCrc32(patch)); // patch CRC32 (over everything so far)
323
324 return absl::OkStatus();
325}
326
327} // namespace util
328} // namespace yaze
void WriteVariableLength(std::vector< uint8_t > &output, uint64_t value)
Definition bps.cc:32
uint64_t ReadVariableLength(const uint8_t *data, size_t size, size_t &offset)
Definition bps.cc:45
uint32_t ReadLE32(const uint8_t *data)
Definition bps.cc:59
void WriteLE32(std::vector< uint8_t > &output, uint32_t value)
Definition bps.cc:66
absl::Status ApplyBpsPatch(const std::vector< uint8_t > &source, const std::vector< uint8_t > &patch, std::vector< uint8_t > &output)
Definition bps.cc:75
absl::Status CreateBpsPatch(const std::vector< uint8_t > &source, const std::vector< uint8_t > &target, std::vector< uint8_t > &patch)
Definition bps.cc:228
uint32_t CalculateCrc32(const uint8_t *data, size_t size)
Definition rom_hash.cc:62