From d24b5b3193e170b1e1153d5568827e00dfde5569 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 1 May 2024 18:33:32 -0600 Subject: [PATCH] Add tkmk00 --- lib/libmio0/tkmk00.c | 639 +++++++++++++++++++++++++++++++++++++ lib/libmio0/tkmk00.h | 13 + src/utils/Decompressor.cpp | 38 +++ src/utils/Decompressor.h | 1 + 4 files changed, 691 insertions(+) create mode 100644 lib/libmio0/tkmk00.c create mode 100644 lib/libmio0/tkmk00.h diff --git a/lib/libmio0/tkmk00.c b/lib/libmio0/tkmk00.c new file mode 100644 index 0000000..9e302bf --- /dev/null +++ b/lib/libmio0/tkmk00.c @@ -0,0 +1,639 @@ +#include +#include + +#include "tkmk00.h" +#include "utils.h" +#if defined(_WIN32) || defined(_WIN64) +#include +#include +#endif + +#define TKMK00_VERSION "0.1" + +static int32_t t1, t7, t8, t9, s0, s1, s3, s4, s6, s7, v0, v1; + +// 0x400 allocated on stack +static uint16_t rgba_buf[0x40]; // SP[000]-SP[07F] - buffer of 32 RGBA colors +static uint16_t buffer80_u16[0x3F]; // SP[080]-SP[0FD] +static uint16_t bufferFE_u16[0x3F]; // SP[0FE]-SP[17B] + // SP[17C]-SP[19F] - preserved registers +static uint8_t byte_buffer[0x20]; // SP[1A0]-SP[1BF] - byte buffer +static uint8_t *some_ptrs[8]; // SP[1C0]-SP[1DF] - 8 pointers to A0 data read from starting at offset 0xC +static uint16_t some_u16s[8]; // SP[1E0]-SP[1EF] - 8 u16s, related to some_ptrs +static uint32_t some_u32s[0x80]; // SP[200]-SP[3F0] - indexes used to initialize buffer80 and bufferFE + +static int32_t header6; +static int some_offset; +static int some_flags; +static const uint8_t *in_ptr; + +static void proc_80040A60(void); +static void proc_80040AC8(void); +static void proc_80040BC0(uint32_t, uint32_t*); +static void proc_80040C54(void); +static void proc_80040C94(void); + +// this is needed to perform logical shifts on signed data +static int32_t SRL(int32_t val, int amount) +{ + uint32_t vU = (uint32_t)val; + vU >>= amount; + return (int32_t)vU; +} + +// a0[in]: pointer to TKMK00 data +// a1[out]: pointer to output (1 byte per pixel) +// a2[out]: pointer to output (RGBA16, 2 bytes per pixel) +// a3[in]: RGBA color to set alpha to 0, values observed: 0x01, 0xBE +void tkmk00_decode(const uint8_t *tkmk, uint8_t *tmp_buf, uint8_t *rgba16, int32_t alpha_color) // 800405D0/0411D0 +{ + unsigned offset; + unsigned test_bits; + int width, height; + int col, row; + int pixels; + int alpha; + unsigned i; + uint16_t rgba0; + uint16_t rgba1; + uint8_t red0, red1, green0, green1, blue0, blue1; + + width = read_u16_be(&tkmk[0x8]); + height = read_u16_be(&tkmk[0xA]); + alpha = alpha_color; + header6 = tkmk[0x6]; + pixels = width * height; + memset(rgba_buf, 0xFF, sizeof(rgba_buf)); + memset(rgba16, 0x0, 2 * pixels); + memset(tmp_buf, 0x0, pixels); + + for (i = 0; i < 8; i++) { + offset = read_u32_be(&tkmk[0xC + i*4]); + if (0 == (header6 & (0x1 << i))) { + offset -= 4; + } + some_ptrs[i] = tkmk + offset; + } + + memset(some_u16s, 0, sizeof(some_u16s)); + some_offset = 0x0; // no idea, used in proc_80040A60 + some_flags = read_u32_be(&tkmk[0x2C]); // used in proc_80040A60 + in_ptr = &tkmk[0x30]; + uint32_t val = 0x20; + proc_80040BC0(DIM(some_u32s)-4, &val); // recursive + + t1 = v0; + t7 = 0; + + for (row = 0; row != height; row++) { + for (col = 0; col != width; col++) { + t9 = read_u16_be(rgba16); + + if (t9 != 0) { + s3 = t9 & 0xFFFE; + t7 = t9; + if (alpha == s3) { + write_u16_be(rgba16, s3); + t7 = s3; + } + } else { + v1 = tmp_buf[0]; + v1 += 1; + proc_80040AC8(); + + if (v0 == 0) { + write_u16_be(rgba16, t7); + } else { + v1 = 1; + proc_80040A60(); + + if (v0 != 0) { + proc_80040C54(); + + s0 = s4; + proc_80040C54(); + + s1 = s4; + proc_80040C54(); + + rgba0 = 0; + rgba1 = 0; + if (row != 0) { + rgba0 = read_u16_be(rgba16 - (width * 2)); + rgba1 = read_u16_be(rgba16 - 2); + } else { + if (col != 0) { + rgba1 = read_u16_be(rgba16 - 2); + } + } + + red0 = (rgba0 & 0x7C0) >> 6; + red1 = (rgba1 & 0x7C0) >> 6; + t8 = (red0 + red1) / 2; + t9 = s0; + proc_80040C94(); + s0 = t9; + + v1 = t9 - t8; + green0 = (rgba0 & 0xF800) >> 11; + green1 = (rgba1 & 0xF800) >> 11; + t8 = v1 + (green0 + green1) / 2; + if (t8 >= 0x20) { + t8 = 0x1F; + } else if (t8 < 0) { + t8 = 0; + } + t9 = s1; + proc_80040C94(); + s1 = t9; + + blue0 = (rgba0 & 0x3E) >> 1; + blue1 = (rgba1 & 0x3E) >> 1; + t8 = v1 + (blue0 + blue1) / 2; + if (t8 >= 0x20) { + t8 = 0x1F; + } else if (t8 < 0) { + t8 = 0; + } + t9 = s4; + proc_80040C94(); + + t7 = (s1 << 11) | (s0 << 6) | (t9 << 1); + if (t7 != alpha) { + t7 |= 0x1; + } + + // insert new value by shifting others to right + for (i = DIM(rgba_buf) - 1; i > 0; i--) { + rgba_buf[i] = rgba_buf[i - 1]; + } + rgba_buf[0] = t7; + } else { + v1 = 6; + proc_80040A60(); + t7 = rgba_buf[v0]; + if (v0 != 0) { + for (i = v0; i > 0; i--) { + rgba_buf[i] = rgba_buf[i - 1]; + } + rgba_buf[0] = t7; + } + } + write_u16_be(rgba16, t7); + test_bits = 0; + if (col != 0) { + test_bits |= 0x01; + } + if (col < (width - 1)) { + test_bits |= 0x02; + } + if (col < (width - 2)) { + test_bits |= 0x04; + } + if (row < (height - 1)) { + test_bits |= 0x08; + } + if (row < (height - 2)) { + test_bits |= 0x10; + } + + if (0x2 == (test_bits & 0x2)) { + tmp_buf[1]++; + } + if (0x4 == (test_bits & 0x4)) { + tmp_buf[2]++; + } + if (0x9 == (test_bits & 0x9)) { + tmp_buf[width - 1]++; + } + if (0x8 == (test_bits & 0x8)) { + tmp_buf[width]++; + } + if (0xA == (test_bits & 0xA)) { + tmp_buf[width + 1]++; + } + if (0x10 == (test_bits & 0x10)) { + tmp_buf[2*width]++; + } + + v1 = 1; + proc_80040A60(); + + if (v0 != 0) { + uint8_t *out = rgba16; + s0 = width * 2; + s3 = t7 | 0x1; + + do { + v1 = 2; + proc_80040A60(); + if (v0 == 0) { + v1 = 1; + proc_80040A60(); + + if (v0 == 0) { + break; + } else { + v1 = 1; + proc_80040A60(); + out += 4; + if (v0 == 0) { + out -= 8; + } + } + } else if (v0 == 1) { + out -= 2; + } else if (v0 == 3) { + out += 2; + } + out += s0; + write_u16_be(out, s3); + } while (1); + } + } + } + tmp_buf += 1; + rgba16 += 2; + } + } +} + +// inputs: a0, a3, v1, t0 +// outputs: a0, a3, t0, t8, t9, v0 +static void proc_80040A60(void) // 80040A60/041660 +{ + unsigned this_offset; + this_offset = some_offset + v1; + t8 = 0x20 - v1; + v0 = SRL(some_flags, t8); // v0 = t0 >> t8; + if (this_offset < 0x21) { + if (this_offset != 0x20) { + some_flags <<= v1; + some_offset += v1; + } else { + some_flags = read_u32_be(in_ptr); + some_offset = 0; + in_ptr += 4; + } + } else { + this_offset = 0x40; + some_flags = read_u32_be(in_ptr); + this_offset -= v1; + this_offset -= some_offset; + some_offset -= t8; + t8 = SRL(some_flags, this_offset); // t8 = t0 >> t9; + v0 |= t8; + in_ptr += 4; + some_flags <<= some_offset; + } +} + +// inputs: t2, v1 +// outputs: t8, t9, s6, s7, v0 +static void proc_80040AC8(void) // 80040AC8/0416C8 +{ + uint8_t *s6ptr; + t8 = SRL(header6, v1); // t8 = t2 >> v1; + t9 = t8 & 0x1; + s7 = some_u16s[v1]; + if (t9 == 0) { + s6ptr = some_ptrs[v1]; + if (s7 == 0) { + s6ptr += 4; + s7 = 0x20; + some_ptrs[v1] = s6ptr; + } + t9 = read_u32_be(s6ptr); + s7 -= 1; + some_u16s[v1] = s7; + v0 = SRL(t9, s7); // v0 = t9 >> s7; + v0 &= 0x1; + } else { + s6ptr = some_ptrs[v1]; + if (s7 == 0) { + s7 = *s6ptr; + v0 = 0x100; + v0 <<= v1; + if ((s7 & 0x80) == 0x00) { // if (s7 >= 0) { + v0 = ~v0; + s7 += 3; + header6 &= v0; + } else { + s7 &= 0x7F; + s7 += 1; + header6 |= v0; + } + v0 = s6ptr[1]; + s6ptr += 2; + s7 <<= 3; + byte_buffer[v1] = v0; + some_ptrs[v1] = s6ptr; + } + v0 = byte_buffer[v1]; + s7 -= 1; + some_u16s[v1] = s7; + t8 = s7 & 0x7; + v0 = SRL(v0, t8); // v0 >>= t8; + v0 &= 0x1; + if (t8 == 0 && s7 != 0) { + t8 = 0x100; + s7 = t8 << v1; + s7 &= header6; + if (s7 != 0) { + s7 = s6ptr[0]; + s6ptr += 1; + byte_buffer[v1] = s7; + some_ptrs[v1] = s6ptr; + } + } + } +} + +// inputs: s3, s4 +// outputs: v0, v1, s0, s1, s3, s4 +static void proc_80040BC0(uint32_t u32idx, uint32_t *val) // 80040BC0/0417C0 +{ + u32idx--; + v1 = 0; + proc_80040AC8(); + + if (v0 != 0) { + uint32_t idx; + some_u32s[u32idx] = *val; + (*val)++; + proc_80040BC0(u32idx, val); + idx = some_u32s[u32idx]; + buffer80_u16[idx] = v0; + proc_80040BC0(u32idx, val); + idx = some_u32s[u32idx]; + u32idx++; + s6 = idx; + bufferFE_u16[idx] = v0; + v0 = s6; + } else { + s0 = 0; + for (s1 = 5; s1 != 0; s1--) { + v1 = 0; + proc_80040AC8(); + s0 = v0 + s0 * 2; + } + u32idx++; + v0 = s0; + } +} + +// inputs: t1 +// outputs: s4, v0 +static void proc_80040C54(void) // 80040C54/041854 +{ + s4 = t1; + while (s4 >= 0x20) { + v1 = 0; + proc_80040AC8(); + if (v0 == 0) { + s4 = buffer80_u16[s4]; + } else { + s4 = bufferFE_u16[s4]; + } + } +} + +// inputs: t8, t9 +// outputs: v0, t9 +static void proc_80040C94(void) // 80040C94/041894 +{ + if (t8 >= 0x10) { + v0 = (0x1F - t8) * 2; + if (v0 < t9) { + v0 = 0x1F; + t9 = v0 - t9; + } else { + v0 = t9 & 0x1; + t9 = SRL(t9, 1); // t9 >>= 1; + if (v0 != 0) { + t9 += t8 + 1; + } else { + t9 = t8 - t9; + } + } + } else { + v0 = t8 << 1; + if (v0 >= t9) { + v0 = t9 & 0x1; + t9 = SRL(t9, 1); // t9 >>= 1; + if (v0 != 0) { + t9 += t8 + 1; + } else { + t9 = t8 - t9; + } + } + } +} + +// TKMK00 standalone executable +#ifdef TKMK00_STANDALONE + +#include + +typedef struct +{ + char *in_filename; + char *out_filename; + char *tmp_filename; + unsigned int offset; + int compress; + uint32_t alpha_color; +} arg_config; + +static arg_config default_config = +{ + .in_filename = NULL, + .out_filename = NULL, + .tmp_filename = NULL, + .offset = 0x0, + .compress = 0, + .alpha_color = 0x01 +}; + +static void print_usage(void) +{ + ERROR("Usage: tkmk00 [-c / -d] [-o OFFSET] FILE [OUTPUT]\n" + "\n" + "tkmk00 v" TKMK00_VERSION ": TKMK00 compression and decompression tool\n" + "\n" + "Optional arguments:\n" + " -a color to use for alpha (default: 0x%02X)\n" + " -c compress raw RGBA16 data into TKMK00 (Not functional)\n" + " -d decompress TKMK00 into RGBA16 raw data (default: decompress)\n" + " -o OFFSET starting offset in FILE (default: 0x%X)\n" + " -t TMP_FILE save temp buffer data to TMP_FILE (default: do not save)\n" + "\n" + "File arguments:\n" + " FILE input file\n" + " [OUTPUT] output file (default: FILE.offset.bin)\n", + default_config.alpha_color, + default_config.offset); + exit(1); +} + +// parse command line arguments +static void parse_arguments(int argc, char *argv[], arg_config *config) +{ + int file_count = 0; + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1] != '\0') { + switch (argv[i][1]) { + case 'a': + if (++i >= argc) { + print_usage(); + } + config->alpha_color = strtoul(argv[i], NULL, 0); + break; + case 'c': + config->compress = 1; + break; + case 'd': + config->compress = 0; + break; + case 'o': + if (++i >= argc) { + print_usage(); + } + config->offset = strtoul(argv[i], NULL, 0); + break; + case 't': + if (++i >= argc) { + print_usage(); + } + config->tmp_filename = argv[i]; + break; + default: + print_usage(); + break; + } + } else { + switch (file_count) { + case 0: + config->in_filename = argv[i]; + break; + case 1: + config->out_filename = argv[i]; + break; + default: // too many + print_usage(); + break; + } + file_count++; + } + } + if (file_count < 1) { + print_usage(); + } +} + +static FILE *open_out_file(const char *out_file) { + if (strcmp(out_file, "-") == 0) { +#if defined(_WIN32) || defined(_WIN64) + _setmode(_fileno(stdout), _O_BINARY); +#endif + return stdout; + } else { + return fopen(out_file, "wb"); + } +} + +static int extract_tkmk00(const char *in_filename, const char *out_filename, const char *tmp_filename, uint32_t offset, uint32_t alpha_color) +{ + FILE *out; + uint8_t *in_buf = NULL; + uint8_t *tmp_buf = NULL; + uint8_t *out_buf = NULL; + size_t bytes_written; + int w, h; + size_t tmp_size; + size_t out_size; + long in_size; + int ret_val = EXIT_SUCCESS; + + in_size = read_file(in_filename, &in_buf); + if (in_size < 0) { + ERROR("Error: reading input file \"%s\"\n", in_filename); + return EXIT_FAILURE; + } + + // verify input + if (memcmp(&in_buf[offset], "TKMK00", 6)) { + ERROR("Error: offset 0x%X does not begin with \"TKMK00\"\n", offset); + return EXIT_FAILURE; + } + + w = read_u16_be(&in_buf[offset + 0x8]); + h = read_u16_be(&in_buf[offset + 0xA]); + + // allocate output memory + out_size = 2 * w * h; + tmp_buf = calloc(1, out_size); + out_buf = calloc(1, out_size); + + // run decoder + tkmk00_decode(&in_buf[offset], tmp_buf, out_buf, alpha_color); + + // optionally write tmp file + if (tmp_filename) { + tmp_size = w * h; + bytes_written = write_file(tmp_filename, tmp_buf, tmp_size); + if (bytes_written < tmp_size) { + ERROR("Error writing to temp file \"%s\"\n", tmp_filename); + return EXIT_FAILURE; + } + } + + out = open_out_file(out_filename); + if (out == NULL) { + ret_val = EXIT_FAILURE; + goto free_all; + } + + // write to output files + bytes_written = fwrite(out_buf, 1, out_size, out); + if (bytes_written != out_size) { + ERROR("Error writing to output file \"%s\"\n", out_filename); + ret_val = EXIT_FAILURE; + } + + // clean up + if (out != stdout) { + fclose(out); + } + +free_all: + free(tmp_buf); + free(out_buf); + free(in_buf); + + return ret_val; +} + +int main(int argc, char *argv[]) +{ + char out_filename[FILENAME_MAX]; + arg_config config; + int ret_val; + + config = default_config; + parse_arguments(argc, argv, &config); + if (config.out_filename == NULL) { + config.out_filename = out_filename; + sprintf(config.out_filename, "%s.%06X.bin", config.in_filename, config.offset); + } + + if (config.compress) { + ERROR("TKMK00 compression not implemented yet.\n"); + ret_val = EXIT_FAILURE; + } else { + ret_val = extract_tkmk00(config.in_filename, config.out_filename, config.tmp_filename, config.offset, config.alpha_color); + } + + return ret_val; +} +#endif // TKMK00_STANDALONE diff --git a/lib/libmio0/tkmk00.h b/lib/libmio0/tkmk00.h new file mode 100644 index 0000000..650e5e7 --- /dev/null +++ b/lib/libmio0/tkmk00.h @@ -0,0 +1,13 @@ +#ifndef TKMK00_H_ +#define TKMK00_H_ + +#include + +// decode TKMK00 data in memory +// tkmk: buffer containing TKMK00 data +// tmp_buf: tempory buffer to load pixel data (must be >= width*height bytes) +// rgba: buffer to output decompressed RGBA16 data (must be >= 2*width*height bytes) +// alpha_color: RGBA color to set apha to 0 +void tkmk00_decode(const uint8_t *tkmk, uint8_t *tmp_buf, uint8_t *rgba16, int32_t alpha_color); + +#endif // TKMK00_H_ diff --git a/src/utils/Decompressor.cpp b/src/utils/Decompressor.cpp index 150ae5d..b12ba0e 100644 --- a/src/utils/Decompressor.cpp +++ b/src/utils/Decompressor.cpp @@ -6,6 +6,7 @@ extern "C" { #include +#include } std::unordered_map gCachedChunks; @@ -35,6 +36,23 @@ DataChunk* Decompressor::Decode(const std::vector& buffer, const uint32 } } +DataChunk* Decompressor::DecodeTKMK00(const std::vector& buffer, const uint32_t offset, const uint32_t size, const uint32_t alpha) { + + if(gCachedChunks.contains(offset)){ + return gCachedChunks[offset]; + } + + const uint8_t* in_buf = buffer.data() + offset; + + const auto decompressed = new uint8_t[size]; + const auto rgba = new uint8_t[size]; + SPDLOG_INFO("RUN?"); + tkmk00_decode(in_buf, decompressed, rgba, alpha); + SPDLOG_INFO("RUN2?"); + gCachedChunks[offset] = new DataChunk{ decompressed, size }; + return gCachedChunks[offset]; +} + DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector& buffer, std::optional manualSize) { auto offset = GetSafeNode(node, "offset"); @@ -58,6 +76,26 @@ DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector }; } + // Extract compressed tkmk00 assets in mk64. + if (node["tkmk00"]) { + const auto alpha = GetSafeNode(node, "alpha"); + const auto width = GetSafeNode(node, "width"); + const auto height = GetSafeNode(node, "height"); + const auto textureSize = width * height * 2; + auto assetPtr = ASSET_PTR(offset); + auto gameSize = Companion::Instance->GetRomData().size(); + + auto fileOffset = TranslateAddr(offset, true); + offset = ASSET_PTR(offset); + + auto decoded = DecodeTKMK00(buffer, fileOffset + offset, textureSize, alpha); + auto size = node["size"] ? node["size"].as() : manualSize.value_or(decoded->size); + return { + .root = decoded, + .segment = { decoded->data, size } + }; + } + // Extract compressed files that contain many assets. switch(type) { case CompressionType::MIO0: diff --git a/src/utils/Decompressor.h b/src/utils/Decompressor.h index d7671a4..da46d25 100644 --- a/src/utils/Decompressor.h +++ b/src/utils/Decompressor.h @@ -33,6 +33,7 @@ struct DecompressedData { class Decompressor { public: static DataChunk* Decode(const std::vector& buffer, uint32_t offset, CompressionType type); + static DataChunk* DecodeTKMK00(const std::vector& buffer, const uint32_t offset, const uint32_t size, const uint32_t alpha); static DecompressedData AutoDecode(YAML::Node& node, std::vector& buffer, std::optional size = std::nullopt); static DecompressedData AutoDecode(uint32_t offset, std::optional size, std::vector& buffer); static uint32_t TranslateAddr(uint32_t addr, bool baseAddress = false);