From 6ccdeaa226394b68732ace6dd525a19a62e3c575 Mon Sep 17 00:00:00 2001 From: KiritoDv Date: Fri, 11 Oct 2024 13:54:38 -0600 Subject: [PATCH] Added Yay0 support with auto detection for pkmn-stadium files --- lib/libyay0/yay0.c | 72 ++++++++++++++++++++++++++++++++++++++ lib/libyay0/yay0.h | 5 +++ src/Companion.cpp | 27 ++------------ src/Companion.h | 1 - src/utils/Decompressor.cpp | 65 +++++++++++++++++++++++----------- src/utils/Decompressor.h | 1 + 6 files changed, 125 insertions(+), 46 deletions(-) create mode 100644 lib/libyay0/yay0.c create mode 100644 lib/libyay0/yay0.h diff --git a/lib/libyay0/yay0.c b/lib/libyay0/yay0.c new file mode 100644 index 0000000..6035948 --- /dev/null +++ b/lib/libyay0/yay0.c @@ -0,0 +1,72 @@ +#include "yay0.h" +#include "libmio0/utils.h" +#include +#include + +uint8_t* yay0_decode(const uint8_t* in_buf, uint32_t* out_size){ + + const uint8_t* in = in_buf; + const char* magic = (const char*) in; + if(!strncmp(magic, "PERS-SZP", 8)){ + uint32_t header_size = read_u32_be(in + 8); + magic += header_size; + in += header_size; + } + + if(strncmp(magic, "Yay0", 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; +} \ No newline at end of file diff --git a/lib/libyay0/yay0.h b/lib/libyay0/yay0.h new file mode 100644 index 0000000..7b101ab --- /dev/null +++ b/lib/libyay0/yay0.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern uint8_t* yay0_decode(const uint8_t* in, uint32_t* out_size); \ No newline at end of file diff --git a/src/Companion.cpp b/src/Companion.cpp index f489317..8c4476d 100644 --- a/src/Companion.cpp +++ b/src/Companion.cpp @@ -339,7 +339,7 @@ void Companion::ParseCurrentFileConfig(YAML::Node node) { if (segments[0].IsSequence() && segments[0].size() == 2) { gCurrentSegmentNumber = segments[0][0].as(); gCurrentFileOffset = segments[0][1].as(); - gCurrentCompressionType = GetCompressionType(this->gRomData, gCurrentFileOffset); + gCurrentCompressionType = Decompressor::GetCompressionType(this->gRomData, gCurrentFileOffset); if(node["no_compression"]) { gCurrentCompressionType = CompressionType::None; } @@ -518,7 +518,7 @@ void Companion::ProcessFile(YAML::Node root) { if (segments[0].IsSequence() && segments[0].size() == 2) { gCurrentSegmentNumber = segments[0][0].as(); gCurrentFileOffset = segments[0][1].as(); - gCurrentCompressionType = GetCompressionType(this->gRomData, gCurrentFileOffset); + gCurrentCompressionType = Decompressor::GetCompressionType(this->gRomData, gCurrentFileOffset); if(root[":config"]["no_compression"]) { gCurrentCompressionType = CompressionType::None; } @@ -1263,29 +1263,6 @@ std::optional> Companion::GetFactory(const std::str return this->gFactories[type]; } -/** - * @param offset Rom offset of compressed mio0 file. - * @returns CompressionType - */ -CompressionType Companion::GetCompressionType(std::vector& buffer, const uint32_t offset) { - if (offset) { - LUS::BinaryReader reader((char*) buffer.data() + offset, sizeof(uint32_t)); - reader.SetEndianness(Torch::Endianness::Big); - - const std::string header = reader.ReadCString(); - - // Check if a compressed header exists - if (header == "MIO0") { - return CompressionType::MIO0; - } else if (header == "YAY0") { - return CompressionType::YAY0; - } else if (header == "YAZ0") { - return CompressionType::YAZ0; - } - } - return CompressionType::None; -} - std::optional Companion::SearchTable(uint32_t addr){ for(auto& table : this->gTables){ if(addr >= table.start && addr <= table.end){ diff --git a/src/Companion.h b/src/Companion.h index e99719f..e039726 100644 --- a/src/Companion.h +++ b/src/Companion.h @@ -147,7 +147,6 @@ public: std::optional GetFileOffset(void) const { return this->gCurrentFileOffset; }; std::optional GetCurrSegmentNumber(void) const { return this->gCurrentSegmentNumber; }; CompressionType GetCurrCompressionType(void) const { return this->gCurrentCompressionType; }; - CompressionType GetCompressionType(std::vector& buffer, const uint32_t offset); std::optional GetCurrentVRAM(void) const { return this->gCurrentVram; }; std::optional
SearchTable(uint32_t addr); diff --git a/src/utils/Decompressor.cpp b/src/utils/Decompressor.cpp index 840a901..011fad4 100644 --- a/src/utils/Decompressor.cpp +++ b/src/utils/Decompressor.cpp @@ -6,6 +6,7 @@ extern "C" { #include +#include #include } @@ -31,13 +32,23 @@ DataChunk* Decompressor::Decode(const std::vector& buffer, const uint32 gCachedChunks[offset] = new DataChunk{ decompressed, head.dest_size }; return gCachedChunks[offset]; } + case CompressionType::YAY0: { + uint32_t size = 0; + uint8_t* decompressed = yay0_decode(in_buf, &size); + + if(!decompressed){ + throw std::runtime_error("Failed to decode YAY0"); + } + + gCachedChunks[offset] = new DataChunk{ decompressed, size }; + return gCachedChunks[offset]; + } default: throw std::runtime_error("Unknown compression type"); } } 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]; } @@ -59,18 +70,13 @@ DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector auto fileOffset = TranslateAddr(offset, true); // Extract compressed assets. - if (node["mio0"]) { - auto assetPtr = ASSET_PTR(offset); - auto gameSize = Companion::Instance->GetRomData().size(); - - auto fileOffset = TranslateAddr(offset, true); - offset = ASSET_PTR(offset); - - auto decoded = Decode(buffer, fileOffset + offset, CompressionType::MIO0); + if (node["mio0"] || node["yay0"]) { + auto foffset = TranslateAddr(offset, true); + auto decoded = Decode(buffer, foffset, node["mio0"] ? CompressionType::MIO0 : CompressionType::YAY0); auto size = node["size"] ? node["size"].as() : manualSize.value_or(decoded->size); return { - .root = decoded, - .segment = { decoded->data, size } + .root = decoded, + .segment = { decoded->data, size } }; } @@ -80,8 +86,6 @@ DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector 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); @@ -89,26 +93,24 @@ DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector 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 } + .root = decoded, + .segment = { decoded->data, size } }; } // Extract compressed files that contain many assets. switch(type) { - case CompressionType::MIO0: - { + case CompressionType::YAY0: + case CompressionType::MIO0: { offset = ASSET_PTR(offset); - auto decoded = Decode(buffer, fileOffset, CompressionType::MIO0); + auto decoded = Decode(buffer, fileOffset, type); auto size = node["size"] ? node["size"].as() : manualSize.value_or(decoded->size - offset); return { .root = decoded, .segment = { decoded->data + offset, size } }; } - case CompressionType::YAY0: - throw std::runtime_error("Found compressed yay0 segment.\nDecompression of yay0 has not been implemented yet."); case CompressionType::YAZ0: throw std::runtime_error("Found compressed yaz0 segment.\nDecompression of yaz0 has not been implemented yet."); case CompressionType::None: // The data does not have compression @@ -158,6 +160,29 @@ uint32_t Decompressor::TranslateAddr(uint32_t addr, bool baseAddress){ return addr; } +CompressionType Decompressor::GetCompressionType(std::vector& buffer, const uint32_t offset) { + if (offset) { + LUS::BinaryReader reader((char*) buffer.data() + offset, sizeof(uint32_t)); + reader.SetEndianness(Torch::Endianness::Big); + + const std::string header = reader.ReadCString(); + + // Check if a compressed header exists + if (header == "MIO0") { + return CompressionType::MIO0; + } + + if (header == "Yay0" || header == "PERS") { + return CompressionType::YAY0; + } + + if (header == "Yaz0") { + return CompressionType::YAZ0; + } + } + return CompressionType::None; +} + bool Decompressor::IsSegmented(uint32_t addr) { if(IS_SEGMENTED(addr)){ const auto segment = Companion::Instance->GetFileOffsetFromSegmentedAddr(SEGMENT_NUMBER(addr)); diff --git a/src/utils/Decompressor.h b/src/utils/Decompressor.h index 768ee17..4d2056a 100644 --- a/src/utils/Decompressor.h +++ b/src/utils/Decompressor.h @@ -36,6 +36,7 @@ public: 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 CompressionType GetCompressionType(std::vector& buffer, const uint32_t offset); static uint32_t TranslateAddr(uint32_t addr, bool baseAddress = false); static bool IsSegmented(uint32_t addr);