yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
compression.cc
Go to the documentation of this file.
1#include "compression.h"
2
3#include <climits>
4#include <iostream>
5#include <memory>
6#include <string>
7
8#include "absl/status/status.h"
9#include "absl/status/statusor.h"
10#include "absl/strings/str_format.h"
11#include "rom/rom.h"
12#include "util/hyrule_magic.h"
13#include "util/macro.h"
14
15#define DEBUG_LOG(msg) std::cout << msg << std::endl
16
17namespace yaze {
18namespace gfx {
19
20std::vector<uint8_t> HyruleMagicCompress(uint8_t const* const src,
21 int const oldsize, int* const size,
22 int const flag) {
23 // A command header stores (length - 1) in at most 10 bits. Longer runs,
24 // matches, or literal blocks would spill into the command bits and decode
25 // as a different command.
26 constexpr int kMaxCommandLength = 1024;
27
28 // Allocate buffer large enough for worst-case output
29 std::vector<uint8_t> b2(std::max(0x1000, oldsize * 2));
30
31 int i, j, k, l, m = 0, n, o = 0, bd = 0, p, q = 0, r;
32
33 // Emits the q pending literal bytes that end just before src[i].
34 auto flush_literals = [&]() {
35 q--;
36
37 if (q > 31) {
38 b2[bd++] = (unsigned char)(224 + (q >> 8));
39 }
40
41 b2[bd++] = (unsigned char)q;
42 q++;
43
44 memcpy(b2.data() + bd, src + i - q, q);
45
46 bd += q;
47 q = 0;
48 };
49
50 for (i = 0; i < oldsize;) {
51 l = src[i]; // grab a char from the buffer.
52
53 k = 0;
54
55 r = !!q; // r = the same logical value (0 or 1) as q, but not the same
56 // value necesarily.
57
58 for (j = 0; j < i - 1; j++) {
59 if (src[j] == l) {
60 // Compare only bytes that exist in the input. The match may overlap
61 // src[i..] because the SNES decoder copies forward one byte at a time.
62 m = std::min(oldsize - i, kMaxCommandLength);
63
64 for (n = 0; n < m; n++)
65 if (src[n + j] != src[n + i])
66 break;
67
68 if (n > k)
69 k = n, o = j;
70 }
71 }
72
73 for (n = i + 1; n < oldsize; n++) {
74 if (src[n] != l) {
75 break;
76 }
77 }
78
79 n -= i; // offset back by i. i.e. n = k+1 as above.
80
81 if (n > 1 + r)
82 p = 1;
83 else {
84 m = i + 1 < oldsize ? src[i + 1] : 0;
85
86 for (n = i + 2; n < oldsize; n++) {
87 if (src[n] != l)
88 break;
89
90 n++;
91
92 if (n >= oldsize || src[n] != m)
93 break;
94 }
95
96 n -= i;
97
98 if (n > 2 + r)
99 p = 2;
100 else {
101 m = oldsize - i;
102
103 for (n = 1; n < m; n++)
104 if (src[i + n] != l + n)
105 break;
106
107 if (n > 1 + r)
108 p = 3;
109 else
110 p = 0;
111 }
112 }
113
114 if (k > 3 + r && k > n + (p & 1))
115 p = 4, n = k;
116
117 if (!p) {
118 q++, i++;
119 if (q == kMaxCommandLength) {
120 flush_literals();
121 }
122 } else {
123 if (q) {
124 flush_literals();
125 }
126
127 n = std::min(n, kMaxCommandLength);
128 i += n;
129 n--;
130
131 if (n > 31) {
132 b2[bd++] = (unsigned char)(224 + (n >> 8) + (p << 2));
133 b2[bd++] = (unsigned char)n;
134 } else
135 b2[bd++] = (unsigned char)((p << 5) + n);
136
137 switch (p) {
138 case 1:
139 case 3:
140 b2[bd++] = (unsigned char)l;
141 break;
142
143 case 2:
144 b2[bd++] = (unsigned char)l;
145 b2[bd++] = (unsigned char)m;
146
147 break;
148
149 case 4:
150 if (flag) {
151 b2[bd++] = (unsigned char)(o >> 8);
152 b2[bd++] = (unsigned char)o;
153 } else {
154 b2[bd++] = (unsigned char)o;
155 b2[bd++] = (unsigned char)(o >> 8);
156 }
157 }
158
159 continue;
160 }
161 }
162
163 if (q) {
164 flush_literals();
165 }
166
167 b2[bd++] = 255;
168 b2.resize(bd);
169 *size = bd;
170
171 return b2;
172}
173
174std::vector<uint8_t> HyruleMagicDecompress(uint8_t const* src, int* const size,
175 int const p_big_endian,
176 size_t max_src_size) {
177 std::vector<uint8_t> b2(1024);
178 const uint8_t* const src_start = src;
179
180 int bd = 0, bs = 1024;
181
182 unsigned char a;
183 unsigned char b;
184 unsigned short c, d;
185
186 // Use a lambda to allow early returns instead of goto end_decompression
187 auto decompress_loop = [&]() {
188 for (;;) {
189 // Bounds check: Ensure we can read at least one byte (command)
190 if (max_src_size != static_cast<size_t>(-1) &&
191 (size_t)(src - src_start) >= max_src_size) {
192 std::cerr
193 << "HyruleMagicDecompress: Reached end of buffer unexpectedly."
194 << std::endl;
195 return;
196 }
197
198 // retrieve a uint8_t from the buffer.
199 a = *(src++);
200
201 // end the decompression routine if we encounter 0xff.
202 if (a == 0xff)
203 return;
204
205 // examine the top 3 bits of a.
206 b = (a >> 5);
207
208 if (b == 7) // i.e. 0b 111
209 {
210 // get bits 0b 0001 1100
211 b = ((a >> 2) & 7);
212
213 // get bits 0b 0000 0011, multiply by 256, OR with the next byte.
214 c = ((a & 0x0003) << 8);
215
216 // Bounds check: Ensure we can read the next byte
217 if (max_src_size != static_cast<size_t>(-1) &&
218 (size_t)(src - src_start) >= max_src_size) {
219 std::cerr << "HyruleMagicDecompress: Reached end of buffer reading "
220 "extended len"
221 << std::endl;
222 return;
223 }
224 c |= *(src++);
225 } else
226 // or get bits 0b 0001 1111
227 c = (uint16_t)(a & 31);
228
229 c++;
230
231 if ((bd + c) > (bs - 512)) {
232 // need to increase the buffer size.
233 bs += 1024;
234 // Safety check for excessive allocation
235 if (bs > 1024 * 1024 * 16) { // 16MB limit
236 std::cerr << "HyruleMagicDecompress: Excessive allocation detected."
237 << std::endl;
238 bd = 0;
239 return;
240 }
241 b2.resize(bs);
242 }
243
244 // 7 was handled, here we handle other decompression codes.
245
246 switch (b) {
247 case 0: // 0b 000
248
249 // raw copy
250
251 // Bounds check: Ensure we can read c bytes
252 if (max_src_size != static_cast<size_t>(-1) &&
253 (size_t)(src - src_start + c) > max_src_size) {
254 std::cerr << "HyruleMagicDecompress: Raw copy exceeds buffer."
255 << std::endl;
256 return;
257 }
258
259 // copy info from the src buffer to our new buffer
260 memcpy(b2.data() + bd, src, c);
261
262 // increment the src pointer accordingly.
263 src += c;
264
265 bd += c;
266
267 break;
268
269 case 1: // 0b 001
270
271 // rle copy
272
273 // Bounds check: Ensure we can read 1 byte
274 if (max_src_size != static_cast<size_t>(-1) &&
275 (size_t)(src - src_start) >= max_src_size) {
276 std::cerr << "HyruleMagicDecompress: RLE copy exceeds buffer."
277 << std::endl;
278 return;
279 }
280
281 // make c duplicates of one byte, inc the src pointer.
282 memset(b2.data() + bd, *(src++), c);
283
284 // increase the b2 offset.
285 bd += c;
286
287 break;
288
289 case 2: // 0b 010
290
291 // rle 16-bit alternating copy
292
293 // Bounds check: Ensure we can read 2 bytes
294 if (max_src_size != static_cast<size_t>(-1) &&
295 (size_t)(src - src_start + 2) > max_src_size) {
296 std::cerr
297 << "HyruleMagicDecompress: RLE 16-bit copy exceeds buffer."
298 << std::endl;
299 return;
300 }
301
302 d = zelda3::ldle16b(src);
303
304 src += 2;
305
306 while (c > 1) {
307 // copy that 16-bit number c/2 times into the b2 buffer.
308 zelda3::stle16b(b2.data() + bd, d);
309
310 bd += 2;
311 c -= 2; // hence c/2
312 }
313
314 if (c) // if there's a remainder of c/2, this handles it.
315 b2[bd++] = (char)d;
316
317 break;
318
319 case 3: // 0b 011
320
321 // incrementing copy
322
323 // Bounds check: Ensure we can read 1 byte
324 if (max_src_size != static_cast<size_t>(-1) &&
325 (size_t)(src - src_start) >= max_src_size) {
326 std::cerr << "HyruleMagicDecompress: Inc copy exceeds buffer."
327 << std::endl;
328 return;
329 }
330
331 // get the current src byte.
332 a = *(src++);
333
334 while (c--) {
335 b2[bd++] = a++;
336 }
337
338 break;
339
340 default: // 0b 100, 101, 110
341
342 // lz copy
343
344 // Bounds check: Ensure we can read 2 bytes
345 if (max_src_size != static_cast<size_t>(-1) &&
346 (size_t)(src - src_start + 2) > max_src_size) {
347 std::cerr << "HyruleMagicDecompress: LZ copy exceeds buffer."
348 << std::endl;
349 return;
350 }
351
352 if (p_big_endian) {
353 d = (*src << 8) + src[1];
354 } else {
355 d = zelda3::ldle16b(src);
356 }
357
358 while (c--) {
359 if (d >= static_cast<unsigned short>(bs)) {
360 std::cerr << "HyruleMagicDecompress: LZ ref out of bounds."
361 << std::endl;
362 return;
363 }
364 b2[bd++] = b2[d++];
365 }
366
367 src += 2;
368 }
369 }
370 };
371
372 decompress_loop();
373
374 b2.resize(bd);
375
376 if (size)
377 (*size) = bd;
378
379 return b2;
380}
381
382namespace lc_lz2 {
383
385 std::cout << "Command: " << std::to_string(piece->command) << "\n";
386 std::cout << "Command Length: " << piece->length << "\n";
387 std::cout << "Argument: ";
388 auto arg_size = piece->argument.size();
389 for (int i = 0; i < arg_size; ++i) {
390 printf("%02X ", piece->argument.at(i));
391 }
392 std::cout << "\nArgument Length: " << piece->argument_length << "\n";
393}
394
396 auto compressed_chain = chain_head->next;
397 while (compressed_chain != nullptr) {
398 std::cout << "- Compression Piece -\n";
399 PrintCompressionPiece(compressed_chain);
400 compressed_chain = compressed_chain->next;
401 }
402}
403
404void CheckByteRepeat(const uint8_t* rom_data, DataSizeArray& data_size_taken,
405 CommandArgumentArray& cmd_args, uint& src_data_pos,
406 const unsigned int last_pos) {
407 unsigned int pos = src_data_pos;
408 char byte_to_repeat = rom_data[pos];
409 while (pos <= last_pos && rom_data[pos] == byte_to_repeat) {
410 data_size_taken[kCommandByteFill]++;
411 pos++;
412 }
413 cmd_args[kCommandByteFill][0] = byte_to_repeat;
414}
415
416void CheckWordRepeat(const uint8_t* rom_data, DataSizeArray& data_size_taken,
417 CommandArgumentArray& cmd_args, uint& src_data_pos,
418 const unsigned int last_pos) {
419 if (src_data_pos + 2 <= last_pos &&
420 rom_data[src_data_pos] != rom_data[src_data_pos + 1]) {
421 unsigned int pos = src_data_pos;
422 char byte1 = rom_data[pos];
423 char byte2 = rom_data[pos + 1];
424 pos += 2;
425 data_size_taken[kCommandWordFill] = 2;
426 while (pos + 1 <= last_pos) {
427 if (rom_data[pos] == byte1 && rom_data[pos + 1] == byte2)
428 data_size_taken[kCommandWordFill] += 2;
429 else
430 break;
431 pos += 2;
432 }
433 cmd_args[kCommandWordFill][0] = byte1;
434 cmd_args[kCommandWordFill][1] = byte2;
435 }
436}
437
438void CheckIncByte(const uint8_t* rom_data, DataSizeArray& data_size_taken,
439 CommandArgumentArray& cmd_args, uint& src_data_pos,
440 const unsigned int last_pos) {
441 unsigned int pos = src_data_pos;
442 char byte = rom_data[pos];
443 pos++;
444 data_size_taken[kCommandIncreasingFill] = 1;
445 byte++;
446 while (pos <= last_pos && byte == rom_data[pos]) {
447 data_size_taken[kCommandIncreasingFill]++;
448 byte++;
449 pos++;
450 }
451 cmd_args[kCommandIncreasingFill][0] = rom_data[src_data_pos];
452}
453
454void CheckIntraCopy(const uint8_t* rom_data, DataSizeArray& data_size_taken,
455 CommandArgumentArray& cmd_args, uint& src_data_pos,
456 const unsigned int last_pos, unsigned int start) {
457 if (src_data_pos != start) {
458 unsigned int searching_pos = start;
459 unsigned int current_pos_u = src_data_pos;
460 unsigned int copied_size = 0;
461 unsigned int search_start = start;
462
463 while (searching_pos < src_data_pos && current_pos_u <= last_pos) {
464 while (rom_data[current_pos_u] != rom_data[searching_pos] &&
465 searching_pos < src_data_pos)
466 searching_pos++;
467 search_start = searching_pos;
468 while (current_pos_u <= last_pos &&
469 rom_data[current_pos_u] == rom_data[searching_pos] &&
470 searching_pos < src_data_pos) {
471 copied_size++;
472 current_pos_u++;
473 searching_pos++;
474 }
475 if (copied_size > data_size_taken[kCommandRepeatingBytes]) {
476 search_start -= start;
477 printf("- Found repeat of %d at %d\n", copied_size, search_start);
478 data_size_taken[kCommandRepeatingBytes] = copied_size;
479 cmd_args[kCommandRepeatingBytes][0] = search_start & kSnesByteMax;
480 cmd_args[kCommandRepeatingBytes][1] = search_start >> 8;
481 }
482 current_pos_u = src_data_pos;
483 copied_size = 0;
484 }
485 }
486}
487
488// Check if a command managed to pick up `max_win` or more bytes
489// Avoids being even with copy command, since it's possible to merge copy
490void ValidateForByteGain(const DataSizeArray& data_size_taken,
491 const CommandSizeArray& cmd_size, uint& max_win,
492 uint& cmd_with_max) {
493 for (unsigned int cmd_i = 1; cmd_i < 5; cmd_i++) {
494 unsigned int cmd_size_taken = data_size_taken[cmd_i];
495 // TODO(@scawful): Replace conditional with table of command sizes
496 // "Table that is even with copy but all other cmd are 2"
497 auto table_check =
498 !(cmd_i == kCommandRepeatingBytes && cmd_size_taken == 3);
499 if (cmd_size_taken > max_win && cmd_size_taken > cmd_size[cmd_i] &&
500 table_check) {
501 printf("==> C:%d / S:%d\n", cmd_i, cmd_size_taken);
502 cmd_with_max = cmd_i;
503 max_win = cmd_size_taken;
504 }
505 }
506}
507
508void CompressionCommandAlternative(const uint8_t* rom_data,
509 CompressionPiecePointer& compressed_chain,
510 const CommandSizeArray& cmd_size,
511 const CommandArgumentArray& cmd_args,
512 uint& src_data_pos, uint& comp_accumulator,
513 uint& cmd_with_max, uint& max_win) {
514 printf("- Ok we get a gain from %d\n", cmd_with_max);
515 std::string buffer;
516 buffer.push_back(cmd_args[cmd_with_max][0]);
517 if (cmd_size[cmd_with_max] == 2) {
518 buffer.push_back(cmd_args[cmd_with_max][1]);
519 }
520
521 auto new_comp_piece = std::make_shared<CompressionPiece>(
522 cmd_with_max, max_win, buffer, cmd_size[cmd_with_max]);
523 PrintCompressionPiece(new_comp_piece);
524 // If we let non compressed stuff, we need to add a copy chunk before
525 if (comp_accumulator != 0) {
526 std::string copy_buff;
527 copy_buff.resize(comp_accumulator);
528 for (int i = 0; i < comp_accumulator; ++i) {
529 copy_buff[i] = rom_data[i + src_data_pos - comp_accumulator];
530 }
531 auto copy_chunk = std::make_shared<CompressionPiece>(
532 kCommandDirectCopy, comp_accumulator, copy_buff, comp_accumulator);
533 compressed_chain->next = copy_chunk;
534 compressed_chain = copy_chunk;
535 } else {
536 compressed_chain->next = new_comp_piece;
537 compressed_chain = new_comp_piece;
538 }
539 src_data_pos += max_win;
540 comp_accumulator = 0;
541}
542
543void CheckByteRepeatV2(const uint8_t* data, uint& src_pos,
544 const unsigned int last_pos, CompressionCommand& cmd) {
545 unsigned int i = 0;
546 while (src_pos + i < last_pos && data[src_pos] == data[src_pos + i]) {
547 ++i;
548 }
550 cmd.arguments[kCommandByteFill][0] = data[src_pos];
551}
552
553void CheckWordRepeatV2(const uint8_t* data, uint& src_pos,
554 const unsigned int last_pos, CompressionCommand& cmd) {
555 if (src_pos + 2 <= last_pos && data[src_pos] != data[src_pos + 1]) {
556 unsigned int pos = src_pos;
557 char byte1 = data[pos];
558 char byte2 = data[pos + 1];
559 pos += 2;
561 while (pos + 1 <= last_pos) {
562 if (data[pos] == byte1 && data[pos + 1] == byte2)
563 cmd.data_size[kCommandWordFill] += 2;
564 else
565 break;
566 pos += 2;
567 }
568 cmd.arguments[kCommandWordFill][0] = byte1;
569 cmd.arguments[kCommandWordFill][1] = byte2;
570 }
571}
572
573void CheckIncByteV2(const uint8_t* rom_data, uint& src_data_pos,
574 const unsigned int last_pos, CompressionCommand& cmd) {
575 unsigned int pos = src_data_pos;
576 char byte = rom_data[pos];
577 pos++;
579 byte++;
580 while (pos <= last_pos && byte == rom_data[pos]) {
582 byte++;
583 pos++;
584 }
585 cmd.arguments[kCommandIncreasingFill][0] = rom_data[src_data_pos];
586}
587
588void CheckIntraCopyV2(const uint8_t* rom_data, uint& src_data_pos,
589 const unsigned int last_pos, unsigned int start,
590 CompressionCommand& cmd) {
591 if (src_data_pos != start) {
592 unsigned int searching_pos = start;
593 unsigned int current_pos_u = src_data_pos;
594 unsigned int copied_size = 0;
595 unsigned int search_start = start;
596
597 while (searching_pos < src_data_pos && current_pos_u <= last_pos) {
598 while (rom_data[current_pos_u] != rom_data[searching_pos] &&
599 searching_pos < src_data_pos)
600 searching_pos++;
601 search_start = searching_pos;
602 while (current_pos_u <= last_pos &&
603 rom_data[current_pos_u] == rom_data[searching_pos] &&
604 searching_pos < src_data_pos) {
605 copied_size++;
606 current_pos_u++;
607 searching_pos++;
608 }
609 if (copied_size > cmd.data_size[kCommandRepeatingBytes]) {
610 search_start -= start;
611 printf("- Found repeat of %d at %d\n", copied_size, search_start);
612 cmd.data_size[kCommandRepeatingBytes] = copied_size;
613 cmd.arguments[kCommandRepeatingBytes][0] = search_start & kSnesByteMax;
614 cmd.arguments[kCommandRepeatingBytes][1] = search_start >> 8;
615 }
616 current_pos_u = src_data_pos;
617 copied_size = 0;
618 }
619 }
620}
621
622// Table indicating command sizes, in bytes
623const std::array<int, 5> kCommandSizes = {1, 2, 2, 2, 3};
624
625// TODO(@scawful): TEST ME
627 uint& cmd_with_max) {
628 for (unsigned int cmd_i = 1; cmd_i < 5; cmd_i++) {
629 unsigned int cmd_size_taken = cmd.data_size[cmd_i];
630 // Check if the command size exceeds the maximum win and the size in the
631 // command sizes table, except for the repeating bytes command when the size
632 // taken is 3
633 if (cmd_size_taken > max_win && cmd_size_taken > kCommandSizes[cmd_i] &&
634 !(cmd_i == kCommandRepeatingBytes && cmd_size_taken == 3)) {
635 printf("==> C:%d / S:%d\n", cmd_i, cmd_size_taken);
636 cmd_with_max = cmd_i;
637 max_win = cmd_size_taken;
638 }
639 }
640}
641
642void CompressionCommandAlternativeV2(const uint8_t* rom_data,
643 const CompressionCommand& cmd,
644 CompressionPiecePointer& compressed_chain,
645 uint& src_data_pos, uint& comp_accumulator,
646 uint& cmd_with_max, uint& max_win) {
647 printf("- Ok we get a gain from %d\n", cmd_with_max);
648 std::string buffer;
649 buffer.push_back(cmd.arguments[cmd_with_max][0]);
650 if (cmd.cmd_size[cmd_with_max] == 2) {
651 buffer.push_back(cmd.arguments[cmd_with_max][1]);
652 }
653
654 auto new_comp_piece = std::make_shared<CompressionPiece>(
655 cmd_with_max, max_win, buffer, cmd.cmd_size[cmd_with_max]);
656 PrintCompressionPiece(new_comp_piece);
657 // If we let non compressed stuff, we need to add a copy chunk before
658 if (comp_accumulator != 0) {
659 std::string copy_buff;
660 copy_buff.resize(comp_accumulator);
661 for (int i = 0; i < comp_accumulator; ++i) {
662 copy_buff[i] = rom_data[i + src_data_pos - comp_accumulator];
663 }
664 auto copy_chunk = std::make_shared<CompressionPiece>(
665 kCommandDirectCopy, comp_accumulator, copy_buff, comp_accumulator);
666 compressed_chain->next = copy_chunk;
667 compressed_chain = copy_chunk;
668 } else {
669 compressed_chain->next = new_comp_piece;
670 compressed_chain = new_comp_piece;
671 }
672 src_data_pos += max_win;
673 comp_accumulator = 0;
674}
675
677 const uint8_t* rom_data, CompressionPiecePointer& compressed_chain,
678 const CompressionCommand& command, uint& source_data_position,
679 uint& uncompressed_data_size, uint& best_command, uint& best_command_gain) {
680 std::cout << "- Identified a gain from command: " << best_command
681 << std::endl;
682
683 // Create a buffer to store the arguments for the best command.
684 std::string argument_buffer;
685 argument_buffer.push_back(command.arguments[best_command][0]);
686 if (command.cmd_size[best_command] == 2) {
687 argument_buffer.push_back(command.arguments[best_command][1]);
688 }
689
690 // Create a new compression piece for the best command.
691 auto new_compression_piece = std::make_shared<CompressionPiece>(
692 best_command, best_command_gain, argument_buffer,
693 command.cmd_size[best_command]);
694 PrintCompressionPiece(new_compression_piece);
695
696 // If there is uncompressed data, create a direct copy compression piece for
697 // it.
698 if (uncompressed_data_size != 0) {
699 std::string copy_buffer(uncompressed_data_size, 0);
700 for (int i = 0; i < uncompressed_data_size; ++i) {
701 copy_buffer[i] =
702 rom_data[i + source_data_position - uncompressed_data_size];
703 }
704 auto direct_copy_piece = std::make_shared<CompressionPiece>(
705 kCommandDirectCopy, uncompressed_data_size, copy_buffer,
706 uncompressed_data_size);
707
708 // Append the direct copy piece to the chain.
709 compressed_chain->next = direct_copy_piece;
710 compressed_chain = direct_copy_piece;
711 }
712
713 // Append the new compression piece to the chain.
714 compressed_chain->next = new_compression_piece;
715 compressed_chain = new_compression_piece;
716
717 // Update the position in the source data and reset the uncompressed data
718 // size.
719 source_data_position += best_command_gain;
720 uncompressed_data_size = 0;
721}
722
723absl::StatusOr<CompressionPiecePointer> SplitCompressionPiece(
724 CompressionPiecePointer& piece, int mode) {
725 CompressionPiecePointer new_piece;
726 unsigned int length_left = piece->length - kMaxLengthCompression;
727 piece->length = kMaxLengthCompression;
728
729 switch (piece->command) {
730 case kCommandByteFill:
731 case kCommandWordFill:
732 new_piece = std::make_shared<CompressionPiece>(
733 piece->command, length_left, piece->argument, piece->argument_length);
734 break;
736 new_piece = std::make_shared<CompressionPiece>(
737 piece->command, length_left, piece->argument, piece->argument_length);
738 new_piece->argument[0] =
739 (char)(piece->argument[0] + kMaxLengthCompression);
740 break;
741 case kCommandDirectCopy: {
742 std::string empty;
743 piece->argument_length = kMaxLengthCompression;
744 new_piece = std::make_shared<CompressionPiece>(
745 piece->command, length_left, empty, length_left);
746 // MEMCPY
747 for (int i = 0; i < length_left; ++i) {
748 new_piece->argument[i] = piece->argument[i + kMaxLengthCompression];
749 }
750 break;
751 }
753 piece->argument_length = kMaxLengthCompression;
754 unsigned int offset = piece->argument[0] + (piece->argument[1] << 8);
755 new_piece = std::make_shared<CompressionPiece>(
756 piece->command, length_left, piece->argument, piece->argument_length);
757 if (mode == kNintendoMode2) {
758 new_piece->argument[0] =
760 new_piece->argument[1] = (offset + kMaxLengthCompression) >> 8;
761 }
762 if (mode == kNintendoMode1) {
763 new_piece->argument[1] =
765 new_piece->argument[0] = (offset + kMaxLengthCompression) >> 8;
766 }
767 } break;
768 default: {
769 return absl::InvalidArgumentError(
770 "SplitCompressionCommand: Invalid Command");
771 }
772 }
773 return new_piece;
774}
775
777 int mode) {
778 unsigned int pos = 0;
779 auto piece = start;
780 std::vector<uint8_t> output;
781
782 while (piece != nullptr) {
783 if (piece->length <= kMaxLengthNormalHeader) { // Normal header
784 output.push_back(BUILD_HEADER(piece->command, piece->length));
785 pos++;
786 } else {
787 if (piece->length <= kMaxLengthCompression) {
788 output.push_back(kCompressionStringMod |
789 ((uint8_t)piece->command << 2) |
790 (((piece->length - 1) & 0xFF00) >> 8));
791 pos++;
792 printf("Building extended header : cmd: %d, length: %d - %02X\n",
793 piece->command, piece->length, output[pos - 1]);
794 output.push_back(((piece->length - 1) & 0x00FF)); // (char)
795 pos++;
796 } else {
797 // We need to split the command
798 auto new_piece = SplitCompressionPiece(piece, mode);
799 if (!new_piece.ok()) {
800 std::cout << new_piece.status().ToString() << std::endl;
801 }
802 printf("New added piece\n");
803 auto piece_data = new_piece.value();
804 PrintCompressionPiece(piece_data);
805 piece_data->next = piece->next;
806 piece->next = piece_data;
807 continue;
808 }
809 }
810
811 if (piece->command == kCommandRepeatingBytes) {
812 char tmp[2];
813 tmp[0] = piece->argument[0];
814 tmp[1] = piece->argument[1];
815 if (mode == kNintendoMode1) {
816 tmp[0] = piece->argument[1];
817 tmp[1] = piece->argument[0];
818 }
819 for (const auto& each : tmp) {
820 output.push_back(each);
821 pos++;
822 }
823 } else {
824 for (int i = 0; i < piece->argument_length; ++i) {
825 output.push_back(piece->argument[i]);
826 pos++;
827 }
828 }
829 pos += piece->argument_length;
830 piece = piece->next;
831 }
832 output.push_back(kSnesByteMax);
833 return output;
834}
835
837 int mode, int start, int src_data_pos) {
838 if (chain_head->next != nullptr) {
839 Rom temp_rom;
841 temp_rom.LoadFromData(CreateCompressionString(chain_head->next, mode)))
843 auto decomp_data,
844 DecompressV2(temp_rom.data(), 0, temp_rom.size(), 1, temp_rom.size()));
845 if (!std::equal(decomp_data.begin() + start, decomp_data.end(),
846 temp_rom.begin())) {
847 return absl::InternalError(absl::StrFormat(
848 "Compressed data does not match uncompressed data at %d\n",
849 (uint)(src_data_pos - start)));
850 }
851 }
852 return absl::OkStatus();
853}
854
855// Merge consecutive copy if possible
857 CompressionPiecePointer piece = start;
858
859 while (piece != nullptr) {
860 if (piece->command == kCommandDirectCopy && piece->next != nullptr &&
861 piece->next->command == kCommandDirectCopy &&
862 piece->length + piece->next->length <= kMaxLengthCompression) {
863 unsigned int previous_length = piece->length;
864 piece->length = piece->length + piece->next->length;
865
866 for (int i = 0; i < piece->next->argument_length; ++i) {
867 piece->argument[i + previous_length] = piece->next->argument[i];
868 }
869 piece->argument_length = piece->length;
871
872 auto p_next_next = piece->next->next;
873 piece->next = p_next_next;
874 continue; // Next could be another copy
875 }
876 piece = piece->next;
877 }
878 return start;
879}
880
881absl::StatusOr<std::vector<uint8_t>> CompressV2(const uint8_t* data,
882 const int start,
883 const int length, int mode,
884 bool check) {
885 // Surely there's no need to compress zero...
886 if (length == 0) {
887 return std::vector<uint8_t>();
888 }
889
890 // Worst case should be a copy of the string with extended header
891 std::string worst_case = "aaa";
892 auto compressed_chain =
893 std::make_shared<CompressionPiece>(1, 1, worst_case, 2);
894 auto compressed_chain_start = compressed_chain;
895
896 CompressionCommand current_cmd = {/*argument*/ {{}},
897 /*cmd_size*/ {0, 1, 2, 1, 2},
898 /*data_size*/ {0, 0, 0, 0, 0}};
899
900 unsigned int src_pos = start;
901 unsigned int last_pos = start + length - 1;
902 unsigned int comp_accumulator = 0; // Used when skipping using copy
903
904 while (true) {
905 current_cmd.data_size.fill({});
906 current_cmd.arguments.fill({{}});
907
908 CheckByteRepeatV2(data, src_pos, last_pos, current_cmd);
909 CheckWordRepeatV2(data, src_pos, last_pos, current_cmd);
910 CheckIncByteV2(data, src_pos, last_pos, current_cmd);
911 CheckIntraCopyV2(data, src_pos, last_pos, start, current_cmd);
912
913 unsigned int max_win = 2;
914 unsigned int cmd_with_max = kCommandDirectCopy;
915 ValidateForByteGain(current_cmd.data_size, current_cmd.cmd_size, max_win,
916 cmd_with_max);
917 // ValidateForByteGainV2(current_cmd, max_win, cmd_with_max);
918
919 if (cmd_with_max == kCommandDirectCopy) {
920 // This is the worst case scenario
921 // Progress through the next byte, in case there's a different
922 // compression command we can implement before we hit 32 bytes.
923 src_pos++;
924 comp_accumulator++;
925
926 // Arbitrary choice to do a 32 bytes grouping for copy.
927 if (comp_accumulator == 32 || src_pos > last_pos) {
928 std::string buffer = SetBuffer(data, src_pos, comp_accumulator);
929 auto new_comp_piece = std::make_shared<CompressionPiece>(
930 kCommandDirectCopy, comp_accumulator, buffer, comp_accumulator);
931 compressed_chain->next = new_comp_piece;
932 comp_accumulator = 0;
933 }
934 } else {
935 AddAlternativeCompressionCommand(data, compressed_chain, current_cmd,
936 src_pos, comp_accumulator, cmd_with_max,
937 max_win);
938 }
939
940 if (src_pos > last_pos) {
941 printf("Breaking compression loop\n");
942 break;
943 }
944
945 if (check) {
946 RETURN_IF_ERROR(ValidateCompressionResult(compressed_chain_start, mode,
947 start, src_pos))
948 }
949 }
950
951 // Skipping compression chain header
952 MergeCopy(compressed_chain_start->next);
953 PrintCompressionChain(compressed_chain_start);
954 return CreateCompressionString(compressed_chain_start->next, mode);
955}
956
957absl::StatusOr<std::vector<uint8_t>> CompressGraphics(const uint8_t* data,
958 const int pos,
959 const int length) {
960 return CompressV2(data, pos, length, kNintendoMode2);
961}
962
963absl::StatusOr<std::vector<uint8_t>> CompressOverworld(const uint8_t* data,
964 const int pos,
965 const int length) {
966 return CompressV2(data, pos, length, kNintendoMode1);
967}
968
969absl::StatusOr<std::vector<uint8_t>> CompressOverworld(
970 const std::vector<uint8_t> data, const int pos, const int length) {
971 return CompressV3(data, pos, length, kNintendoMode1);
972}
973
975 unsigned int pos = context.src_pos;
976
977 // Ensure the sequence does not start with an uncompressable byte
978 if (pos == 0 || context.data[pos - 1] != context.data[pos]) {
979 char byte_to_repeat = context.data[pos];
980 while (pos <= context.last_pos && context.data[pos] == byte_to_repeat) {
982 pos++;
983 }
984
985 context.current_cmd.arguments[kCommandByteFill][0] = byte_to_repeat;
986
987 // Added debug log
988 DEBUG_LOG("CheckByteRepeatV3: byte_to_repeat = "
989 << (int)byte_to_repeat << ", size = "
991 }
992}
993
995 if (context.src_pos + 1 <= context.last_pos) { // Changed the condition here
996 unsigned int pos = context.src_pos;
997 char byte1 = context.data[pos];
998 char byte2 = context.data[pos + 1];
999 pos += 2;
1001 while (pos + 1 <= context.last_pos) {
1002 if (context.data[pos] == byte1 && context.data[pos + 1] == byte2)
1004 else
1005 break;
1006 pos += 2;
1007 }
1008
1009 context.current_cmd.arguments[kCommandWordFill][0] = byte1;
1010 context.current_cmd.arguments[kCommandWordFill][1] = byte2;
1011 }
1012
1013 DEBUG_LOG("CheckWordRepeatV3: byte1 = "
1014 << (int)context.current_cmd.arguments[kCommandWordFill][0]
1015 << ", byte2 = "
1016 << (int)context.current_cmd.arguments[kCommandWordFill][1]
1017 << ", size = " << context.current_cmd.data_size[kCommandWordFill]);
1018}
1019
1021 unsigned int pos = context.src_pos;
1022 uint8_t byte = context.data[pos];
1023 pos++;
1025 byte++;
1026
1027 while (pos <= context.last_pos && byte == context.data[pos]) {
1029 byte++;
1030 pos++;
1031 }
1032
1033 // Let's see if the sequence is surrounded by identical bytes and if so,
1034 // consider if a direct copy is better.
1035 if (context.current_cmd.data_size[kCommandIncreasingFill] == 3 &&
1036 context.src_pos > 0 && pos < context.data.size() &&
1037 context.data[context.src_pos - 1] == context.data[pos]) {
1039 0; // Reset the size to 0 to prioritize direct copy
1040 return;
1041 }
1042
1044 context.data[context.src_pos];
1045
1046 DEBUG_LOG("CheckIncByteV3: byte = "
1047 << (int)context.current_cmd.arguments[kCommandIncreasingFill][0]
1048 << ", size = "
1050}
1051
1053 const int window_size =
1054 32; // This can be adjusted for optimal performance and results
1055
1056 // We'll only search for repeating sequences if we're not at the very
1057 // beginning
1058 if (context.src_pos > 0 &&
1059 context.src_pos + window_size <= context.data.size()) {
1060 unsigned int max_copied_size = 0;
1061 unsigned int best_search_start = 0;
1062
1063 // Slide the window over the source data
1064 for (int win_pos = 1; win_pos < window_size && win_pos < context.src_pos;
1065 ++win_pos) {
1066 auto start_search_from = context.data.begin() + context.src_pos - win_pos;
1067 auto search_end = context.data.begin() + context.src_pos;
1068
1069 // Use std::search to find the sequence in the window in the previous
1070 // source data
1071 auto found_pos = std::search(
1072 start_search_from, search_end, context.data.begin() + context.src_pos,
1073 context.data.begin() + context.src_pos + win_pos);
1074
1075 if (found_pos != search_end) {
1076 // Check the entire length of the match
1077 unsigned int len = 0;
1078 while (context.src_pos + len < context.data.size() &&
1079 context.data[context.src_pos + len] == *(found_pos + len)) {
1080 len++;
1081 }
1082
1083 if (len > max_copied_size) {
1084 max_copied_size = len;
1085 best_search_start = found_pos - context.data.begin();
1086 }
1087 }
1088 }
1089
1090 if (max_copied_size >
1092 DEBUG_LOG("CheckIntraCopyV3: Detected repeating sequence of length "
1093 << max_copied_size << " starting from " << best_search_start);
1094 context.current_cmd.data_size[kCommandRepeatingBytes] = max_copied_size;
1096 best_search_start & kSnesByteMax;
1098 best_search_start >> 8;
1099 }
1100
1101 DEBUG_LOG("CheckIntraCopyV3: max_copied_size = " << max_copied_size
1102 << ", best_search_start = "
1103 << best_search_start);
1104 }
1105}
1106
1108 // Initialize the current_cmd with default values.
1109 context.current_cmd = {/*argument*/ {{}},
1110 /*cmd_size*/ {0, 1, 2, 1, 2},
1111 /*data_size*/ {0, 0, 0, 0, 0}};
1112}
1113
1115 // Reset the data_size and arguments for a fresh check.
1116 context.current_cmd.data_size.fill({});
1117 context.current_cmd.arguments.fill({{}});
1118
1119 CheckByteRepeatV3(context);
1120 CheckWordRepeatV3(context);
1121 CheckIncByteV3(context);
1122 CheckIntraCopyV3(context);
1123
1124 DEBUG_LOG("CheckAvailableCompressionCommands: src_pos = " << context.src_pos);
1125}
1126
1128 int max_net_savings = -1; // Adjusted the bias to consider any savings
1129
1130 // Start with the default scenario.
1132
1133 for (unsigned int cmd_i = 1; cmd_i < 5; cmd_i++) {
1134 unsigned int cmd_size_taken = context.current_cmd.data_size[cmd_i];
1135 int net_savings = cmd_size_taken - context.current_cmd.cmd_size[cmd_i];
1136
1137 // Skip commands that aren't efficient.
1138 if (cmd_size_taken <= 2 && cmd_i != kCommandDirectCopy) {
1139 continue;
1140 }
1141
1142 // Check surrounding data for optimization.
1143 if (context.src_pos > 0 &&
1144 context.src_pos + cmd_size_taken < context.data.size()) {
1145 char prev_byte = context.data[context.src_pos - 1];
1146 char next_byte = context.data[context.src_pos + cmd_size_taken];
1147 if (prev_byte != next_byte && cmd_size_taken == 3) {
1148 continue;
1149 }
1150 }
1151
1152 // Check if the current command offers more net savings.
1153 if (net_savings > max_net_savings) {
1154 context.cmd_with_max = cmd_i;
1155 max_net_savings = net_savings;
1156 }
1157 }
1158
1159 DEBUG_LOG("DetermineBestCompression: cmd_with_max = "
1160 << context.cmd_with_max << ", data_size = "
1161 << context.current_cmd.data_size[context.cmd_with_max]);
1162}
1163
1165 // If the next best compression method isn't direct copy and we have bytes
1166 // accumulated for direct copy, flush them out.
1167 if (context.cmd_with_max != kCommandDirectCopy &&
1168 context.comp_accumulator > 0) {
1169 uint8_t header = BUILD_HEADER(kCommandDirectCopy, context.comp_accumulator);
1170 context.compressed_data.push_back(header);
1171 std::vector<uint8_t> uncompressed_data(
1172 context.data.begin() + context.src_pos - context.comp_accumulator,
1173 context.data.begin() + context.src_pos);
1174 context.compressed_data.insert(context.compressed_data.end(),
1175 uncompressed_data.begin(),
1176 uncompressed_data.end());
1177 context.comp_accumulator = 0;
1178 return;
1179 }
1180
1181 // If the next best compression method is not direct copy and we haven't
1182 // accumulated any bytes, treat it as a single byte direct copy.
1183 if (context.cmd_with_max != kCommandDirectCopy &&
1184 context.comp_accumulator == 0) {
1185 context.compressed_data.push_back(
1186 0x00); // Command for a single byte direct copy
1187 context.compressed_data.push_back(
1188 context.data[context.src_pos]); // The single byte
1189 context.src_pos++;
1190 return;
1191 }
1192
1193 // If we reach here, accumulate bytes for a direct copy.
1194 context.src_pos++;
1195 context.comp_accumulator++;
1196
1197 // If we've accumulated the maximum bytes for a direct copy command or
1198 // reached the end, flush them.
1199 if (context.comp_accumulator >= 32 || context.src_pos > context.last_pos) {
1200 uint8_t header = BUILD_HEADER(kCommandDirectCopy, context.comp_accumulator);
1201 context.compressed_data.push_back(header);
1202 std::vector<uint8_t> uncompressed_data(
1203 context.data.begin() + context.src_pos - context.comp_accumulator,
1204 context.data.begin() + context.src_pos);
1205 context.compressed_data.insert(context.compressed_data.end(),
1206 uncompressed_data.begin(),
1207 uncompressed_data.end());
1208 context.comp_accumulator = 0;
1209 }
1210
1211 DEBUG_LOG("HandleDirectCopy: src_pos = " << context.src_pos
1212 << ", compressed_data size = "
1213 << context.compressed_data.size());
1214}
1215
1217 DEBUG_LOG("AddCompressionToChain: Adding command arguments: ");
1218
1219 // If there's uncompressed data, add a copy chunk before the compression
1220 // command
1221 if (context.comp_accumulator != 0) {
1222 uint8_t header = BUILD_HEADER(kCommandDirectCopy, context.comp_accumulator);
1223 context.compressed_data.push_back(header);
1224 std::vector<uint8_t> uncompressed_data(
1225 context.data.begin() + context.src_pos - context.comp_accumulator,
1226 context.data.begin() + context.src_pos);
1227 context.compressed_data.insert(context.compressed_data.end(),
1228 uncompressed_data.begin(),
1229 uncompressed_data.end());
1230 context.comp_accumulator = 0;
1231 }
1232
1233 // Now, add the compression command
1234 uint8_t header =
1235 BUILD_HEADER(context.cmd_with_max,
1236 context.current_cmd.data_size[context.cmd_with_max]);
1237 context.compressed_data.push_back(header);
1238
1239 DEBUG_LOG("AddCompressionToChain: (Before) src_pos = "
1240 << context.src_pos
1241 << ", compressed_data size = " << context.compressed_data.size());
1242
1243 // Add the command arguments to the compressed_data vector
1244 context.compressed_data.push_back(
1245 context.current_cmd.arguments[context.cmd_with_max][0]);
1246 if (context.current_cmd.cmd_size[context.cmd_with_max] == 2) {
1247 context.compressed_data.push_back(
1248 context.current_cmd.arguments[context.cmd_with_max][1]);
1249 }
1250
1251 context.src_pos += context.current_cmd.data_size[context.cmd_with_max];
1252 context.comp_accumulator = 0;
1253
1254 DEBUG_LOG("AddCompressionToChain: (After) src_pos = "
1255 << context.src_pos
1256 << ", compressed_data size = " << context.compressed_data.size());
1257}
1258
1260 if (!context.compressed_data.empty()) {
1261 Rom temp_rom;
1262 RETURN_IF_ERROR(temp_rom.LoadFromData(context.compressed_data));
1264 auto decomp_data,
1265 DecompressV2(temp_rom.data(), 0, temp_rom.size(), 1, temp_rom.size()));
1266
1267 if (!std::equal(decomp_data.begin() + context.start, decomp_data.end(),
1268 temp_rom.begin())) {
1269 return absl::InternalError(absl::StrFormat(
1270 "Compressed data does not match uncompressed data at %d\n",
1271 (context.src_pos - context.start)));
1272 }
1273 }
1274 return absl::OkStatus();
1275}
1276
1277absl::StatusOr<CompressionPiece> SplitCompressionPieceV3(
1278 CompressionPiece& piece, int mode) {
1279 CompressionPiece new_piece;
1280 unsigned int length_left = piece.length - kMaxLengthCompression;
1282
1283 switch (piece.command) {
1284 case kCommandByteFill:
1285 case kCommandWordFill:
1286 new_piece = CompressionPiece(piece.command, length_left, piece.argument,
1287 piece.argument_length);
1288 break;
1290 new_piece = CompressionPiece(piece.command, length_left, piece.argument,
1291 piece.argument_length);
1292 new_piece.argument[0] = (char)(piece.argument[0] + kMaxLengthCompression);
1293 break;
1294 case kCommandDirectCopy: {
1296 std::string empty_string = "";
1297 new_piece = CompressionPiece(piece.command, length_left, empty_string,
1298 length_left);
1299 // MEMCPY
1300 for (int i = 0; i < length_left; ++i) {
1301 new_piece.argument[i] = piece.argument[i + kMaxLengthCompression];
1302 }
1303 break;
1304 }
1307 unsigned int offset = piece.argument[0] + (piece.argument[1] << 8);
1308 new_piece = CompressionPiece(piece.command, length_left, piece.argument,
1309 piece.argument_length);
1310 if (mode == kNintendoMode2) {
1311 new_piece.argument[0] = (offset + kMaxLengthCompression) & kSnesByteMax;
1312 new_piece.argument[1] = (offset + kMaxLengthCompression) >> 8;
1313 }
1314 if (mode == kNintendoMode1) {
1315 new_piece.argument[1] = (offset + kMaxLengthCompression) & kSnesByteMax;
1316 new_piece.argument[0] = (offset + kMaxLengthCompression) >> 8;
1317 }
1318 } break;
1319 default: {
1320 return absl::InvalidArgumentError(
1321 "SplitCompressionCommand: Invalid Command");
1322 }
1323 }
1324
1325 return new_piece;
1326}
1327
1329 unsigned int pos = 0;
1330
1331 for (CompressionPiece& piece : context.compression_pieces) {
1332 if (piece.length <= kMaxLengthNormalHeader) { // Normal Header
1333 context.compression_string.push_back(
1334 BUILD_HEADER(piece.command, piece.length));
1335 pos++;
1336 } else {
1337 if (piece.length <= kMaxLengthCompression) {
1338 context.compression_string.push_back(
1339 kCompressionStringMod | ((uint8_t)piece.command << 2) |
1340 (((piece.length - 1) & 0xFF00) >> 8));
1341 pos++;
1342 std::cout << "Building extended header : cmd: " << piece.command
1343 << ", length: " << piece.length << " - "
1344 << (int)context.compression_string[pos - 1] << std::endl;
1345 context.compression_string.push_back(
1346 ((piece.length - 1) & 0x00FF)); // (char)
1347 } else {
1348 // We need to split the command
1349 auto new_piece = SplitCompressionPieceV3(piece, context.mode);
1350 if (!new_piece.ok()) {
1351 std::cout << new_piece.status().ToString() << std::endl;
1352 }
1353 context.compression_pieces.insert(
1354 context.compression_pieces.begin() + pos + 1, new_piece.value());
1355 continue;
1356 }
1357 }
1358
1359 if (piece.command == kCommandRepeatingBytes) {
1360 char tmp[2];
1361 tmp[0] = piece.argument[0];
1362 tmp[1] = piece.argument[1];
1363 if (context.mode == kNintendoMode1) {
1364 tmp[0] = piece.argument[1];
1365 tmp[1] = piece.argument[0];
1366 }
1367 for (const auto& each : tmp) {
1368 context.compression_string.push_back(each);
1369 pos++;
1370 }
1371 } else {
1372 for (int i = 0; i < piece.argument_length; ++i) {
1373 context.compression_string.push_back(piece.argument[i]);
1374 pos++;
1375 }
1376 }
1377 pos += piece.argument_length;
1378 }
1379
1380 // Add any remaining uncompressed data
1381 if (context.comp_accumulator > 0) {
1382 context.compressed_data.insert(
1383 context.compressed_data.end(),
1384 context.data.begin() + context.src_pos - context.comp_accumulator,
1385 context.data.begin() + context.src_pos);
1386 context.comp_accumulator = 0;
1387 }
1388
1389 // Add the end marker to the compressed data
1390 context.compressed_data.push_back(kSnesByteMax);
1391 DEBUG_LOG("FinalizeCompression: compressed_data size = "
1392 << context.compressed_data.size());
1393}
1394
1395absl::StatusOr<std::vector<uint8_t>> CompressV3(
1396 const std::vector<uint8_t>& data, const int start, const int length,
1397 int mode, bool check) {
1398 if (length == 0) {
1399 return std::vector<uint8_t>();
1400 }
1401
1402 CompressionContext context(data, start, length, mode);
1403 InitializeCompression(context);
1404
1405 while (context.src_pos <= context.last_pos) {
1407 DetermineBestCompression(context);
1408
1409 DEBUG_LOG("CompressV3 Loop: cmd_with_max = " << context.cmd_with_max);
1410
1411 if (context.cmd_with_max == kCommandDirectCopy) {
1412 HandleDirectCopy(context);
1413 } else {
1414 AddCompressionToChain(context);
1415 }
1416
1417 if (check) {
1419 }
1420 }
1421
1422 FinalizeCompression(context);
1423 return std::vector<uint8_t>(context.compressed_data.begin(),
1424 context.compressed_data.end());
1425}
1426
1427std::string SetBuffer(const uint8_t* data, int src_pos, int comp_accumulator) {
1428 std::string buffer;
1429 for (int i = 0; i < comp_accumulator; ++i) {
1430 buffer.push_back(data[i + src_pos - comp_accumulator]);
1431 }
1432 return buffer;
1433}
1434
1435std::string SetBuffer(const std::vector<uint8_t>& data, int src_pos,
1436 int comp_accumulator) {
1437 std::string buffer;
1438 for (int i = 0; i < comp_accumulator; ++i) {
1439 buffer.push_back(data[i + src_pos - comp_accumulator]);
1440 }
1441 return buffer;
1442}
1443
1444void memfill(const uint8_t* data, std::vector<uint8_t>& buffer, int buffer_pos,
1445 int offset, int length) {
1446 auto a = data[offset];
1447 auto b = data[offset + 1];
1448 for (int i = 0; i < length; i = i + 2) {
1449 buffer[buffer_pos + i] = a;
1450 if ((i + 1) < length)
1451 buffer[buffer_pos + i + 1] = b;
1452 }
1453}
1454
1455absl::StatusOr<std::vector<uint8_t>> DecompressV2(const uint8_t* data,
1456 int offset, int size,
1457 int mode, size_t rom_size) {
1458 if (size == 0) {
1459 return std::vector<uint8_t>();
1460 }
1461
1462 // Validate initial offset is within bounds (if rom_size provided)
1463 // rom_size == static_cast<size_t>(-1) means "no bounds checking"
1464 if (rom_size != static_cast<size_t>(-1) &&
1465 (offset < 0 || static_cast<size_t>(offset) >= rom_size)) {
1466 return absl::OutOfRangeError(
1467 absl::StrFormat("DecompressV2: Initial offset %d exceeds ROM size %zu",
1468 offset, rom_size));
1469 }
1470
1471 std::vector<uint8_t> buffer(size, 0);
1472 unsigned int length = 0;
1473 unsigned int buffer_pos = 0;
1474 uint8_t command = 0;
1475
1476 // Bounds check before initial header read
1477 if (rom_size != static_cast<size_t>(-1) &&
1478 static_cast<size_t>(offset) >= rom_size) {
1479 return absl::OutOfRangeError(
1480 absl::StrFormat("DecompressV2: Initial offset %d exceeds ROM size %zu",
1481 offset, rom_size));
1482 }
1483 uint8_t header = data[offset];
1484
1485 while (header != kSnesByteMax) {
1486 // Bounds check before reading command (if rom_size provided)
1487 if (rom_size != static_cast<size_t>(-1) &&
1488 static_cast<size_t>(offset) >= rom_size) {
1489 return absl::OutOfRangeError(absl::StrFormat(
1490 "DecompressV2: Offset %d exceeds ROM size %zu while reading command",
1491 offset, rom_size));
1492 }
1493
1494 if ((header & kExpandedMod) == kExpandedMod) {
1495 // Expanded Command - needs 2 bytes
1496 if (rom_size != static_cast<size_t>(-1) &&
1497 static_cast<size_t>(offset + 1) >= rom_size) {
1498 return absl::OutOfRangeError(
1499 absl::StrFormat("DecompressV2: Offset %d+1 exceeds ROM size %zu "
1500 "for expanded command",
1501 offset, rom_size));
1502 }
1503 command = ((header >> 2) & kCommandMod);
1504 length = (((header << 8) | data[offset + 1]) & kExpandedLengthMod);
1505 offset += 2; // Advance 2 bytes in ROM
1506 } else {
1507 // Normal Command
1508 command = ((header >> 5) & kCommandMod);
1509 length = (header & kNormalLengthMod);
1510 offset += 1; // Advance 1 byte in ROM
1511 }
1512 length += 1; // each commands is at least of size 1 even if index 00
1513
1514 // CRITICAL: Check and resize buffer BEFORE any command writes
1515 // This prevents WASM "index out of bounds" errors
1516 // The buffer may need to grow if decompressed data exceeds initial size
1517 if (buffer_pos + length > static_cast<unsigned int>(size)) {
1518 // Double the buffer size (with overflow protection)
1519 int new_size = size;
1520 while (buffer_pos + length > static_cast<unsigned int>(new_size)) {
1521 if (new_size > INT_MAX / 2) {
1522 return absl::ResourceExhaustedError(absl::StrFormat(
1523 "DecompressV2: Buffer size overflow at pos %u, length %u",
1524 buffer_pos, length));
1525 }
1526 new_size *= 2;
1527 }
1528 size = new_size;
1529 buffer.resize(size);
1530 }
1531
1532 switch (command) {
1533 case kCommandDirectCopy: // Does not advance in the ROM
1534 // Bounds check for direct copy
1535 if (rom_size != static_cast<size_t>(-1) &&
1536 static_cast<size_t>(offset + length) > rom_size) {
1537 return absl::OutOfRangeError(
1538 absl::StrFormat("DecompressV2: DirectCopy offset %d + length %u "
1539 "exceeds ROM size %zu",
1540 offset, length, rom_size));
1541 }
1542 memcpy(buffer.data() + buffer_pos, data + offset, length);
1543 buffer_pos += length;
1544 offset += length;
1545 break;
1546 case kCommandByteFill:
1547 // Bounds check for byte fill
1548 if (rom_size != static_cast<size_t>(-1) &&
1549 static_cast<size_t>(offset) >= rom_size) {
1550 return absl::OutOfRangeError(absl::StrFormat(
1551 "DecompressV2: ByteFill offset %d exceeds ROM size %zu", offset,
1552 rom_size));
1553 }
1554 memset(buffer.data() + buffer_pos, (int)(data[offset]), length);
1555 buffer_pos += length;
1556 offset += 1; // Advances 1 byte in the ROM
1557 break;
1558 case kCommandWordFill:
1559 // Bounds check for word fill (needs 2 bytes)
1560 if (rom_size != static_cast<size_t>(-1) &&
1561 static_cast<size_t>(offset + 1) >= rom_size) {
1562 return absl::OutOfRangeError(absl::StrFormat(
1563 "DecompressV2: WordFill offset %d+1 exceeds ROM size %zu", offset,
1564 rom_size));
1565 }
1566 memfill(data, buffer, buffer_pos, offset, length);
1567 buffer_pos += length;
1568 offset += 2; // Advance 2 byte in the ROM
1569 break;
1571 // Bounds check for increasing fill
1572 if (rom_size != static_cast<size_t>(-1) &&
1573 static_cast<size_t>(offset) >= rom_size) {
1574 return absl::OutOfRangeError(absl::StrFormat(
1575 "DecompressV2: IncreasingFill offset %d exceeds ROM size %zu",
1576 offset, rom_size));
1577 }
1578 auto inc_byte = data[offset];
1579 for (unsigned int i = 0; i < length; i++) {
1580 buffer[buffer_pos] = inc_byte++;
1581 buffer_pos++;
1582 }
1583 offset += 1; // Advance 1 byte in the ROM
1584 } break;
1586 // Bounds check for repeating bytes (needs 2 bytes)
1587 if (rom_size != static_cast<size_t>(-1) &&
1588 static_cast<size_t>(offset + 1) >= rom_size) {
1589 return absl::OutOfRangeError(absl::StrFormat(
1590 "DecompressV2: RepeatingBytes offset %d+1 exceeds ROM size %zu",
1591 offset, rom_size));
1592 }
1593 uint16_t s1 = ((data[offset + 1] & kSnesByteMax) << 8);
1594 uint16_t s2 = (data[offset] & kSnesByteMax);
1595 int addr = (s1 | s2);
1596 if (mode == kNintendoMode1) { // Reversed byte order for
1597 // overworld maps
1598 addr = (data[offset + 1] & kSnesByteMax) |
1599 ((data[offset] & kSnesByteMax) << 8);
1600 }
1601 // The source must already be written. (This used to compare it with
1602 // the input offset, which only held for streams deep in a ROM.)
1603 if (static_cast<unsigned int>(addr) >= buffer_pos) {
1604 return absl::InternalError(absl::StrFormat(
1605 "Decompress: copy source %#06x is not before the output "
1606 "position %#06x (stream offset %#06x)",
1607 addr, buffer_pos, offset));
1608 }
1609 if (static_cast<size_t>(addr) + length > buffer.size()) {
1610 return absl::OutOfRangeError(absl::StrFormat(
1611 "DecompressV2: copy source %#06x+%d is outside the output", addr,
1612 length));
1613 }
1614 // Copy one byte at a time, as the game's decompressor does: a source
1615 // run that reaches the bytes this command writes repeats them. memcpy
1616 // of an overlapping range does not, so streams from
1617 // HyruleMagicCompress (which emits such copies) decoded wrong.
1618 for (int i = 0; i < length; ++i) {
1619 buffer[buffer_pos + i] = buffer[addr + i];
1620 }
1621 buffer_pos += length;
1622 offset += 2;
1623 } break;
1624 default: {
1625 std::cout << absl::StrFormat(
1626 "Decompress: Invalid header (Offset : %#06x, Command: %#04x)\n",
1627 offset, command);
1628 } break;
1629 }
1630 // Bounds check before reading next header byte
1631 if (rom_size != static_cast<size_t>(-1) &&
1632 static_cast<size_t>(offset) >= rom_size) {
1633 return absl::OutOfRangeError(
1634 absl::StrFormat("DecompressV2: Offset %d exceeds ROM size %zu while "
1635 "reading next header",
1636 offset, rom_size));
1637 }
1638 header = data[offset];
1639 }
1640
1641 return buffer;
1642}
1643
1644absl::StatusOr<ExactDecodeResult> DecompressExact(const uint8_t* data,
1645 size_t data_size,
1646 size_t offset,
1647 size_t max_output,
1648 bool big_endian_copy) {
1649 if (data == nullptr || offset >= data_size) {
1650 return absl::OutOfRangeError(
1651 absl::StrFormat("LC-LZ2 stream offset 0x%06zX is outside %zu bytes",
1652 offset, data_size));
1653 }
1654
1655 ExactDecodeResult result;
1656 size_t pos = offset;
1657 auto next_byte = [&]() -> absl::StatusOr<uint8_t> {
1658 if (pos >= data_size) {
1659 return absl::OutOfRangeError(absl::StrFormat(
1660 "LC-LZ2 stream at 0x%06zX is truncated at 0x%06zX", offset, pos));
1661 }
1662 return data[pos++];
1663 };
1664
1665 while (true) {
1666 ASSIGN_OR_RETURN(const uint8_t header, next_byte());
1667 if (header == kSnesByteMax) {
1668 break;
1669 }
1670
1671 int command = 0;
1672 size_t length = 0;
1673 if ((header & kExpandedMod) == kExpandedMod) {
1674 ASSIGN_OR_RETURN(const uint8_t low, next_byte());
1675 command = (header >> 2) & kCommandMod;
1676 length = ((static_cast<size_t>(header & 0x03) << 8) | low) + 1;
1677 } else {
1678 command = header >> 5;
1679 length = static_cast<size_t>(header & kNormalLengthMod) + 1;
1680 }
1681
1682 if (result.data.size() + length > max_output) {
1683 return absl::OutOfRangeError(
1684 absl::StrFormat("LC-LZ2 stream at 0x%06zX decodes past %zu bytes",
1685 offset, max_output));
1686 }
1687
1688 switch (command) {
1689 case kCommandDirectCopy:
1690 for (size_t i = 0; i < length; ++i) {
1691 ASSIGN_OR_RETURN(const uint8_t value, next_byte());
1692 result.data.push_back(value);
1693 }
1694 break;
1695 case kCommandByteFill: {
1696 ASSIGN_OR_RETURN(const uint8_t value, next_byte());
1697 result.data.insert(result.data.end(), length, value);
1698 break;
1699 }
1700 case kCommandWordFill: {
1701 ASSIGN_OR_RETURN(const uint8_t first, next_byte());
1702 ASSIGN_OR_RETURN(const uint8_t second, next_byte());
1703 for (size_t i = 0; i < length; ++i) {
1704 result.data.push_back((i & 1) == 0 ? first : second);
1705 }
1706 break;
1707 }
1709 ASSIGN_OR_RETURN(uint8_t value, next_byte());
1710 for (size_t i = 0; i < length; ++i) {
1711 result.data.push_back(value++);
1712 }
1713 break;
1714 }
1715 default: {
1716 // Commands 4-7 all take the hardware's copy path (bit 7 set).
1717 ASSIGN_OR_RETURN(const uint8_t first, next_byte());
1718 ASSIGN_OR_RETURN(const uint8_t second, next_byte());
1719 size_t source =
1720 big_endian_copy ? (first << 8) | second : (second << 8) | first;
1721 for (size_t i = 0; i < length; ++i) {
1722 if (source >= result.data.size()) {
1723 return absl::InvalidArgumentError(absl::StrFormat(
1724 "LC-LZ2 stream at 0x%06zX copies from output offset 0x%04zX "
1725 "before it is written",
1726 offset, source));
1727 }
1728 result.data.push_back(result.data[source++]);
1729 }
1730 break;
1731 }
1732 }
1733 }
1734
1735 result.compressed_size = pos - offset;
1736 return result;
1737}
1738
1739absl::StatusOr<std::vector<uint8_t>> DecompressGraphics(const uint8_t* data,
1740 int pos, int size) {
1741 return DecompressV2(data, pos, size, kNintendoMode2);
1742}
1743
1744absl::StatusOr<std::vector<uint8_t>> DecompressOverworld(const uint8_t* data,
1745 int pos, int size) {
1746 return DecompressV2(data, pos, size, kNintendoMode1);
1747}
1748
1749absl::StatusOr<std::vector<uint8_t>> DecompressOverworld(
1750 const std::vector<uint8_t> data, int pos, int size) {
1751 return DecompressV2(data.data(), pos, size, kNintendoMode1);
1752}
1753
1754} // namespace lc_lz2
1755} // namespace gfx
1756} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
auto begin()
Definition rom.h:203
auto data() const
Definition rom.h:201
auto size() const
Definition rom.h:200
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:606
#define DEBUG_LOG(msg)
#define BUILD_HEADER(command, length)
Definition compression.h:13
unsigned int uint
Definition macro.h:4
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
void CheckIntraCopyV3(CompressionContext &context)
absl::StatusOr< std::vector< uint8_t > > CompressGraphics(const uint8_t *data, const int pos, const int length)
absl::StatusOr< std::vector< uint8_t > > CompressV3(const std::vector< uint8_t > &data, const int start, const int length, int mode, bool check)
Compresses a buffer of data using the LC_LZ2 algorithm.
constexpr int kExpandedLengthMod
Definition compression.h:59
constexpr int kCommandDirectCopy
Definition compression.h:46
void memfill(const uint8_t *data, std::vector< uint8_t > &buffer, int buffer_pos, int offset, int length)
void FinalizeCompression(CompressionContext &context)
void CheckWordRepeatV2(const uint8_t *data, uint &src_pos, const unsigned int last_pos, CompressionCommand &cmd)
void InitializeCompression(CompressionContext &context)
absl::StatusOr< std::vector< uint8_t > > DecompressV2(const uint8_t *data, int offset, int size, int mode, size_t rom_size)
Decompresses a buffer of data using the LC_LZ2 algorithm.
struct CompressionPiece CompressionPiece
Definition compression.h:90
absl::StatusOr< std::vector< uint8_t > > CompressOverworld(const uint8_t *data, const int pos, const int length)
constexpr int kSnesByteMax
Definition compression.h:56
constexpr int kNintendoMode1
Definition compression.h:54
void AddAlternativeCompressionCommand(const uint8_t *rom_data, CompressionPiecePointer &compressed_chain, const CompressionCommand &command, uint &source_data_position, uint &uncompressed_data_size, uint &best_command, uint &best_command_gain)
std::shared_ptr< CompressionPiece > CompressionPiecePointer
Definition compression.h:91
void CheckIncByteV3(CompressionContext &context)
absl::Status ValidateCompressionResultV3(const CompressionContext &context)
constexpr int kNintendoMode2
Definition compression.h:55
std::string SetBuffer(const uint8_t *data, int src_pos, int comp_accumulator)
constexpr int kCommandRepeatingBytes
Definition compression.h:50
std::array< std::array< char, 2 >, 5 > CommandArgumentArray
Definition compression.h:75
void CheckByteRepeat(const uint8_t *rom_data, DataSizeArray &data_size_taken, CommandArgumentArray &cmd_args, uint &src_data_pos, const unsigned int last_pos)
void CheckByteRepeatV2(const uint8_t *data, uint &src_pos, const unsigned int last_pos, CompressionCommand &cmd)
void HandleDirectCopy(CompressionContext &context)
void CheckIncByteV2(const uint8_t *rom_data, uint &src_data_pos, const unsigned int last_pos, CompressionCommand &cmd)
absl::StatusOr< std::vector< uint8_t > > DecompressGraphics(const uint8_t *data, int pos, int size)
constexpr int kCommandMod
Definition compression.h:57
void PrintCompressionPiece(const CompressionPiecePointer &piece)
constexpr int kCommandWordFill
Definition compression.h:48
constexpr int kMaxLengthNormalHeader
Definition compression.h:52
void CheckAvailableCompressionCommands(CompressionContext &context)
constexpr int kMaxLengthCompression
Definition compression.h:53
void CompressionCommandAlternativeV2(const uint8_t *rom_data, const CompressionCommand &cmd, CompressionPiecePointer &compressed_chain, uint &src_data_pos, uint &comp_accumulator, uint &cmd_with_max, uint &max_win)
void CheckIntraCopyV2(const uint8_t *rom_data, uint &src_data_pos, const unsigned int last_pos, unsigned int start, CompressionCommand &cmd)
std::vector< uint8_t > CreateCompressionString(CompressionPiecePointer &start, int mode)
absl::StatusOr< std::vector< uint8_t > > DecompressOverworld(const uint8_t *data, int pos, int size)
std::array< uint, 5 > DataSizeArray
Definition compression.h:77
constexpr int kCommandIncreasingFill
Definition compression.h:49
void ValidateForByteGainV2(const CompressionCommand &cmd, uint &max_win, uint &cmd_with_max)
void ValidateForByteGain(const DataSizeArray &data_size_taken, const CommandSizeArray &cmd_size, uint &max_win, uint &cmd_with_max)
constexpr int kCommandByteFill
Definition compression.h:47
void PrintCompressionChain(const CompressionPiecePointer &chain_head)
void CheckIncByte(const uint8_t *rom_data, DataSizeArray &data_size_taken, CommandArgumentArray &cmd_args, uint &src_data_pos, const unsigned int last_pos)
constexpr int kExpandedMod
Definition compression.h:58
const std::array< int, 5 > kCommandSizes
void AddCompressionToChain(CompressionContext &context)
absl::StatusOr< CompressionPiecePointer > SplitCompressionPiece(CompressionPiecePointer &piece, int mode)
void CompressionCommandAlternative(const uint8_t *rom_data, CompressionPiecePointer &compressed_chain, const CommandSizeArray &cmd_size, const CommandArgumentArray &cmd_args, uint &src_data_pos, uint &comp_accumulator, uint &cmd_with_max, uint &max_win)
constexpr int kNormalLengthMod
Definition compression.h:60
constexpr int kCompressionStringMod
Definition compression.h:61
absl::StatusOr< ExactDecodeResult > DecompressExact(const uint8_t *data, size_t data_size, size_t offset, size_t max_output, bool big_endian_copy)
Decodes one LC-LZ2 stream with the semantics of the ALTTP bank-00 decompressor (Decompress at $00:E79...
void CheckWordRepeat(const uint8_t *rom_data, DataSizeArray &data_size_taken, CommandArgumentArray &cmd_args, uint &src_data_pos, const unsigned int last_pos)
void CheckByteRepeatV3(CompressionContext &context)
CompressionPiecePointer MergeCopy(CompressionPiecePointer &start)
std::array< uint, 5 > CommandSizeArray
Definition compression.h:76
void DetermineBestCompression(CompressionContext &context)
absl::StatusOr< CompressionPiece > SplitCompressionPieceV3(CompressionPiece &piece, int mode)
absl::StatusOr< std::vector< uint8_t > > CompressV2(const uint8_t *data, const int start, const int length, int mode, bool check)
Compresses a buffer of data using the LC_LZ2 algorithm.
void CheckWordRepeatV3(CompressionContext &context)
void CheckIntraCopy(const uint8_t *rom_data, DataSizeArray &data_size_taken, CommandArgumentArray &cmd_args, uint &src_data_pos, const unsigned int last_pos, unsigned int start)
absl::Status ValidateCompressionResult(CompressionPiecePointer &chain_head, int mode, int start, int src_data_pos)
std::vector< uint8_t > HyruleMagicDecompress(uint8_t const *src, int *const size, int const p_big_endian, size_t max_src_size)
std::vector< uint8_t > HyruleMagicCompress(uint8_t const *const src, int const oldsize, int *const size, int const flag)
uint16_t ldle16b(uint8_t const *const p_arr)
void stle16b(uint8_t *const p_arr, uint16_t const p_val)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::array< std::array< char, 2 >, 5 > arguments
Definition compression.h:66
std::vector< CompressionPiece > compression_pieces
std::vector< uint8_t > compression_string
std::vector< uint8_t > compressed_data
One LC-LZ2 stream decoded the way the game decodes it.