diff --git a/src/app/gfx/compression.cc b/src/app/gfx/compression.cc index f15286d5..bfb14456 100644 --- a/src/app/gfx/compression.cc +++ b/src/app/gfx/compression.cc @@ -6,7 +6,6 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "absl/strings/str_cat.h" #include "app/core/constants.h" #include "app/rom.h" @@ -15,9 +14,287 @@ namespace yaze { namespace gfx { -namespace lc_lz2 { +// Hyrule Magic +uint8_t* HyruleMagicCompress(uint8_t const* const src, int const oldsize, + int* const size, int const flag) { + unsigned char* b2 = + (unsigned char*)malloc(0x1000); // allocate a 2^12 sized buffer -// Compression commands + int i, j, k, l, m = 0, n, o = 0, bd = 0, p, q = 0, r; + + for (i = 0; i < oldsize;) { + l = src[i]; // grab a char from the buffer. + + k = 0; + + r = !!q; // r = the same logical value (0 or 1) as q, but not the same + // value necesarily. + + for (j = 0; j < i - 1; j++) { + if (src[j] == l) { + m = oldsize - j; + + for (n = 0; n < m; n++) + if (src[n + j] != src[n + i]) break; + + if (n > k) k = n, o = j; + } + } + + for (n = i + 1; n < oldsize; n++) { + if (src[n] != l) { + // look for chars identical to the first one. + // stop if we can't find one. + // n will reach i+k+1 for some k >= 0. + + break; + } + } + + n -= i; // offset back by i. i.e. n = k+1 as above. + + if (n > 1 + r) + p = 1; + else { + m = src[i + 1]; + + for (n = i + 2; n < oldsize; n++) { + if (src[n] != l) break; + + n++; + + if (src[n] != m) break; + } + + n -= i; + + if (n > 2 + r) + p = 2; + else { + m = oldsize - i; + + for (n = 1; n < m; n++) + if (src[i + n] != l + n) break; + + if (n > 1 + r) + p = 3; + else + p = 0; + } + } + + if (k > 3 + r && k > n + (p & 1)) p = 4, n = k; + + if (!p) + q++, i++; + else { + if (q) { + q--; + + if (q > 31) { + b2[bd++] = (unsigned char)(224 + (q >> 8)); + } + + b2[bd++] = (unsigned char)q; + q++; + + memcpy(b2 + bd, src + i - q, q); + + bd += q; + q = 0; + } + + i += n; + n--; + + if (n > 31) { + b2[bd++] = (unsigned char)(224 + (n >> 8) + (p << 2)); + b2[bd++] = (unsigned char)n; + } else + b2[bd++] = (unsigned char)((p << 5) + n); + + switch (p) { + case 1: + case 3: + b2[bd++] = (unsigned char)l; + break; + + case 2: + b2[bd++] = (unsigned char)l; + b2[bd++] = (unsigned char)m; + + break; + + case 4: + if (flag) { + b2[bd++] = (unsigned char)(o >> 8); + b2[bd++] = (unsigned char)o; + } else { + b2[bd++] = (unsigned char)o; + b2[bd++] = (unsigned char)(o >> 8); + } + } + + continue; + } + } + + if (q) { + q--; + + if (q > 31) { + b2[bd++] = (unsigned char)(224 + (q >> 8)); + } + + b2[bd++] = (unsigned char)q; + q++; + + memcpy(b2 + bd, src + i - q, q); + + bd += q; + } + + b2[bd++] = 255; + b2 = (unsigned char*)realloc(b2, bd); + *size = bd; + + return b2; +} + +uint8_t* HyruleMagicDecompress(uint8_t const* src, int* const size, + int const p_big_endian) { + unsigned char* b2 = (unsigned char*)malloc(1024); + + int bd = 0, bs = 1024; + + unsigned char a; + unsigned char b; + unsigned short c, d; + + for (;;) { + // retrieve a uchar from the buffer. + a = *(src++); + + // end the decompression routine if we encounter 0xff. + if (a == 0xff) break; + + // examine the top 3 bits of a. + b = (a >> 5); + + if (b == 7) // i.e. 0b 111 + { + // get bits 0b 0001 1100 + b = ((a >> 2) & 7); + + // get bits 0b 0000 0011, multiply by 256, OR with the next byte. + c = ((a & 0x0003) << 8); + c |= *(src++); + } else + // or get bits 0b 0001 1111 + c = (uint16_t)(a & 31); + + c++; + + if ((bd + c) > (bs - 512)) { + // need to increase the buffer size. + bs += 1024; + b2 = (uint8_t*)realloc(b2, bs); + } + + // 7 was handled, here we handle other decompression codes. + + switch (b) { + case 0: // 0b 000 + + // raw copy + + // copy info from the src buffer to our new buffer, + // at offset bd (which we'll be increasing; + memcpy(b2 + bd, src, c); + + // increment the src pointer accordingly. + src += c; + + bd += c; + + break; + + case 1: // 0b 001 + + // rle copy + + // make c duplicates of one byte, inc the src pointer. + memset(b2 + bd, *(src++), c); + + // increase the b2 offset. + bd += c; + + break; + + case 2: // 0b 010 + + // rle 16-bit alternating copy + + d = core::ldle16b(src); + + src += 2; + + while (c > 1) { + // copy that 16-bit number c/2 times into the b2 buffer. + core::stle16b(b2 + bd, d); + + bd += 2; + c -= 2; // hence c/2 + } + + if (c) // if there's a remainder of c/2, this handles it. + b2[bd++] = (char)d; + + break; + + case 3: // 0b 011 + + // incrementing copy + + // get the current src byte. + a = *(src++); + + while (c--) { + // increment that byte and copy to b2 in c iterations. + // e.g. a = 4, b2 will have 4,5,6,7,8... written to it. + b2[bd++] = a++; + } + + break; + + default: // 0b 100, 101, 110 + + // lz copy + + if (p_big_endian) { + d = (*src << 8) + src[1]; + } else { + d = core::ldle16b(src); + } + + while (c--) { + // copy from a different location in the buffer. + b2[bd++] = b2[d++]; + } + + src += 2; + } + } + + b2 = (unsigned char*)realloc(b2, bd); + + if (size) (*size) = bd; + + // return the unsigned char* buffer b2, which contains the uncompressed data. + return b2; +} + +namespace lc_lz2 { void PrintCompressionPiece(const CompressionPiecePointer& piece) { std::cout << "Command: " << std::to_string(piece->command) << "\n"; @@ -590,286 +867,6 @@ absl::StatusOr> CompressV2(const uchar* data, return CreateCompressionString(compressed_chain_start->next, mode); } -// Hyrule Magic -uint8_t* Compress(uint8_t const* const src, int const oldsize, int* const size, - int const flag) { - unsigned char* b2 = - (unsigned char*)malloc(0x1000); // allocate a 2^12 sized buffer - - int i, j, k, l, m = 0, n, o = 0, bd = 0, p, q = 0, r; - - for (i = 0; i < oldsize;) { - l = src[i]; // grab a char from the buffer. - - k = 0; - - r = !!q; // r = the same logical value (0 or 1) as q, but not the same - // value necesarily. - - for (j = 0; j < i - 1; j++) { - if (src[j] == l) { - m = oldsize - j; - - for (n = 0; n < m; n++) - if (src[n + j] != src[n + i]) break; - - if (n > k) k = n, o = j; - } - } - - for (n = i + 1; n < oldsize; n++) { - if (src[n] != l) { - // look for chars identical to the first one. - // stop if we can't find one. - // n will reach i+k+1 for some k >= 0. - - break; - } - } - - n -= i; // offset back by i. i.e. n = k+1 as above. - - if (n > 1 + r) - p = 1; - else { - m = src[i + 1]; - - for (n = i + 2; n < oldsize; n++) { - if (src[n] != l) break; - - n++; - - if (src[n] != m) break; - } - - n -= i; - - if (n > 2 + r) - p = 2; - else { - m = oldsize - i; - - for (n = 1; n < m; n++) - if (src[i + n] != l + n) break; - - if (n > 1 + r) - p = 3; - else - p = 0; - } - } - - if (k > 3 + r && k > n + (p & 1)) p = 4, n = k; - - if (!p) - q++, i++; - else { - if (q) { - q--; - - if (q > 31) { - b2[bd++] = (unsigned char)(224 + (q >> 8)); - } - - b2[bd++] = (unsigned char)q; - q++; - - memcpy(b2 + bd, src + i - q, q); - - bd += q; - q = 0; - } - - i += n; - n--; - - if (n > 31) { - b2[bd++] = (unsigned char)(224 + (n >> 8) + (p << 2)); - b2[bd++] = (unsigned char)n; - } else - b2[bd++] = (unsigned char)((p << 5) + n); - - switch (p) { - case 1: - case 3: - b2[bd++] = (unsigned char)l; - break; - - case 2: - b2[bd++] = (unsigned char)l; - b2[bd++] = (unsigned char)m; - - break; - - case 4: - if (flag) { - b2[bd++] = (unsigned char)(o >> 8); - b2[bd++] = (unsigned char)o; - } else { - b2[bd++] = (unsigned char)o; - b2[bd++] = (unsigned char)(o >> 8); - } - } - - continue; - } - } - - if (q) { - q--; - - if (q > 31) { - b2[bd++] = (unsigned char)(224 + (q >> 8)); - } - - b2[bd++] = (unsigned char)q; - q++; - - memcpy(b2 + bd, src + i - q, q); - - bd += q; - } - - b2[bd++] = 255; - b2 = (unsigned char*)realloc(b2, bd); - *size = bd; - - return b2; -} - -uint8_t* Uncompress(uint8_t const* src, int* const size, - int const p_big_endian) { - unsigned char* b2 = (unsigned char*)malloc(1024); - - int bd = 0, bs = 1024; - - unsigned char a; - unsigned char b; - unsigned short c, d; - - for (;;) { - // retrieve a uchar from the buffer. - a = *(src++); - - // end the decompression routine if we encounter 0xff. - if (a == 0xff) break; - - // examine the top 3 bits of a. - b = (a >> 5); - - if (b == 7) // i.e. 0b 111 - { - // get bits 0b 0001 1100 - b = ((a >> 2) & 7); - - // get bits 0b 0000 0011, multiply by 256, OR with the next byte. - c = ((a & 0x0003) << 8); - c |= *(src++); - } else - // or get bits 0b 0001 1111 - c = (uint16_t)(a & 31); - - c++; - - if ((bd + c) > (bs - 512)) { - // need to increase the buffer size. - bs += 1024; - b2 = (uint8_t*)realloc(b2, bs); - } - - // 7 was handled, here we handle other decompression codes. - - switch (b) { - case 0: // 0b 000 - - // raw copy - - // copy info from the src buffer to our new buffer, - // at offset bd (which we'll be increasing; - memcpy(b2 + bd, src, c); - - // increment the src pointer accordingly. - src += c; - - bd += c; - - break; - - case 1: // 0b 001 - - // rle copy - - // make c duplicates of one byte, inc the src pointer. - memset(b2 + bd, *(src++), c); - - // increase the b2 offset. - bd += c; - - break; - - case 2: // 0b 010 - - // rle 16-bit alternating copy - - d = core::ldle16b(src); - - src += 2; - - while (c > 1) { - // copy that 16-bit number c/2 times into the b2 buffer. - core::stle16b(b2 + bd, d); - - bd += 2; - c -= 2; // hence c/2 - } - - if (c) // if there's a remainder of c/2, this handles it. - b2[bd++] = (char)d; - - break; - - case 3: // 0b 011 - - // incrementing copy - - // get the current src byte. - a = *(src++); - - while (c--) { - // increment that byte and copy to b2 in c iterations. - // e.g. a = 4, b2 will have 4,5,6,7,8... written to it. - b2[bd++] = a++; - } - - break; - - default: // 0b 100, 101, 110 - - // lz copy - - if (p_big_endian) { - d = (*src << 8) + src[1]; - } else { - d = core::ldle16b(src); - } - - while (c--) { - // copy from a different location in the buffer. - b2[bd++] = b2[d++]; - } - - src += 2; - } - } - - b2 = (unsigned char*)realloc(b2, bd); - - if (size) (*size) = bd; - - // return the unsigned char* buffer b2, which contains the uncompressed data. - return b2; -} - absl::StatusOr> CompressGraphics(const uchar* data, const int pos, const int length) { @@ -887,9 +884,6 @@ absl::StatusOr> CompressOverworld( return CompressV3(data, pos, length, kNintendoMode1); } -// ============================================================================ -// Compression V3 - void CheckByteRepeatV3(CompressionContext& context) { uint pos = context.src_pos; diff --git a/src/app/gfx/compression.h b/src/app/gfx/compression.h index 6ce99c81..cd35d1c7 100644 --- a/src/app/gfx/compression.h +++ b/src/app/gfx/compression.h @@ -13,6 +13,13 @@ namespace yaze { namespace gfx { +// Hyrule Magic +uint8_t* HyruleMagicCompress(uint8_t const* const src, int const oldsize, + int* const size, int const flag); + +uint8_t* HyruleMagicDecompress(uint8_t const* src, int* const size, + int const p_big_endian); + /** * @namespace yaze::gfx::lc_lz2 * @brief Contains the LC_LZ2 compression algorithm. @@ -138,31 +145,33 @@ void CompressionCommandAlternativeV2(const uchar* data, /** * @brief Compresses a buffer of data using the LC_LZ2 algorithm. - * \deprecated Use Compress and Uncompress instead. + * \deprecated Use HyruleMagicDecompress instead. */ -absl::StatusOr> CompressV2(const uchar* data, const int start, - const int length, int mode = 1, - bool check = false); +absl::StatusOr> CompressV2(const uchar* data, + const int start, + const int length, int mode = 1, + bool check = false); -absl::StatusOr> CompressGraphics(const uchar* data, const int pos, - const int length); -absl::StatusOr> CompressOverworld(const uchar* data, const int pos, - const int length); -absl::StatusOr> CompressOverworld(const std::vector data, - const int pos, const int length); +absl::StatusOr> CompressGraphics(const uchar* data, + const int pos, + const int length); +absl::StatusOr> CompressOverworld(const uchar* data, + const int pos, + const int length); +absl::StatusOr> CompressOverworld( + const std::vector data, const int pos, const int length); absl::StatusOr SplitCompressionPiece( CompressionPiecePointer& piece, int mode); -std::vector CreateCompressionString(CompressionPiecePointer& start, int mode); +std::vector CreateCompressionString(CompressionPiecePointer& start, + int mode); absl::Status ValidateCompressionResult(CompressionPiecePointer& chain_head, int mode, int start, int src_data_pos); CompressionPiecePointer MergeCopy(CompressionPiecePointer& start); -// Compression V3 - struct CompressionContext { std::vector data; std::vector compressed_data; @@ -209,42 +218,35 @@ void FinalizeCompression(CompressionContext& context); /** * @brief Compresses a buffer of data using the LC_LZ2 algorithm. - * \deprecated Use Compress and Uncompress instead. + * \deprecated Use HyruleMagicCompress */ -absl::StatusOr> CompressV3(const std::vector& data, - const int start, const int length, - int mode = 1, bool check = false); - -// Hyrule Magic -uint8_t* Compress(uint8_t const* const src, int const oldsize, int* const size, - int const flag); - -uint8_t* Uncompress(uint8_t const* src, int* const size, - int const p_big_endian); - -// Decompression +absl::StatusOr> CompressV3( + const std::vector& data, const int start, const int length, + int mode = 1, bool check = false); std::string SetBuffer(const std::vector& data, int src_pos, int comp_accumulator); std::string SetBuffer(const uchar* data, int src_pos, int comp_accumulator); -void memfill(const uchar* data, std::vector& buffer, int buffer_pos, int offset, - int length); +void memfill(const uchar* data, std::vector& buffer, int buffer_pos, + int offset, int length); /** * @brief Decompresses a buffer of data using the LC_LZ2 algorithm. - * \deprecated Use Compress and Uncompress instead. + * @note Works well for graphics but not overworld data. Prefer Hyrule Magic + * routines for overworld data. */ absl::StatusOr> DecompressV2(const uchar* data, int offset, - int size = 0x800, int mode = 1); -absl::StatusOr> DecompressGraphics(const uchar* data, int pos, int size); -absl::StatusOr> DecompressOverworld(const uchar* data, int pos, int size); -absl::StatusOr> DecompressOverworld(const std::vector data, - int pos, int size); + int size = 0x800, + int mode = 1); +absl::StatusOr> DecompressGraphics(const uchar* data, + int pos, int size); +absl::StatusOr> DecompressOverworld(const uchar* data, + int pos, int size); +absl::StatusOr> DecompressOverworld( + const std::vector data, int pos, int size); } // namespace lc_lz2 - } // namespace gfx - } // namespace yaze #endif // YAZE_APP_GFX_COMPRESSION_H diff --git a/src/app/rom.cc b/src/app/rom.cc index 515a320c..5fd23287 100644 --- a/src/app/rom.cc +++ b/src/app/rom.cc @@ -150,7 +150,7 @@ absl::Status Rom::SaveAllGraphicsData() { final_data = gfx::Bpp8SnesToIndexed(sheet_data, 8); int size = 0; if (compressed) { - auto compressed_data = gfx::lc_lz2::Compress( + auto compressed_data = gfx::HyruleMagicCompress( final_data.data(), final_data.size(), &size, 1); for (int j = 0; j < size; j++) { sheet_data[j] = compressed_data[j]; diff --git a/src/app/zelda3/overworld/overworld.cc b/src/app/zelda3/overworld/overworld.cc index 6a39a087..8ed9a806 100644 --- a/src/app/zelda3/overworld/overworld.cc +++ b/src/app/zelda3/overworld/overworld.cc @@ -542,8 +542,10 @@ absl::Status Overworld::SaveOverworldMaps() { std::vector a, b; int size_a, size_b; // Compress single_map_1 and single_map_2 - auto a_char = gfx::lc_lz2::Compress(single_map_1.data(), 256, &size_a, 1); - auto b_char = gfx::lc_lz2::Compress(single_map_2.data(), 256, &size_b, 1); + auto a_char = + gfx::HyruleMagicCompress(single_map_1.data(), 256, &size_a, 1); + auto b_char = + gfx::HyruleMagicCompress(single_map_2.data(), 256, &size_b, 1); if (a_char == nullptr || b_char == nullptr) { return absl::AbortedError("Error compressing map gfx."); }