diff options
| -rw-r--r-- | src/Companion.cpp | 1 | ||||
| -rw-r--r-- | src/factories/DisplayListFactory.cpp | 10 | ||||
| -rw-r--r-- | src/factories/VtxFactory.cpp | 9 | ||||
| -rw-r--r-- | src/factories/bk64/ModelFactory.cpp | 11 | ||||
| -rw-r--r-- | src/factories/bk64/SoundfontTblFactory.cpp | 100 | ||||
| -rw-r--r-- | src/factories/bk64/SoundfontTblFactory.h | 11 | ||||
| -rw-r--r-- | src/utils/Decompressor.cpp | 19 |
7 files changed, 152 insertions, 9 deletions
diff --git a/src/Companion.cpp b/src/Companion.cpp index 59d42a1..27b2472 100644 --- a/src/Companion.cpp +++ b/src/Companion.cpp @@ -268,6 +268,7 @@ void Companion::Init(const ExportType type, std::atomic<size_t>& assetCount, boo this->RegisterFactory("BK64:MAP", std::make_shared<BK64::MapFactory>()); this->RegisterFactory("BK64:QUIZQ", std::make_shared<BK64::QuizQuestionFactory>()); this->RegisterFactory("BK64:MODEL", std::make_shared<BK64::ModelFactory>()); + this->RegisterFactory("BK64:SOUNDFONT_CTL", std::make_shared<BK64::SoundfontCtlFactory>()); this->RegisterFactory("BK64:SOUNDFONT_TBL", std::make_shared<BK64::SoundfontTblFactory>()); this->RegisterFactory("BK64:SPRITE", std::make_shared<BK64::SpriteFactory>()); #endif diff --git a/src/factories/DisplayListFactory.cpp b/src/factories/DisplayListFactory.cpp index ce4e77d..2d6d48e 100644 --- a/src/factories/DisplayListFactory.cpp +++ b/src/factories/DisplayListFactory.cpp @@ -523,6 +523,16 @@ std::optional<std::shared_ptr<IParsedData>> DListFactory::parse(std::vector<uint size_t length = 0; while (processing) { + // Some DLs can jump to offsets that never reach a G_ENDDL; stop at + // the buffer edge instead of reading past it and synthesize the terminator. + if (reader.GetBaseAddress() + 2 * sizeof(uint32_t) > segment.size) { + SPDLOG_WARN("DL at 0x{:X} ran off the end of its buffer without G_ENDDL; truncating", + GetSafeNode<uint32_t>(node, "offset")); + gfxs.push_back(static_cast<uint32_t>(GBI(G_ENDDL)) << 24); + gfxs.push_back(0); + break; + } + auto w0 = reader.ReadUInt32(); auto w1 = reader.ReadUInt32(); diff --git a/src/factories/VtxFactory.cpp b/src/factories/VtxFactory.cpp index 8cb6efb..f0790ef 100644 --- a/src/factories/VtxFactory.cpp +++ b/src/factories/VtxFactory.cpp @@ -159,6 +159,15 @@ std::optional<std::shared_ptr<IParsedData>> VtxFactory::parse(std::vector<uint8_ auto count = GetSafeNode<size_t>(node, "count"); auto [_, segment] = Decompressor::AutoDecode(node, buffer); + + // Garbage G_VTX commands in DLs can autogen a count that overruns the file; + // clamp to what the segment actually holds before the reader copies it. + if (count * sizeof(VtxRaw) > segment.size) { + SPDLOG_WARN("VTX at 0x{:X}: count {} needs 0x{:X} bytes but segment holds 0x{:X}; clamping to {}", + GetSafeNode<uint32_t>(node, "offset"), count, count * sizeof(VtxRaw), segment.size, + segment.size / sizeof(VtxRaw)); + count = segment.size / sizeof(VtxRaw); + } LUS::BinaryReader reader(segment.data, count * sizeof(VtxRaw)); reader.SetEndianness(Torch::Endianness::Big); diff --git a/src/factories/bk64/ModelFactory.cpp b/src/factories/bk64/ModelFactory.cpp index 71977bc..b0b3f6d 100644 --- a/src/factories/bk64/ModelFactory.cpp +++ b/src/factories/bk64/ModelFactory.cpp @@ -700,7 +700,16 @@ std::optional<std::shared_ptr<IParsedData>> ModelFactory::parse(std::vector<uint // catches the sequential sub-lists; an intra-buffer G_DL can jump to some arbitrary // offset that no G_ENDDL precedes. if (opCode == GBI(G_DL) && SEGMENT_NUMBER(w1) == 3) { - dlOffsets.emplace(SEGMENT_OFFSET(w1)); + // Some jump targets may be garbage; only split on ones that land + // inside the DL section on a command boundary, or the resulting GFX asset + // parses off the end of the file. + uint32_t target = SEGMENT_OFFSET(w1); + if (target < dlCount * GFX_CMD_SIZE && (target % GFX_CMD_SIZE) == 0) { + dlOffsets.emplace(target); + } else { + SPDLOG_WARN("[BKModel] {} G_DL target 0x{:X} outside DL section (size 0x{:X}); skipping split", + symbol, target, dlCount * GFX_CMD_SIZE); + } } } } diff --git a/src/factories/bk64/SoundfontTblFactory.cpp b/src/factories/bk64/SoundfontTblFactory.cpp index 99d5b76..4bfec77 100644 --- a/src/factories/bk64/SoundfontTblFactory.cpp +++ b/src/factories/bk64/SoundfontTblFactory.cpp @@ -39,11 +39,14 @@ int32_t ReadS32BE(const uint8_t* p) { // ALInstrument:[14] s16 soundCount, [16+] u32 soundOffsets[] // ALSound: [8] u32 wavetable // ALWaveTable: [0] u32 base, [4] s32 len -size_t ComputeTblSize(const uint8_t* ctl, size_t ctlSize, uint32_t ctlRomOffset) { +size_t ComputeTblSize(const uint8_t* ctl, size_t ctlSize, uint32_t ctlRomOffset, bool quiet = false) { auto check = [&](uint32_t off, size_t need, const char* what) { if ((size_t)off + need > ctlSize) { - SPDLOG_ERROR("SoundfontTblFactory: ctl walk OOB reading {} (ctl@0x{:X} size=0x{:X} off=0x{:X} need=0x{:X})", - what, ctlRomOffset, ctlSize, off, need); + if (!quiet) { + SPDLOG_ERROR( + "SoundfontTblFactory: ctl walk OOB reading {} (ctl@0x{:X} size=0x{:X} off=0x{:X} need=0x{:X})", + what, ctlRomOffset, ctlSize, off, need); + } throw std::runtime_error("SoundfontTblFactory: ctl walk OOB"); } }; @@ -113,26 +116,107 @@ size_t ComputeTblSize(const uint8_t* ctl, size_t ctlSize, uint32_t ctlRomOffset) return (size_t)((maxEnd + 0xF) & ~(uint64_t)0xF); } +// AL_BANK_VERSION magic +constexpr uint16_t kAlBankRevision = 0x4231; + +bool ValidateCtl(const uint8_t* ctl, size_t ctlSize) { + if (ctlSize < 8) { + return false; + } + if (ReadU16BE(ctl) != kAlBankRevision) { + return false; + } + int16_t bankCount = ReadS16BE(ctl + 2); + if (bankCount <= 0 || bankCount > 16) { + return false; + } + uint32_t bank0 = ReadU32BE(ctl + 4); + if (bank0 != 0 && (bank0 < 4u + 4u * bankCount || bank0 >= ctlSize)) { + return false; + } + try { + ComputeTblSize(ctl, ctlSize, 0, /*quiet=*/true); + } catch (...) { + return false; + } + return true; +} + } // namespace +uint32_t LocateSoundfontCtl(const std::vector<uint8_t>& rom, uint32_t ctlOffset, uint32_t ctlSize) { + if ((size_t)ctlOffset + ctlSize <= rom.size() && ValidateCtl(rom.data() + ctlOffset, ctlSize)) { + return ctlOffset; + } + + // Romhacks can shift the whole audio region; hunt + // for the real ALBankFile header instead of failing. + std::vector<uint32_t> candidates; + for (size_t i = 0; i + 8 <= rom.size(); i += 8) { + if (rom[i] != 0x42 || rom[i + 1] != 0x31) { + continue; + } + size_t avail = std::min<size_t>(ctlSize, rom.size() - i); + if (ValidateCtl(rom.data() + i, avail)) { + candidates.push_back((uint32_t)i); + } + } + + if (candidates.empty()) { + SPDLOG_ERROR("SoundfontCtl: no valid ALBankFile found anywhere in ROM (vanilla ctl@0x{:X} size=0x{:X})", + ctlOffset, ctlSize); + throw std::runtime_error("SoundfontCtl: soundfont ctl not found in ROM"); + } + + uint32_t best = candidates[0]; + for (uint32_t c : candidates) { + auto dist = [&](uint32_t off) { return off > ctlOffset ? off - ctlOffset : ctlOffset - off; }; + if (dist(c) < dist(best)) { + best = c; + } + } + + SPDLOG_WARN("SoundfontCtl: ctl not at vanilla offset 0x{:X}; relocated to 0x{:X} (delta 0x{:X}, {} candidates)", + ctlOffset, best, (uint32_t)(best - ctlOffset), candidates.size()); + return best; +} + +std::optional<std::shared_ptr<IParsedData>> SoundfontCtlFactory::parse(std::vector<uint8_t>& buffer, YAML::Node& node) { + const auto offset = GetSafeNode<uint32_t>(node, "offset"); + const auto size = GetSafeNode<uint32_t>(node, "size"); + + const uint32_t realOffset = LocateSoundfontCtl(buffer, offset, size); + if ((size_t)realOffset + size > buffer.size()) { + throw std::runtime_error("SoundfontCtlFactory: ctl exceeds ROM size"); + } + + return std::make_shared<RawBuffer>(buffer.data() + realOffset, size); +} + std::optional<std::shared_ptr<IParsedData>> SoundfontTblFactory::parse(std::vector<uint8_t>& buffer, YAML::Node& node) { const auto tblOffset = GetSafeNode<uint32_t>(node, "offset"); const auto ctlOffset = GetSafeNode<uint32_t>(node, "ctl_offset"); const auto ctlSize = GetSafeNode<uint32_t>(node, "ctl_size"); - if ((size_t)ctlOffset + ctlSize > buffer.size()) { + // The tbl sits right after the ctl, so it shifts by + // the same delta when a romhack relocates the audio region. + const uint32_t realCtlOffset = LocateSoundfontCtl(buffer, ctlOffset, ctlSize); + const uint32_t realTblOffset = tblOffset + (realCtlOffset - ctlOffset); + + if ((size_t)realCtlOffset + ctlSize > buffer.size()) { throw std::runtime_error("SoundfontTblFactory: ctl_offset + ctl_size exceeds ROM size"); } - const size_t tblSize = ComputeTblSize(buffer.data() + ctlOffset, ctlSize, ctlOffset); + const size_t tblSize = ComputeTblSize(buffer.data() + realCtlOffset, ctlSize, realCtlOffset); - if ((size_t)tblOffset + tblSize > buffer.size()) { + if ((size_t)realTblOffset + tblSize > buffer.size()) { throw std::runtime_error("SoundfontTblFactory: computed tbl size exceeds ROM bounds"); } - SPDLOG_INFO("SoundfontTbl: tbl@0x{:X} size 0x{:X} (computed from ctl@0x{:X})", tblOffset, tblSize, ctlOffset); + SPDLOG_INFO("SoundfontTbl: tbl@0x{:X} size 0x{:X} (computed from ctl@0x{:X})", realTblOffset, tblSize, + realCtlOffset); - return std::make_shared<RawBuffer>(buffer.data() + tblOffset, tblSize); + return std::make_shared<RawBuffer>(buffer.data() + realTblOffset, tblSize); } } // namespace BK64 diff --git a/src/factories/bk64/SoundfontTblFactory.h b/src/factories/bk64/SoundfontTblFactory.h index 4d63609..b1979a2 100644 --- a/src/factories/bk64/SoundfontTblFactory.h +++ b/src/factories/bk64/SoundfontTblFactory.h @@ -13,4 +13,15 @@ class SoundfontTblFactory : public BaseFactory { } }; +class SoundfontCtlFactory : public BaseFactory { + public: + std::optional<std::shared_ptr<IParsedData>> parse(std::vector<uint8_t>& buffer, YAML::Node& data) override; + inline std::unordered_map<ExportType, std::shared_ptr<BaseExporter>> GetExporters() override { + return { REGISTER(Header, BlobHeaderExporter) REGISTER(Binary, BlobBinaryExporter) + REGISTER(Code, BlobCodeExporter) }; + } +}; + +uint32_t LocateSoundfontCtl(const std::vector<uint8_t>& rom, uint32_t ctlOffset, uint32_t ctlSize); + } // namespace BK64 diff --git a/src/utils/Decompressor.cpp b/src/utils/Decompressor.cpp index f9c800c..c591f6c 100644 --- a/src/utils/Decompressor.cpp +++ b/src/utils/Decompressor.cpp @@ -228,7 +228,19 @@ DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector<uint8_t> } auto decoded = Decode(buffer, fileOffset, type, sizeEntry.value()); + // Rom data can reference offsets past the decoded file; clamp instead of + // letting decoded->size - offsetFromFile underflow into a giant bogus segment. + if (offsetFromFile > decoded->size) { + SPDLOG_WARN("AutoDecode BKZIP: offset 0x{:X} lies past decoded size 0x{:X}; clamping to empty segment", + offsetFromFile, decoded->size); + offsetFromFile = decoded->size; + } auto size = node["size"] ? node["size"].as<size_t>() : manualSize.value_or(decoded->size - offsetFromFile); + if (size > decoded->size - offsetFromFile) { + SPDLOG_WARN("AutoDecode BKZIP: requested size 0x{:X} exceeds available 0x{:X}; reducing", size, + decoded->size - offsetFromFile); + size = 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); @@ -241,6 +253,13 @@ DecompressedData Decompressor::AutoDecode(YAML::Node& node, std::vector<uint8_t> { fileOffset = TranslateAddr(offset, false); + // An offset past the file must not underflow availableSize. + if (fileOffset > buffer.size()) { + SPDLOG_WARN("AutoDecode: offset 0x{:X} lies past file size 0x{:X}; clamping to empty segment", + fileOffset, buffer.size()); + fileOffset = buffer.size(); + } + auto availableSize = buffer.size() - fileOffset; size_t size; |
