Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/Companion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,8 @@ void Companion::ParseCurrentFileConfig(YAML::Node node, std::atomic<size_t>& ass
}
}

this->gCurrentFileConfig.reset(node);

if (node["directory"]) {
this->gCurrentDirectory = node["directory"].as<std::string>();
}
Expand Down Expand Up @@ -1295,6 +1297,7 @@ void Companion::ProcessFile(YAML::Node root, std::atomic<size_t>& assetCount) {
this->gCurrentExternalFiles.clear();
this->gSubFileList.clear();
this->gManualSegments.clear();
this->gCurrentFileConfig.reset(YAML::Node());
GFXDOverride::ClearVtx();

if (root[":config"]) {
Expand Down Expand Up @@ -1447,7 +1450,11 @@ void Companion::Process(std::atomic<size_t>& assetCount) {
}
}
this->gAssetPath = (this->gSourceDirectory / rom["path"].as<std::string>()).string();
this->gCommonAssetPath = (this->gSourceDirectory / rom["common_path"].as<std::string>()).string();
// Optional: a rom that keeps all its ymls under one tree has no common dir, and
// getRecursiveEntries already treats an empty path as "nothing to add".
if (rom["common_path"]) {
this->gCommonAssetPath = (this->gSourceDirectory / rom["common_path"].as<std::string>()).string();
}

if (rom["filelist"]) {
const std::string filelistPath = (this->gSourceDirectory / rom["filelist"].as<std::string>()).string();
Expand Down
34 changes: 34 additions & 0 deletions src/factories/bk64/GeoLayoutFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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++)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down
9 changes: 6 additions & 3 deletions src/factories/bk64/GeoLayoutFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
126 changes: 41 additions & 85 deletions src/factories/bk64/ModelFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -478,35 +501,26 @@ 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);
texInfo.height = static_cast<uint8_t>(height);
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);
Expand All @@ -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) {
Expand All @@ -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);

Expand Down
Loading