From 8b5ef9129c2106564f0cfbc7f6dd934b5b5884ae Mon Sep 17 00:00:00 2001 From: inspectredc Date: Wed, 2 Jul 2025 18:55:50 +0100 Subject: [PATCH] ma2d1 support --- CMakeLists.txt | 7 + lib/libyay0/yay0.c | 215 +++++++++++++++ lib/libyay0/yay0.h | 3 + lib/libyay0/yay1.c | 281 ++++++++++++++++++++ lib/libyay0/yay1.h | 8 + src/Companion.cpp | 8 + src/factories/CompressedTextureFactory.cpp | 29 +- src/factories/mario_artist/MA2D1Factory.cpp | 107 ++++++++ src/factories/mario_artist/MA2D1Factory.h | 45 ++++ src/utils/Decompressor.cpp | 17 ++ src/utils/Decompressor.h | 1 + 11 files changed, 719 insertions(+), 2 deletions(-) create mode 100644 lib/libyay0/yay1.c create mode 100644 lib/libyay0/yay1.h create mode 100644 src/factories/mario_artist/MA2D1Factory.cpp create mode 100644 src/factories/mario_artist/MA2D1Factory.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ba3859a..7e048ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ option(BUILD_SM64 "Build with Super Mario 64 support" ON) option(BUILD_MK64 "Build with Mario Kart 64 support" ON) option(BUILD_SF64 "Build with Star Fox 64 support" ON) option(BUILD_FZERO "Build with F-Zero X support" ON) +option(BUILD_MARIO_ARTIST "Build with Mario Artist support" ON) option(BUILD_NAUDIO "Build with NAudio support" ON) ################################################################################ @@ -84,6 +85,12 @@ else() list(FILTER SRC_DIR EXCLUDE REGEX "${CMAKE_CURRENT_SOURCE_DIR}/src/factories/fzerox/*") endif() +if(BUILD_MARIO_ARTIST) + add_definitions(-DMARIO_ARTIST_SUPPORT) +else() + list(FILTER SRC_DIR EXCLUDE REGEX "${CMAKE_CURRENT_SOURCE_DIR}/src/factories/mario_artist/*") +endif() + if(BUILD_NAUDIO) add_definitions(-DNAUDIO_SUPPORT) else() diff --git a/lib/libyay0/yay0.c b/lib/libyay0/yay0.c index 6035948..ac27f1e 100644 --- a/lib/libyay0/yay0.c +++ b/lib/libyay0/yay0.c @@ -3,6 +3,221 @@ #include #include +// defines + +#define GET_BIT(buf, bit) ((buf)[(bit) / 8] & (1 << (7 - ((bit) % 8)))) + +// types +typedef struct +{ + int *indexes; + int allocated; + int count; + int start; +} lookback; + +// functions +#define LOOKBACK_COUNT 256 +#define LOOKBACK_INIT_SIZE 128 +static lookback *lookback_init(void) +{ + lookback *lb = malloc(LOOKBACK_COUNT * sizeof(*lb)); + for (int i = 0; i < LOOKBACK_COUNT; i++) { + lb[i].allocated = LOOKBACK_INIT_SIZE; + lb[i].indexes = malloc(lb[i].allocated * sizeof(*lb[i].indexes)); + lb[i].count = 0; + lb[i].start = 0; + } + return lb; +} + +static void lookback_free(lookback *lb) +{ + for (int i = 0; i < LOOKBACK_COUNT; i++) { + free(lb[i].indexes); + } + free(lb); +} + +static inline void lookback_push(lookback *lkbk, unsigned char val, int index) +{ + lookback *lb = &lkbk[val]; + if (lb->count == lb->allocated) { + lb->allocated *= 4; + lb->indexes = realloc(lb->indexes, lb->allocated * sizeof(*lb->indexes)); + } + lb->indexes[lb->count++] = index; +} + +static void PUT_BIT(unsigned char *buf, int bit, int val) +{ + unsigned char mask = 1 << (7 - (bit % 8)); + unsigned int offset = bit / 8; + buf[offset] = (buf[offset] & ~(mask)) | (val ? mask : 0); +} + +// used to find longest matching stream in buffer +// buf: buffer +// start_offset: offset in buf to look back from +// max_search: max number of bytes to find +// found_offset: returned offset found (0 if none found) +// returns max length of matching stream (0 if none found) +static int find_longest(const unsigned char *buf, int start_offset, int max_search, int *found_offset, lookback *lkbk) +{ + int best_length = 0; + int best_offset = 0; + int cur_length; + int search_len; + int farthest, off, i; + int lb_idx; + const unsigned char first = buf[start_offset]; + lookback *lb = &lkbk[first]; + + // buf + // | off start max + // V |+i-> |+i-> | + // |--------------raw-data-----------------| + // |+i-> | |+i-> + // +cur_length + + // check at most the past 4096 values + farthest = MAX(start_offset - 4096, 0); + // find starting index + for (lb_idx = lb->start; lb_idx < lb->count && lb->indexes[lb_idx] < farthest; lb_idx++) {} + lb->start = lb_idx; + for ( ; lb_idx < lb->count && lb->indexes[lb_idx] < start_offset; lb_idx++) { + off = lb->indexes[lb_idx]; + // check at most requested max or up until start + search_len = MIN(max_search, start_offset - off); + for (i = 0; i < search_len; i++) { + if (buf[start_offset + i] != buf[off + i]) { + break; + } + } + cur_length = i; + // if matched up until start, continue matching in already matched parts + if (cur_length == search_len) { + // check at most requested max less current length + search_len = max_search - cur_length; + for (i = 0; i < search_len; i++) { + if (buf[start_offset + cur_length + i] != buf[off + i]) { + break; + } + } + cur_length += i; + } + if (cur_length > best_length) { + best_offset = start_offset - off; + best_length = cur_length; + } + } + + // return best reverse offset and length (may be 0) + *found_offset = best_offset; + return best_length; +} + +int32_t yay0_encode(const uint8_t *in_buf, uint32_t length, uint8_t* out_buf) { + unsigned char *bit_buf; + unsigned char *comp_buf; + unsigned char *uncomp_buf; + unsigned int bit_length; + unsigned int comp_offset; + unsigned int uncomp_offset; + unsigned int bytes_proc = 0; + int bytes_written; + int bit_idx = 0; + int comp_idx = 0; + int uncomp_idx = 0; + lookback *lookbacks; + + // initialize lookback buffer + lookbacks = lookback_init(); + + // allocate some temporary buffers worst case size + bit_buf = malloc((length + 7) / 8); // 1-bit/byte + comp_buf = malloc(length); // 16-bits/2bytes + uncomp_buf = malloc(length); // all uncompressed + memset(bit_buf, 0, (length + 7) / 8); + + // encode data + // special case for first byte + lookback_push(lookbacks, in_buf[0], 0); + uncomp_buf[uncomp_idx] = in_buf[0]; + uncomp_idx += 1; + bytes_proc += 1; + PUT_BIT(bit_buf, bit_idx++, 1); + while (bytes_proc < length) { + int offset; + int max_length = MIN(length - bytes_proc, 0x111); + int longest_match = find_longest(in_buf, bytes_proc, max_length, &offset, lookbacks); + // push current byte before checking next longer match + lookback_push(lookbacks, in_buf[bytes_proc], bytes_proc); + if (longest_match > 2) { + int lookahead_offset; + // lookahead to next byte to see if longer match + int lookahead_length = MIN(length - bytes_proc - 1, 0x111); + int lookahead_match = find_longest(in_buf, bytes_proc + 1, lookahead_length, &lookahead_offset, lookbacks); + // better match found, use uncompressed + lookahead compressed + if ((longest_match + 1) < lookahead_match) { + // uncompressed byte + uncomp_buf[uncomp_idx] = in_buf[bytes_proc]; + uncomp_idx++; + PUT_BIT(bit_buf, bit_idx, 1); + bytes_proc++; + longest_match = lookahead_match; + offset = lookahead_offset; + bit_idx++; + lookback_push(lookbacks, in_buf[bytes_proc], bytes_proc); + } + // first byte already pushed above + for (int i = 1; i < longest_match; i++) { + lookback_push(lookbacks, in_buf[bytes_proc + i], bytes_proc + i); + } + // compressed block + comp_buf[comp_idx] = (((longest_match - 3) & 0x0F) << 4) | + (((offset - 1) >> 8) & 0x0F); + comp_buf[comp_idx + 1] = (offset - 1) & 0xFF; + comp_idx += 2; + PUT_BIT(bit_buf, bit_idx, 0); + bytes_proc += longest_match; + } else { + // uncompressed byte + uncomp_buf[uncomp_idx] = in_buf[bytes_proc]; + uncomp_idx++; + PUT_BIT(bit_buf, bit_idx, 1); + bytes_proc++; + } + bit_idx++; + } + + // compute final sizes and offsets + // +7 so int division accounts for all bits + bit_length = ((bit_idx + 7) / 8); + // compressed data after control bits and aligned to 4-byte boundary + comp_offset = ALIGN(YAY0_HEADER_LENGTH + bit_length, 4); + uncomp_offset = comp_offset + comp_idx; + bytes_written = uncomp_offset + uncomp_idx; + + // output header + memcpy(out_buf, "Yay0", 4); + write_u32_be(&out_buf[4], length); + write_u32_be(&out_buf[8], comp_offset); + write_u32_be(&out_buf[12], uncomp_offset); + // output data + memcpy(&out_buf[YAY0_HEADER_LENGTH], bit_buf, bit_length); + memcpy(&out_buf[comp_offset], comp_buf, comp_idx); + memcpy(&out_buf[uncomp_offset], uncomp_buf, uncomp_idx); + + // free allocated buffers + free(bit_buf); + free(comp_buf); + free(uncomp_buf); + lookback_free(lookbacks); + + return bytes_written; +} + uint8_t* yay0_decode(const uint8_t* in_buf, uint32_t* out_size){ const uint8_t* in = in_buf; diff --git a/lib/libyay0/yay0.h b/lib/libyay0/yay0.h index 7b101ab..8c6df90 100644 --- a/lib/libyay0/yay0.h +++ b/lib/libyay0/yay0.h @@ -2,4 +2,7 @@ #include +#define YAY0_HEADER_LENGTH 16 + +extern int32_t yay0_encode(const uint8_t *in_buf, uint32_t length, uint8_t* out_buf); extern uint8_t* yay0_decode(const uint8_t* in, uint32_t* out_size); \ No newline at end of file diff --git a/lib/libyay0/yay1.c b/lib/libyay0/yay1.c new file mode 100644 index 0000000..89f0579 --- /dev/null +++ b/lib/libyay0/yay1.c @@ -0,0 +1,281 @@ +#include "yay1.h" +#include "libmio0/utils.h" +#include +#include + +// defines + +#define GET_BIT(buf, bit) ((buf)[(bit) / 8] & (1 << (7 - ((bit) % 8)))) + +// types +typedef struct +{ + int *indexes; + int allocated; + int count; + int start; +} lookback; + +// functions +#define LOOKBACK_COUNT 256 +#define LOOKBACK_INIT_SIZE 128 +static lookback *lookback_init(void) +{ + lookback *lb = malloc(LOOKBACK_COUNT * sizeof(*lb)); + for (int i = 0; i < LOOKBACK_COUNT; i++) { + lb[i].allocated = LOOKBACK_INIT_SIZE; + lb[i].indexes = malloc(lb[i].allocated * sizeof(*lb[i].indexes)); + lb[i].count = 0; + lb[i].start = 0; + } + return lb; +} + +static void lookback_free(lookback *lb) +{ + for (int i = 0; i < LOOKBACK_COUNT; i++) { + free(lb[i].indexes); + } + free(lb); +} + +static inline void lookback_push(lookback *lkbk, unsigned char val, int index) +{ + lookback *lb = &lkbk[val]; + if (lb->count == lb->allocated) { + lb->allocated *= 4; + lb->indexes = realloc(lb->indexes, lb->allocated * sizeof(*lb->indexes)); + } + lb->indexes[lb->count++] = index; +} + +static void PUT_BIT(unsigned char *buf, int bit, int val) +{ + unsigned char mask = 1 << (7 - (bit % 8)); + unsigned int offset = bit / 8; + buf[offset] = (buf[offset] & ~(mask)) | (val ? mask : 0); +} + +// used to find longest matching stream in buffer +// buf: buffer +// start_offset: offset in buf to look back from +// max_search: max number of bytes to find +// found_offset: returned offset found (0 if none found) +// returns max length of matching stream (0 if none found) +static int find_longest(const unsigned char *buf, int start_offset, int max_search, int *found_offset, lookback *lkbk) +{ + int best_length = 0; + int best_offset = 0; + int cur_length; + int search_len; + int farthest, off, i; + int lb_idx; + const unsigned char first = buf[start_offset]; + lookback *lb = &lkbk[first]; + + // buf + // | off start max + // V |+i-> |+i-> | + // |--------------raw-data-----------------| + // |+i-> | |+i-> + // +cur_length + + // check at most the past 4096 values + farthest = MAX(start_offset - 4096, 0); + // find starting index + for (lb_idx = lb->start; lb_idx < lb->count && lb->indexes[lb_idx] < farthest; lb_idx++) {} + lb->start = lb_idx; + for ( ; lb_idx < lb->count && lb->indexes[lb_idx] < start_offset; lb_idx++) { + off = lb->indexes[lb_idx]; + // check at most requested max or up until start + search_len = MIN(max_search, start_offset - off); + for (i = 0; i < search_len; i++) { + if (buf[start_offset + i] != buf[off + i]) { + break; + } + } + cur_length = i; + // if matched up until start, continue matching in already matched parts + if (cur_length == search_len) { + // check at most requested max less current length + search_len = max_search - cur_length; + for (i = 0; i < search_len; i++) { + if (buf[start_offset + cur_length + i] != buf[off + i]) { + break; + } + } + cur_length += i; + } + if (cur_length > best_length) { + best_offset = start_offset - off; + best_length = cur_length; + } + } + + // return best reverse offset and length (may be 0) + *found_offset = best_offset; + return best_length; +} + +int32_t yay1_encode(const uint8_t *in_buf, uint32_t length, uint8_t* out_buf) { + unsigned char *bit_buf; + unsigned char *comp_buf; + unsigned char *uncomp_buf; + unsigned int bit_length; + unsigned int comp_offset; + unsigned int uncomp_offset; + unsigned int bytes_proc = 0; + int bytes_written; + int bit_idx = 0; + int comp_idx = 0; + int uncomp_idx = 0; + lookback *lookbacks; + + // initialize lookback buffer + lookbacks = lookback_init(); + + // allocate some temporary buffers worst case size + bit_buf = malloc((length + 7) / 8); // 1-bit/byte + comp_buf = malloc(length); // 16-bits/2bytes + uncomp_buf = malloc(length); // all uncompressed + memset(bit_buf, 0, (length + 7) / 8); + + // encode data + // special case for first byte + lookback_push(lookbacks, in_buf[0], 0); + uncomp_buf[uncomp_idx] = in_buf[0]; + uncomp_idx += 1; + bytes_proc += 1; + PUT_BIT(bit_buf, bit_idx++, 1); + while (bytes_proc < length) { + int offset; + int max_length = MIN(length - bytes_proc, 0x111); + int longest_match = find_longest(in_buf, bytes_proc, max_length, &offset, lookbacks); + // push current byte before checking next longer match + lookback_push(lookbacks, in_buf[bytes_proc], bytes_proc); + if (longest_match > 2) { + int lookahead_offset; + // lookahead to next byte to see if longer match + int lookahead_length = MIN(length - bytes_proc - 1, 0x111); + int lookahead_match = find_longest(in_buf, bytes_proc + 1, lookahead_length, &lookahead_offset, lookbacks); + // better match found, use uncompressed + lookahead compressed + if ((longest_match + 1) < lookahead_match) { + // uncompressed byte + uncomp_buf[uncomp_idx] = in_buf[bytes_proc]; + uncomp_idx++; + PUT_BIT(bit_buf, bit_idx, 1); + bytes_proc++; + longest_match = lookahead_match; + offset = lookahead_offset; + bit_idx++; + lookback_push(lookbacks, in_buf[bytes_proc], bytes_proc); + } + // first byte already pushed above + for (int i = 1; i < longest_match; i++) { + lookback_push(lookbacks, in_buf[bytes_proc + i], bytes_proc + i); + } + // compressed block + comp_buf[comp_idx] = (((longest_match - 3) & 0x0F) << 4) | + (((offset - 1) >> 8) & 0x0F); + comp_buf[comp_idx + 1] = (offset - 1) & 0xFF; + comp_idx += 2; + PUT_BIT(bit_buf, bit_idx, 0); + bytes_proc += longest_match; + } else { + // uncompressed byte + uncomp_buf[uncomp_idx] = in_buf[bytes_proc]; + uncomp_idx++; + PUT_BIT(bit_buf, bit_idx, 1); + bytes_proc++; + } + bit_idx++; + } + + // compute final sizes and offsets + // +7 so int division accounts for all bits + bit_length = ((bit_idx + 7) / 8); + // compressed data after control bits and aligned to 4-byte boundary + comp_offset = ALIGN(YAY1_HEADER_LENGTH + bit_length, 4); + uncomp_offset = comp_offset + comp_idx; + bytes_written = uncomp_offset + uncomp_idx; + + // output header + memcpy(out_buf, "Yay1", 4); + write_u32_be(&out_buf[4], length); + write_u32_be(&out_buf[8], comp_offset); + write_u32_be(&out_buf[12], uncomp_offset); + // output data + memcpy(&out_buf[YAY1_HEADER_LENGTH], bit_buf, bit_length); + memcpy(&out_buf[comp_offset], comp_buf, comp_idx); + memcpy(&out_buf[uncomp_offset], uncomp_buf, uncomp_idx); + + // free allocated buffers + free(bit_buf); + free(comp_buf); + free(uncomp_buf); + lookback_free(lookbacks); + + return bytes_written; +} + +uint8_t* yay1_decode(const uint8_t* in_buf, uint32_t* out_size){ + + const uint8_t* in = in_buf; + + if(strncmp(in, "Yay1", 4) != 0){ + return NULL; + } + + uint32_t decompressed_size = read_u32_be(in + 4); + uint32_t link_table_offset = read_u32_be(in + 8); + uint32_t chunk_offset = read_u32_be(in + 12); + + uint32_t link_table_idx = link_table_offset; + uint32_t chunk_idx = chunk_offset; + uint32_t other_idx = 16; + + uint32_t mask_bit_counter = 0; + uint32_t current_mask = 0; + uint32_t idx = 0; + + uint8_t* out = malloc(decompressed_size); + memset(out, 0, decompressed_size); + *out_size = decompressed_size; + + while(idx < decompressed_size){ + if(mask_bit_counter == 0){ + current_mask = read_u32_be(in + other_idx); + other_idx += 4; + mask_bit_counter = 32; + } + + if(current_mask & 0x80000000){ + out[idx] = in[chunk_idx]; + idx++; + chunk_idx++; + } else { + uint16_t link = read_u16_be(in + link_table_idx); + link_table_idx += 2; + uint32_t offset = idx - (link & 0xFFF); + uint32_t count = link >> 12; + + if(count == 0){ + uint8_t count_modifier = in[chunk_idx]; + chunk_idx++; + count = count_modifier + 18; + } else { + count += 2; + } + + for(size_t i = 0; i < count; i++){ + out[idx] = out[offset + i - 1]; + idx++; + } + } + + current_mask <<= 1; + mask_bit_counter--; + } + + return out; +} diff --git a/lib/libyay0/yay1.h b/lib/libyay0/yay1.h new file mode 100644 index 0000000..78280ed --- /dev/null +++ b/lib/libyay0/yay1.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#define YAY1_HEADER_LENGTH 16 + +extern int32_t yay1_encode(const uint8_t *in_buf, uint32_t length, uint8_t* out_buf); +extern uint8_t* yay1_decode(const uint8_t* in, uint32_t* out_size); diff --git a/src/Companion.cpp b/src/Companion.cpp index c278470..7a4ee09 100644 --- a/src/Companion.cpp +++ b/src/Companion.cpp @@ -75,6 +75,10 @@ #include "factories/fzerox/GhostRecordFactory.h" #endif +#ifdef MARIO_ARTIST_SUPPORT +#include "factories/mario_artist/MA2D1Factory.h" +#endif + #ifdef NAUDIO_SUPPORT #include "factories/naudio/v0/AudioHeaderFactory.h" #include "factories/naudio/v0/BankFactory.h" @@ -169,6 +173,10 @@ void Companion::Init(const ExportType type) { this->RegisterFactory("FZX:GHOST", std::make_shared()); #endif +#ifdef MARIO_ARTIST_SUPPORT + this->RegisterFactory("MA:MA2D1", std::make_shared()); +#endif + #ifdef NAUDIO_SUPPORT this->RegisterFactory("NAUDIO:V0:AUDIO_HEADER", std::make_shared()); this->RegisterFactory("NAUDIO:V0:SEQUENCE", std::make_shared()); diff --git a/src/factories/CompressedTextureFactory.cpp b/src/factories/CompressedTextureFactory.cpp index 1c8a974..6b507e4 100644 --- a/src/factories/CompressedTextureFactory.cpp +++ b/src/factories/CompressedTextureFactory.cpp @@ -9,6 +9,8 @@ extern "C" { #include "n64graphics/n64graphics.h" #include "BaseFactory.h" #include +#include +#include } static bool isTable = false; @@ -31,6 +33,7 @@ static const std::unordered_map sTextureFormats = { static const std::unordered_map sCompressionTypes = { { "MIO0", CompressionType::MIO0 }, { "YAY0", CompressionType::YAY0 }, + { "YAY1", CompressionType::YAY1 }, { "YAZ0", CompressionType::YAZ0 }, }; @@ -86,6 +89,16 @@ ExportResult CompressedTextureHeaderExporter::Export(std::ostream &write, std::s compressedData = static_cast(std::calloc(worstSize, sizeof(uint8_t))); compressedSize = mio0_encode(data.data(), data.size(), compressedData); break; + case CompressionType::YAY0: + worstSize = YAY0_HEADER_LENGTH + ((data.size()+7)/8) + data.size(); + compressedData = static_cast(std::calloc(worstSize, sizeof(uint8_t))); + compressedSize = yay0_encode(data.data(), data.size(), compressedData); + break; + case CompressionType::YAY1: + worstSize = YAY1_HEADER_LENGTH + ((data.size()+7)/8) + data.size(); + compressedData = static_cast(std::calloc(worstSize, sizeof(uint8_t))); + compressedSize = yay1_encode(data.data(), data.size(), compressedData); + break; default: // UNIMPLEMENTED throw std::runtime_error("Unsupported Compressed Texture Type"); @@ -151,6 +164,16 @@ ExportResult CompressedTextureCodeExporter::Export(std::ostream &write, std::sha compressedData = static_cast(std::calloc(worstSize, sizeof(uint8_t))); compressedSize = mio0_encode(data.data(), data.size(), compressedData); break; + case CompressionType::YAY0: + worstSize = YAY0_HEADER_LENGTH + ((data.size()+7)/8) + data.size(); + compressedData = static_cast(std::calloc(worstSize, sizeof(uint8_t))); + compressedSize = yay0_encode(data.data(), data.size(), compressedData); + break; + case CompressionType::YAY1: + worstSize = YAY1_HEADER_LENGTH + ((data.size()+7)/8) + data.size(); + compressedData = static_cast(std::calloc(worstSize, sizeof(uint8_t))); + compressedSize = yay1_encode(data.data(), data.size(), compressedData); + break; default: // UNIMPLEMENTED throw std::runtime_error("Unsupported Compressed Texture Type"); @@ -329,6 +352,8 @@ std::string getcomptype(CompressionType type) { return "MIO0"; case CompressionType::YAY0: return "YAY0"; + case CompressionType::YAY1: + return "YAY1"; case CompressionType::YAZ0: return "YAZ0"; default: @@ -350,7 +375,7 @@ std::optional> CompressedTextureFactory::parse(std: if (!sCompressionTypes.contains(compression)) { SPDLOG_ERROR("Compresed Texture entry at {:X} in yaml missing compression type\n\ Please add one of the following compression types\n\ - MIO0, YAY0 (Unsupported), YAZ0 (Unsupported)", offset); + MIO0, YAY0, YAY1, YAZ0 (Unsupported)", offset); return std::nullopt; } compressionType = sCompressionTypes.at(compression); @@ -447,7 +472,7 @@ std::optional> CompressedTextureFactory::parse_modd if (!sCompressionTypes.contains(compression)) { SPDLOG_ERROR("Compresed Texture entry at {:X} in yaml missing compression type\n\ Please add one of the following compression types\n\ - MIO0, YAY0 (Unsupported), YAZ0 (Unsupported)", offset); + MIO0, YAY0, YAY1, YAZ0 (Unsupported)", offset); return std::nullopt; } compressionType = sCompressionTypes.at(compression); diff --git a/src/factories/mario_artist/MA2D1Factory.cpp b/src/factories/mario_artist/MA2D1Factory.cpp new file mode 100644 index 0000000..b536a9e --- /dev/null +++ b/src/factories/mario_artist/MA2D1Factory.cpp @@ -0,0 +1,107 @@ +#include "MA2D1Factory.h" +#include "spdlog/spdlog.h" + +#include "Companion.h" +#include "utils/Decompressor.h" +#include "utils/TorchUtils.h" + +ExportResult MA::MA2D1HeaderExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement) { + const auto symbol = GetSafeNode(node, "symbol", entryName); + + return std::nullopt; +} + +ExportResult MA::MA2D1CodeExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + const auto symbol = GetSafeNode(node, "symbol", entryName); + const auto offset = GetSafeNode(node, "offset"); + const auto data = std::static_pointer_cast(raw); + + write << "char " << symbol << "_header" << "[] = { "; + + for (size_t i = 0; i < data->mFormat.size(); i++) { + if (i != 0) { + write << ", "; + } + + write << "\'" << data->mFormat.at(i) << "\'"; + } + + write << ", \'" << ((data->mWidth / 100) % 10) << "\'"; + write << ", \'" << ((data->mWidth / 10) % 10) << "\'"; + write << ", \'" << ((data->mWidth / 1) % 10) << "\'"; + + write << ", \'" << ((data->mHeight / 100) % 10) << "\'"; + write << ", \'" << ((data->mHeight / 10) % 10) << "\'"; + write << ", \'" << ((data->mHeight / 1) % 10) << "\'"; + + write << ", \'" << ((data->mSize / 100000) % 10) << "\'"; + write << ", \'" << ((data->mSize / 10000) % 10) << "\'"; + write << ", \'" << ((data->mSize / 1000) % 10) << "\'"; + write << ", \'" << ((data->mSize / 100) % 10) << "\'"; + write << ", \'" << ((data->mSize / 10) % 10) << "\'"; + write << ", \'" << ((data->mSize / 1) % 10) << "\'"; + + write << " };\n\n"; + + return offset + 0x10; +} + +ExportResult MA::MA2D1BinaryExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + // Nothing Required Here For Binary Exporting + + return std::nullopt; +} + +std::optional> MA::MA2D1Factory::parse(std::vector& buffer, YAML::Node& node) { + auto [_, segment] = Decompressor::AutoDecode(node, buffer); + const auto offset = GetSafeNode(node, "offset"); + const auto symbol = GetSafeNode(node, "symbol"); + LUS::BinaryReader reader(segment.data, segment.size); + + reader.SetEndianness(Torch::Endianness::Big); + + YAML::Node thumbnail; + thumbnail["type"] = "TEXTURE"; + thumbnail["ctype"] = "u16"; + thumbnail["format"] = "RGBA16"; + thumbnail["width"] = 24; + thumbnail["height"] = 24; + thumbnail["offset"] = offset; + thumbnail["symbol"] = symbol + "_thumb"; + Companion::Instance->AddAsset(thumbnail); + + reader.Seek(0x480, LUS::SeekOffsetType::Start); + + char headerBuffer[0x10]; + + for (size_t i = 0; i < sizeof(headerBuffer); i++) { + headerBuffer[i] = reader.ReadChar(); + } + + // Override offset + node["offset"] = offset + 0x480; + + std::string format(headerBuffer, headerBuffer + 4); + + uint32_t width = std::stoi(std::string(headerBuffer + 4, 3)); + uint32_t height = std::stoi(std::string(headerBuffer + 7, 3)); + uint32_t size = std::stoi(std::string(headerBuffer + 10, 6)); + + YAML::Node image; + if (format == "NCMP") { + image["type"] = "COMPRESSED_TEXTURE"; + image["compression"] = "YAY1"; + } else { + image["type"] = "TEXTURE"; + } + + image["ctype"] = "u16"; + image["format"] = "RGBA16"; + image["width"] = width; + image["height"] = height; + image["offset"] = offset + 0x490; + image["symbol"] = symbol + "_image"; + Companion::Instance->AddAsset(image); + + return std::make_shared(format, width, height, size); +} diff --git a/src/factories/mario_artist/MA2D1Factory.h b/src/factories/mario_artist/MA2D1Factory.h new file mode 100644 index 0000000..2d6b971 --- /dev/null +++ b/src/factories/mario_artist/MA2D1Factory.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +namespace MA { + +class MA2D1Data : public IParsedData { +public: + std::string mFormat; + uint32_t mWidth; + uint32_t mHeight; + uint32_t mSize; + + MA2D1Data(std::string format, uint32_t width, uint32_t height, uint32_t size) : + mFormat(format), + mWidth(width), + mHeight(height), + mSize(size) {} +}; + +class MA2D1HeaderExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class MA2D1BinaryExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class MA2D1CodeExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class MA2D1Factory : public BaseFactory { +public: + std::optional> parse(std::vector& buffer, YAML::Node& data) override; + inline std::unordered_map> GetExporters() override { + return { + REGISTER(Code, MA2D1CodeExporter) + REGISTER(Header, MA2D1HeaderExporter) + REGISTER(Binary, MA2D1BinaryExporter) + }; + } + bool HasModdedDependencies() override { return true; } +}; +} diff --git a/src/utils/Decompressor.cpp b/src/utils/Decompressor.cpp index 1fee382..5139fcc 100644 --- a/src/utils/Decompressor.cpp +++ b/src/utils/Decompressor.cpp @@ -7,6 +7,7 @@ extern "C" { #include #include +#include #include } @@ -43,6 +44,17 @@ DataChunk* Decompressor::Decode(const std::vector& buffer, const uint32 gCachedChunks[offset] = new DataChunk{ decompressed, size }; return gCachedChunks[offset]; } + case CompressionType::YAY1: { + uint32_t size = 0; + uint8_t* decompressed = yay1_decode(in_buf, &size); + + if(!decompressed){ + throw std::runtime_error("Failed to decode YAY1"); + } + + gCachedChunks[offset] = new DataChunk{ decompressed, size }; + return gCachedChunks[offset]; + } default: throw std::runtime_error("Unknown compression type"); } @@ -106,6 +118,7 @@ DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector // Extract a compressed file which contains many assets. switch(type) { case CompressionType::YAY0: + case CompressionType::YAY1: case CompressionType::MIO0: { offset = ASSET_PTR(offset); @@ -181,6 +194,10 @@ CompressionType Decompressor::GetCompressionType(std::vector& buffer, c return CompressionType::YAY0; } + if (header == "Yay1") { + return CompressionType::YAY1; + } + if (header == "Yaz0") { return CompressionType::YAZ0; } diff --git a/src/utils/Decompressor.h b/src/utils/Decompressor.h index 4aff942..22f4b24 100644 --- a/src/utils/Decompressor.h +++ b/src/utils/Decompressor.h @@ -12,6 +12,7 @@ enum class CompressionType { None, MIO0, YAY0, + YAY1, YAZ0, };