summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Companion.cpp1
-rw-r--r--src/factories/DisplayListFactory.cpp10
-rw-r--r--src/factories/VtxFactory.cpp9
-rw-r--r--src/factories/bk64/ModelFactory.cpp11
-rw-r--r--src/factories/bk64/SoundfontTblFactory.cpp100
-rw-r--r--src/factories/bk64/SoundfontTblFactory.h11
-rw-r--r--src/utils/Decompressor.cpp19
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;