summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeod <47716344+JeodC@users.noreply.github.com>2026-08-26 12:19:10 -0400
committerLywx <kiritodev01@gmail.com>2026-09-01 01:52:15 -0600
commitcd7415d80fabf439167488d9f6255d6570021f01 (patch)
tree25ba9b51d0818dbf5141dd31d256a0b4a46b2fed /src
parent0cc23c7eadafc42328f9679dd1e0390177b70af7 (diff)
BK64: Trust the texture type field
Diffstat (limited to 'src')
-rw-r--r--src/factories/bk64/GeoLayoutFactory.cpp34
-rw-r--r--src/factories/bk64/GeoLayoutFactory.h9
-rw-r--r--src/factories/bk64/ModelFactory.cpp126
3 files changed, 81 insertions, 88 deletions
diff --git a/src/factories/bk64/GeoLayoutFactory.cpp b/src/factories/bk64/GeoLayoutFactory.cpp
index b3a91b3..20016aa 100644
--- a/src/factories/bk64/GeoLayoutFactory.cpp
+++ b/src/factories/bk64/GeoLayoutFactory.cpp
@@ -28,6 +28,11 @@ static uint32_t GetGeoCommandByteSize(const GeoLayoutCommand& cmd) {
case GeoLayoutOpCode::LoadDL:
bodySize = 4;
break; // 2+2
+ case GeoLayoutOpCode::NOP:
+ case GeoLayoutOpCode::NOP2:
+ case GeoLayoutOpCode::NOP3:
+ bodySize = 4;
+ break; // 4 bytes Banjo's Backpack writes and nothing reads
case GeoLayoutOpCode::Skinning:
// 2 per arg + 2 for terminator
bodySize = static_cast<uint32_t>(cmd.args.size()) * 2 + 2;
@@ -150,6 +155,12 @@ ExportResult BK64::GeoLayoutBinaryExporter::Export(std::ostream& write, std::sha
writeU16(std::get<uint16_t>(arguments[0]));
writeU16(std::get<uint16_t>(arguments[1]));
break;
+ case GeoLayoutOpCode::NOP:
+ case GeoLayoutOpCode::NOP2:
+ case GeoLayoutOpCode::NOP3:
+ for (size_t i = 0; i < arguments.size(); i++)
+ writeU8(std::get<uint8_t>(arguments[i]));
+ break;
case GeoLayoutOpCode::Skinning:
writeU16(std::get<uint16_t>(arguments[0]));
for (size_t i = 1; i < arguments.size(); i++)
@@ -298,6 +309,19 @@ ExportResult GeoLayoutModdingExporter::Export(std::ostream& write, std::shared_p
out << YAML::Key << "dlIndex" << YAML::Value << std::get<uint16_t>(arguments.at(0));
out << YAML::Key << "triCount" << YAML::Value << std::get<uint16_t>(arguments.at(1));
break;
+ // Nothing reads the body, so there is nothing worth writing out
+ case GeoLayoutOpCode::NOP:
+ out << YAML::Key << "NOP";
+ out << YAML::Value << YAML::BeginMap;
+ break;
+ case GeoLayoutOpCode::NOP2:
+ out << YAML::Key << "NOP2";
+ out << YAML::Value << YAML::BeginMap;
+ break;
+ case GeoLayoutOpCode::NOP3:
+ out << YAML::Key << "NOP3";
+ out << YAML::Value << YAML::BeginMap;
+ break;
case GeoLayoutOpCode::Skinning:
out << YAML::Key << "Skinning";
out << YAML::Value << YAML::BeginMap;
@@ -543,6 +567,16 @@ std::optional<std::shared_ptr<IParsedData>> GeoLayoutFactory::parse(std::vector<
args.emplace_back(triCount);
break;
}
+ case GeoLayoutOpCode::NOP:
+ case GeoLayoutOpCode::NOP2:
+ case GeoLayoutOpCode::NOP3: {
+ // sGeoCmdList dispatches these to modelRender_geoCmd_NOP--nothing reads the body.
+ // Keep the bytes so the binary exporter writes the command back as it was.
+ for (int32_t i = 0; i < 4; i++) {
+ args.emplace_back(reader.ReadUByte());
+ }
+ break;
+ }
case GeoLayoutOpCode::Skinning: {
auto dlOffsetPreviousBone = reader.ReadUInt16();
diff --git a/src/factories/bk64/GeoLayoutFactory.h b/src/factories/bk64/GeoLayoutFactory.h
index 092d55f..1f09957 100644
--- a/src/factories/bk64/GeoLayoutFactory.h
+++ b/src/factories/bk64/GeoLayoutFactory.h
@@ -14,12 +14,15 @@ enum class GeoLayoutOpCode {
Sort,
Bone,
LoadDL,
- Skinning = 5,
+ NOP,
+ Skinning,
Branch,
UnknownCmd7,
LOD,
- ReferencePoint = 10,
- Selector = 12,
+ NOP2,
+ ReferencePoint,
+ NOP3,
+ Selector,
DrawDistance,
UnknownCmdE,
UnknownCmdF,
diff --git a/src/factories/bk64/ModelFactory.cpp b/src/factories/bk64/ModelFactory.cpp
index 565f06c..0f22d6c 100644
--- a/src/factories/bk64/ModelFactory.cpp
+++ b/src/factories/bk64/ModelFactory.cpp
@@ -5,6 +5,7 @@
#include "types/RawBuffer.h"
#include "utils/Decompressor.h"
#include "utils/TorchUtils.h"
+#include <string_view>
#define BK64_MODEL_HEADER 0xB
#define TEXTURE_HEADER_SIZE 0x8
@@ -43,6 +44,28 @@ static const std::unordered_map<GBIVersion, std::unordered_map<std::string, uint
#define GBI(cmd) gGBITable.at(Companion::Instance->GetGBIVersion()).at(#cmd)
+// textureInfo_getBitDepth tests the type field bit by bit and takes the first one set, so a
+// texture with extra bits still resolves to a format. The game's ladder stops at 0x8; Torch
+// extends to 0x10.
+static std::string_view GetTextureFormat(uint16_t type) {
+ if (type & 0x1) {
+ return "CI4";
+ }
+ if (type & 0x2) {
+ return "CI8";
+ }
+ if (type & 0x4) {
+ return "RGBA16";
+ }
+ if (type & 0x8) {
+ return "RGBA32";
+ }
+ if (type & 0x10) {
+ return "IA8";
+ }
+ return {};
+}
+
ExportResult ModelHeaderExporter::Export(std::ostream& write, std::shared_ptr<IParsedData> raw, std::string& entryName,
YAML::Node& node, std::string* replacement) {
const auto symbol = GetSafeNode(node, "symbol", entryName);
@@ -230,7 +253,7 @@ ExportResult BK64::ModelBinaryExporter::Export(std::ostream& write, std::shared_
std::unordered_map<uint32_t, uint32_t> imageOffsetToTex;
for (uint32_t ti = 0; ti < model->mTexInfos.size(); ti++) {
const auto& tex = model->mTexInfos[ti];
- const bool isCI = tex.type == 0x1 || tex.type == 0x2; // CI4 / CI8
+ const bool isCI = tex.tlutColors != 0; // CI4 and CI8 are the only types with a palette
const uint32_t tlutByteSize = isCI ? tex.tlutColors * 2u : 0u;
imageOffsetToTex[tex.textureDataOffset + tlutByteSize] = ti;
}
@@ -478,14 +501,10 @@ std::optional<std::shared_ptr<IParsedData>> ModelFactory::parse(std::vector<uint
reader.ReadUInt16(); // pad
reader.ReadUInt32(); // pad
- std::string format;
- std::string ctype;
- uint32_t tlutSize = 0;
- uint16_t tlutColors = 0;
-
- // Stash texture metadata for the binary exporter. Type 0x1 just means "has a TLUT" —
- // it's both CI4 and CI8. We can't tell which until all the headers are in, so the real
- // bit depth gets resolved further down.
+ // Stash texture metadata for the binary exporter. Type 0x1 is CI4
+ // and 0x2 is CI8: every type 0x2 region in the ROM holds a 256
+ // entry TLUT and 8-bit pixels, and no type 0x1 region has room for
+ // one.
TexInfo texInfo;
texInfo.type = textureType;
texInfo.width = static_cast<uint8_t>(width);
@@ -493,20 +512,15 @@ std::optional<std::shared_ptr<IParsedData>> ModelFactory::parse(std::vector<uint
texInfo.tlutColors = 0;
texInfo.textureDataOffset = textureDataOffset;
- switch (textureType) {
- case 0x1:
- // Sorted out later, once every header is read
- break;
- case 0x2:
- texInfo.tlutColors = 0x100;
- break;
- case 0x4:
- case 0x8:
- case 0x10:
- break;
- default:
- throw std::runtime_error("BK64::ModelFactory: Invalid Texture Format Found " +
- std::to_string(textureType));
+ const auto format = GetTextureFormat(textureType);
+ if (format.empty()) {
+ throw std::runtime_error("BK64::ModelFactory: Invalid Texture Format Found " +
+ std::to_string(textureType));
+ }
+ if (format == "CI4") {
+ texInfo.tlutColors = 0x10;
+ } else if (format == "CI8") {
+ texInfo.tlutColors = 0x100;
}
modelData->mTexInfos.push_back(texInfo);
@@ -516,45 +530,6 @@ std::optional<std::shared_ptr<IParsedData>> ModelFactory::parse(std::vector<uint
modelOffset + textureSetupOffset + TEXTURE_HEADER_SIZE + textureCount * TEXTURE_METADATA_SIZE;
modelData->mTexDataSize = textureDataSize;
- // Now disambiguate the type 0x1 textures. 0x1 means "has TLUT", which is either CI4
- // (16-entry palette) or CI8 (256-entry palette) — the header doesn't say which. Trick is
- // to measure the gap to the next texture: if it's big enough for a full CI8 payload
- // (0x200 TLUT + W*H pixels), call it CI8, otherwise CI4. The last texture in a list can be
- // padded, hence >= instead of ==. CI8 always needs more room than CI4 at the same W*H
- // (delta = 0x1E0 - W*H/2 > 0 for any BK texture up to 64x64), so there's no overlap to
- // worry about.
- for (uint16_t i = 0; i < textureCount; i++) {
- auto& tex = modelData->mTexInfos[i];
- if (tex.type != 0x1) {
- continue;
- }
-
- uint32_t nextOffset =
- (i + 1 < textureCount) ? modelData->mTexInfos[i + 1].textureDataOffset : textureDataSize;
- uint32_t gap = nextOffset - tex.textureDataOffset;
- uint32_t ci4Size = 0x20 + ((uint32_t)tex.width * tex.height) / 2; // 16-entry TLUT + CI4 pixels
- uint32_t ci8Size = 0x200 + (uint32_t)tex.width * tex.height; // 256-entry TLUT + CI8 pixels
-
- if (gap >= ci8Size) {
- tex.type = 0x2; // CI8
- tex.tlutColors = 0x100;
- if (gap != ci8Size) {
- SPDLOG_INFO("[BK64::Model] tex[{}] {}x{}: gap=0x{:X} >= CI8 (0x{:X}), classified CI8 (pad=0x{:X})",
- i, tex.width, tex.height, gap, ci8Size, gap - ci8Size);
- }
- } else {
- tex.tlutColors = 0x10; // CI4
- if (gap < ci4Size) {
- SPDLOG_WARN("[BK64::Model] tex[{}] {}x{}: gap=0x{:X} smaller than CI4 (0x{:X}), data may be "
- "truncated",
- i, tex.width, tex.height, gap, ci4Size);
- } else if (gap != ci4Size) {
- SPDLOG_INFO("[BK64::Model] tex[{}] {}x{}: gap=0x{:X} (CI4 0x{:X}, pad=0x{:X})", i, tex.width,
- tex.height, gap, ci4Size, gap - ci4Size);
- }
- }
- }
-
// [port] Grab the entire raw texture area so animated frames and any unlisted bytes
// between textures survive into the binary.
if (textureDataSize > 0 && texDataStart + textureDataSize <= segment.size) {
@@ -568,30 +543,11 @@ std::optional<std::shared_ptr<IParsedData>> ModelFactory::parse(std::vector<uint
const auto& tex = modelData->mTexInfos[i];
uint32_t texOffset = texDataStart + tex.textureDataOffset;
- std::string format;
- uint32_t tlutByteSize = 0;
-
- switch (tex.type) {
- case 0x1:
- format = "CI4";
- tlutByteSize = tex.tlutColors * 2;
- break;
- case 0x2:
- format = "CI8";
- tlutByteSize = tex.tlutColors * 2;
- break;
- case 0x4:
- format = "RGBA16";
- break;
- case 0x8:
- format = "RGBA32";
- break;
- case 0x10:
- format = "IA8";
- break;
- default:
- continue;
+ const std::string format{ GetTextureFormat(tex.type) };
+ if (format.empty()) {
+ continue;
}
+ uint32_t tlutByteSize = tex.tlutColors * 2;
std::string texSymbol = symbol + "_tex_" + std::to_string(i);