From 89fd2f9ac9b69d5f6b77dca9347aa61f9850d3b4 Mon Sep 17 00:00:00 2001 From: Jeod <47716344+JeodC@users.noreply.github.com> Date: Sat, 20 Jun 2026 16:30:58 -0400 Subject: Add bk_zip library and BK64 decompression foundation --- src/utils/Decompressor.cpp | 125 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 96 insertions(+), 29 deletions(-) (limited to 'src/utils/Decompressor.cpp') diff --git a/src/utils/Decompressor.cpp b/src/utils/Decompressor.cpp index 2cf9412..f9c800c 100644 --- a/src/utils/Decompressor.cpp +++ b/src/utils/Decompressor.cpp @@ -2,6 +2,7 @@ #include "TorchUtils.h" #include +#include #include "spdlog/spdlog.h" #include @@ -12,13 +13,19 @@ extern "C" { #include } +#include + std::unordered_map gCachedChunks; +std::mutex gDecompCacheMutex; DataChunk* Decompressor::Decode(const std::vector& buffer, const uint32_t offset, const CompressionType type, - bool ignoreCache) { + const uint32_t in_size, bool ignoreCache) { - if (!ignoreCache && Torch::contains(gCachedChunks, offset)) { - return gCachedChunks[offset]; + { + std::lock_guard lock(gDecompCacheMutex); + if (!ignoreCache && Torch::contains(gCachedChunks, offset)) { + return gCachedChunks[offset]; + } } const unsigned char* in_buf = buffer.data() + offset; @@ -32,8 +39,11 @@ DataChunk* Decompressor::Decode(const std::vector& buffer, const uint32 const auto decompressed = new uint8_t[head.dest_size]; mio0_decode(in_buf, decompressed, nullptr); - gCachedChunks[offset] = new DataChunk{ decompressed, head.dest_size }; - return gCachedChunks[offset]; + { + std::lock_guard lock(gDecompCacheMutex); + gCachedChunks[offset] = new DataChunk{ decompressed, head.dest_size }; + return gCachedChunks[offset]; + } } case CompressionType::YAY0: { uint32_t size = 0; @@ -43,8 +53,11 @@ DataChunk* Decompressor::Decode(const std::vector& buffer, const uint32 throw std::runtime_error("Failed to decode YAY0"); } - gCachedChunks[offset] = new DataChunk{ decompressed, size }; - return gCachedChunks[offset]; + { + std::lock_guard lock(gDecompCacheMutex); + gCachedChunks[offset] = new DataChunk{ decompressed, size }; + return gCachedChunks[offset]; + } } case CompressionType::YAY1: { uint32_t size = 0; @@ -54,8 +67,30 @@ DataChunk* Decompressor::Decode(const std::vector& buffer, const uint32 throw std::runtime_error("Failed to decode YAY1"); } - gCachedChunks[offset] = new DataChunk{ decompressed, size }; - return gCachedChunks[offset]; + { + std::lock_guard lock(gDecompCacheMutex); + gCachedChunks[offset] = new DataChunk{ decompressed, size }; + return gCachedChunks[offset]; + } + } + case CompressionType::BKZIP: { + uint32_t size = in_size; + try { + uint8_t* decompressed = BK64::bk_unzip(in_buf, &size); + + if (!decompressed) { + throw std::runtime_error("bk_unzip returned null"); + } + + { + std::lock_guard lock(gDecompCacheMutex); + gCachedChunks[offset] = new DataChunk{ decompressed, size }; + return gCachedChunks[offset]; + } + } catch (const std::exception& e) { + throw std::runtime_error(std::string(e.what()) + " (ROM offset 0x" + Torch::to_hex(offset, false) + + " compressed size 0x" + Torch::to_hex(in_size, false) + ")"); + } } default: throw std::runtime_error("Unknown compression type"); @@ -64,8 +99,11 @@ 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 (Torch::contains(gCachedChunks, offset)) { - return gCachedChunks[offset]; + { + std::lock_guard lock(gDecompCacheMutex); + if (Torch::contains(gCachedChunks, offset)) { + return gCachedChunks[offset]; + } } const uint8_t* in_buf = buffer.data() + offset; @@ -73,8 +111,11 @@ DataChunk* Decompressor::DecodeTKMK00(const std::vector& buffer, const const auto decompressed = new uint8_t[size]; const auto rgba = new uint8_t[size]; tkmk00_decode(in_buf, decompressed, rgba, alpha); - gCachedChunks[offset] = new DataChunk{ rgba, size }; - return gCachedChunks[offset]; + { + std::lock_guard lock(gDecompCacheMutex); + gCachedChunks[offset] = new DataChunk{ rgba, size }; + return gCachedChunks[offset]; + } } DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector& buffer, @@ -84,16 +125,14 @@ DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector CompressionType type = Companion::Instance->GetCurrCompressionType(); auto fileOffset = TranslateAddr(offset, true); + auto offsetFromFile = TranslateAddr(offset, false) - fileOffset; // Check if an asset in a yaml file is mio0 compressed and extract. 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); + auto decoded = Decode(buffer, fileOffset + offsetFromFile, CompressionType::MIO0); size_t decodedSize = decoded->size - offset; size_t size; @@ -144,7 +183,12 @@ DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector size, decodedSize, assetPtr); size = decodedSize; } - + return { .root = decoded, .segment = { decoded->data, size } }; + } + if (node["bkzip"]) { + const auto compressedSize = GetSafeNode(node, "compressed_size"); + auto decoded = Decode(buffer, fileOffset + offsetFromFile, CompressionType::BKZIP, compressedSize); + auto size = node["size"] ? node["size"].as() : manualSize.value_or(decoded->size); return { .root = decoded, .segment = { decoded->data, size } }; } @@ -176,6 +220,20 @@ DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector return { .root = decoded, .segment = { decoded->data + offset, size } }; } + case CompressionType::BKZIP: { + const auto sizeEntry = Companion::Instance->GetCurrentCompressedSize(); + + if (!sizeEntry.has_value()) { + throw std::runtime_error("Auto decode missing file compressed size."); + } + + auto decoded = Decode(buffer, fileOffset, type, sizeEntry.value()); + auto size = node["size"] ? node["size"].as() : manualSize.value_or(decoded->size - offsetFromFile); + SPDLOG_INFO("AutoDecode BKZIP: offset=0x{:X} fileOffset=0x{:X} offsetFromFile=0x{:X} compSize=0x{:X} " + "decodedSize=0x{:X} segSize=0x{:X}", + offset, fileOffset, offsetFromFile, sizeEntry.value(), decoded->size, size); + return { .root = decoded, .segment = { decoded->data + offsetFromFile, size } }; + } case CompressionType::YAZ0: throw std::runtime_error( "Found compressed yaz0 segment.\nDecompression of yaz0 has not been implemented yet."); @@ -203,10 +261,9 @@ DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector return { .root = nullptr, .segment = { buffer.data() + fileOffset, size } }; } + default: + throw std::runtime_error("Auto decode could not find a supported compression type."); } - - throw std::runtime_error("Auto decode could not find a compression type nor uncompressed segment.\nThis is one of " - "those issues that should never really happen."); } DecompressedData Decompressor::AutoDecode(uint32_t offset, std::optional size, std::vector& buffer) { @@ -220,9 +277,15 @@ uint32_t Decompressor::TranslateAddr(uint32_t addr, bool baseAddress) { if (IS_SEGMENTED(addr)) { const auto segment = Companion::Instance->GetFileOffsetFromSegmentedAddr(SEGMENT_NUMBER(addr)); if (!segment.has_value()) { - SPDLOG_ERROR("Segment data missing from game config\nPlease add an entry for segment {}", - SEGMENT_NUMBER(addr)); - return 0; + const auto compressedSegmentPair = + Companion::Instance->GetFileOffsetFromCompressedSegmentedAddr(SEGMENT_NUMBER(addr)); + if (!compressedSegmentPair.has_value()) { + SPDLOG_ERROR("Segment data missing from game config\nPlease add an entry for segment {}", + SEGMENT_NUMBER(addr)); + return 0; + } + return compressedSegmentPair.value().first + + (!baseAddress ? (compressedSegmentPair.value().second + SEGMENT_OFFSET(addr)) : 0); } return segment.value() + (!baseAddress ? SEGMENT_OFFSET(addr) : 0); @@ -234,7 +297,7 @@ uint32_t Decompressor::TranslateAddr(uint32_t addr, bool baseAddress) { const auto vram = vramEntry.value(); if (addr >= vram.addr) { - return vram.offset + (addr - vram.addr); + return vram.offset + (!baseAddress ? (addr - vram.addr) : 0); } } @@ -273,11 +336,14 @@ bool Decompressor::IsSegmented(uint32_t addr) { const auto segment = Companion::Instance->GetFileOffsetFromSegmentedAddr(SEGMENT_NUMBER(addr)); if (!segment.has_value()) { - SPDLOG_ERROR("Segment data missing from game config\nPlease add an entry for segment {}", - SEGMENT_NUMBER(addr)); - return false; + const auto compressedSegmentPair = + Companion::Instance->GetFileOffsetFromCompressedSegmentedAddr(SEGMENT_NUMBER(addr)); + if (!compressedSegmentPair.has_value()) { + SPDLOG_ERROR("Segment data missing from game config\nPlease add an entry for segment {}", + SEGMENT_NUMBER(addr)); + return false; + } } - return true; } @@ -285,6 +351,7 @@ bool Decompressor::IsSegmented(uint32_t addr) { } void Decompressor::ClearCache() { + std::lock_guard lock(gDecompCacheMutex); for (auto& [key, value] : gCachedChunks) { delete[] value->data; } -- cgit v1.2.3