summaryrefslogtreecommitdiff
path: root/src/factories/oot/OoTDListHelpers.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/factories/oot/OoTDListHelpers.cpp')
-rw-r--r--src/factories/oot/OoTDListHelpers.cpp759
1 files changed, 759 insertions, 0 deletions
diff --git a/src/factories/oot/OoTDListHelpers.cpp b/src/factories/oot/OoTDListHelpers.cpp
new file mode 100644
index 0000000..6e81477
--- /dev/null
+++ b/src/factories/oot/OoTDListHelpers.cpp
@@ -0,0 +1,759 @@
+#include "OoTDListHelpers.h"
+#include "Companion.h"
+#include "spdlog/spdlog.h"
+#include "factories/DisplayListFactory.h"
+#include "factories/DisplayListOverrides.h"
+#include "utils/Decompressor.h"
+#include "DeferredVtx.h"
+#include <iomanip>
+#include <sstream>
+#include "n64/gbi-otr.h"
+#include "strhash64/StrHash64.h"
+
+#define C0(pos, width) ((w0 >> (pos)) & ((1U << width) - 1))
+#define ALIGN16(val) (((val) + 0xF) & ~0xF)
+
+// F3DEX2 opcodes (OoT is always f3dex2)
+static constexpr uint8_t F3DEX2_VTX = 0x01;
+static constexpr uint8_t F3DEX2_DL = 0xDE;
+static constexpr uint8_t F3DEX2_MTX = 0xDA;
+static constexpr uint8_t F3DEX2_ENDDL = 0xDF;
+static constexpr uint8_t F3DEX2_SETTIMG = 0xFD;
+static constexpr uint8_t F3DEX2_MOVEMEM = 0xDC;
+static constexpr uint8_t F3DEX2_RDPHALF_1 = 0xE1;
+static constexpr uint8_t F3DEX2_BRANCH_Z = 0x04;
+static constexpr uint8_t F3DEX2_SETOTHERMODE_H = 0xE3;
+static constexpr uint8_t F3DEX2_NOOP = 0x00;
+static constexpr uint8_t F3DEX2_SETTILE = 0xF5;
+static constexpr uint8_t F3DEX2_LOADBLOCK = 0xF3;
+static constexpr uint8_t F3DEX2_MV_LIGHT = 0x0A;
+
+namespace OoT {
+namespace DListHelpers {
+
+// ── Internal helpers (not exposed in header) ──────────────────────────
+
+static uint32_t RemapSegmentedAddr(uint32_t addr, const std::string& expectedType = "") {
+ uint8_t seg = SEGMENT_NUMBER(addr);
+ uint32_t offset = SEGMENT_OFFSET(addr);
+ auto segBase = Companion::Instance->GetFileOffsetFromSegmentedAddr(seg);
+ if (!segBase.has_value()) return addr;
+
+ for (uint8_t otherSeg = 1; otherSeg < 0x20; otherSeg++) {
+ if (otherSeg == seg) continue;
+ auto otherBase = Companion::Instance->GetFileOffsetFromSegmentedAddr(otherSeg);
+ if (otherBase.has_value() && otherBase.value() == segBase.value()) {
+ uint32_t remapped = (otherSeg << 24) | offset;
+ auto node = Companion::Instance->GetNodeByAddr(remapped);
+ if (node.has_value()) {
+ if (!expectedType.empty()) {
+ auto n = std::get<1>(node.value());
+ auto nType = GetSafeNode<std::string>(n, "type");
+ if (nType != expectedType) continue;
+ }
+ return remapped;
+ }
+ }
+ }
+ return addr;
+}
+
+static bool IsAliasSegment(uint32_t addr) {
+ if (!IS_SEGMENTED(addr)) return false;
+ auto thisSeg = Companion::Instance->GetFileOffsetFromSegmentedAddr(SEGMENT_NUMBER(addr));
+ if (!thisSeg.has_value()) return true;
+ for (uint8_t s = 0; s < SEGMENT_NUMBER(addr); s++) {
+ auto otherSeg = Companion::Instance->GetFileOffsetFromSegmentedAddr(s);
+ if (otherSeg.has_value() && otherSeg.value() == thisSeg.value()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// ── Export helpers ─────────────────────────────────────────────────────
+// All Export helpers follow main's pattern: they modify w0/w1 in place and may write
+// intermediate words. The loop's final writer.Write(w0); writer.Write(w1) handles the last pair.
+// Helpers that need to skip the rest of the iteration (gSunDLVtx, BranchZ, RDPHALF_1)
+// write all their words and return true for `continue`.
+
+static bool ExportGSunDLVtx(uint32_t w0, uint32_t w1,
+ LUS::BinaryWriter& writer, std::string* replacement) {
+ if (!replacement || replacement->find("gSunDL") == std::string::npos) return false;
+
+ auto ptr = w1;
+ std::optional<std::pair<std::string, uint32_t>> rangedMatch;
+
+ for (const auto& type : std::vector<std::string>{"TEXTURE", "BLOB"}) {
+ auto decs = Companion::Instance->GetNodesByType(type);
+ if (!decs.has_value()) continue;
+ for (auto& [name, dnode] : decs.value()) {
+ auto doffset = GetSafeNode<uint32_t>(dnode, "offset");
+ uint32_t dsize = 0;
+ if (type == "TEXTURE") {
+ auto fmt = GetSafeNode<std::string>(dnode, "format");
+ auto w = GetSafeNode<uint32_t>(dnode, "width");
+ auto h = GetSafeNode<uint32_t>(dnode, "height");
+ uint32_t bpp = 16;
+ if (fmt == "I4" || fmt == "IA4" || fmt == "CI4") bpp = 4;
+ else if (fmt == "I8" || fmt == "IA8" || fmt == "CI8") bpp = 8;
+ else if (fmt == "RGBA16" || fmt == "IA16") bpp = 16;
+ else if (fmt == "RGBA32") bpp = 32;
+ dsize = (w * h * bpp) / 8;
+ } else if (type == "BLOB") {
+ dsize = GetSafeNode<uint32_t>(dnode, "size");
+ }
+ if (ASSET_PTR(ptr) >= ASSET_PTR(doffset) && ASSET_PTR(ptr) < ASSET_PTR(doffset) + dsize) {
+ auto path = Companion::Instance->GetSafeStringByAddr(doffset, type);
+ uint32_t diff = ASSET_PTR(ptr) - ASSET_PTR(doffset);
+ if (path.has_value() && (!rangedMatch.has_value() || diff < rangedMatch->second)) {
+ rangedMatch = std::make_pair(path.value(), diff);
+ }
+ }
+ }
+ }
+
+ if (!rangedMatch.has_value()) return false;
+
+ auto& [path, diff] = rangedMatch.value();
+ uint64_t hash = CRC64(path.c_str());
+ size_t nvtx = (w0 >> 12) & 0xFF;
+ size_t didx = ((w0 >> 1) & 0x7F) - nvtx;
+ N64Gfx value = gsSPVertexOTR(diff, nvtx, didx);
+ writer.Write(value.words.w0);
+ writer.Write(value.words.w1);
+ writer.Write(static_cast<uint32_t>(hash >> 32));
+ writer.Write(static_cast<uint32_t>(hash & 0xFFFFFFFF));
+ return true; // All words written, caller should continue
+}
+
+// Modifies w0/w1 for the final write. Writes intermediate words.
+static void ExportVtx(uint32_t& w0, uint32_t& w1,
+ LUS::BinaryWriter& writer, std::string* replacement) {
+ size_t nvtx = (w0 >> 12) & 0xFF; // C0(12, 8)
+ size_t didx = ((w0 >> 1) & 0x7F) - nvtx; // C0(1, 7) - C0(12, 8)
+ auto ptr = Companion::Instance->PatchVirtualAddr(w1);
+
+ if (IsAliasSegment(w1)) {
+ w1 = w1 + 1;
+ SPDLOG_INFO("VTX export: alias segment for 0x{:X}", ptr);
+ return; // w0/w1 set, caller writes final pair
+ }
+
+ // Check overlap with cross-file handling
+ if (auto overlap = GFXDOverride::GetVtxOverlap(ptr); overlap.has_value()) {
+ auto ovnode = std::get<1>(overlap.value());
+ auto path = Companion::Instance->RelativePath(std::get<0>(overlap.value()));
+
+ auto currentDir = (*replacement).substr(0, (*replacement).rfind('/'));
+ auto vtxDir = path.substr(0, path.rfind('/'));
+ bool nullCrossFile = ovnode["null_cross_file"] && ovnode["null_cross_file"].as<bool>();
+ if (currentDir != vtxDir && nullCrossFile) {
+ SPDLOG_WARN("Cross-file VTX overlap at 0x{:X} (from {}), writing null vtxDecl", ptr, path);
+ w0 = G_VTX_OTR_HASH << 24;
+ w1 = 0;
+ } else {
+ uint64_t hash = CRC64(path.c_str());
+ if (hash == 0) {
+ throw std::runtime_error("Vtx hash is 0 for " + std::get<0>(overlap.value()));
+ }
+ SPDLOG_INFO("Found vtx: 0x{:X} Hash: 0x{:X} Path: {}", ptr, hash, path);
+ auto offset = GetSafeNode<uint32_t>(ovnode, "offset");
+ auto diff = ASSET_PTR(ptr) - ASSET_PTR(offset);
+ N64Gfx value = gsSPVertexOTR(diff, nvtx, didx);
+ writer.Write(value.words.w0);
+ writer.Write(value.words.w1);
+ w0 = hash >> 32;
+ w1 = hash & 0xFFFFFFFF;
+ }
+ return;
+ }
+
+ // Direct lookup with OOT:ARRAY support
+ auto vtxNode = Companion::Instance->GetNodeByAddr(ptr);
+ std::optional<std::string> dec = std::nullopt;
+ bool nullCrossFile = false;
+ if (vtxNode.has_value()) {
+ auto [vpath, vn] = vtxNode.value();
+ auto vtype = GetSafeNode<std::string>(vn, "type");
+ if (vtype == "VTX" || vtype == "OOT:ARRAY") {
+ dec = vpath;
+ nullCrossFile = vn["null_cross_file"] && vn["null_cross_file"].as<bool>();
+ }
+ }
+ if (dec.has_value()) {
+ auto currentDir = (*replacement).substr(0, (*replacement).rfind('/'));
+ auto vtxDir = dec.value().substr(0, dec.value().rfind('/'));
+ if (currentDir != vtxDir && nullCrossFile) {
+ SPDLOG_WARN("Cross-file VTX at 0x{:X} (from {}), writing null vtxDecl", ptr, dec.value());
+ w0 = G_VTX_OTR_HASH << 24;
+ w1 = 0;
+ } else {
+ uint64_t hash = CRC64(dec.value().c_str());
+ if (hash == 0) {
+ throw std::runtime_error("Vtx hash is 0 for " + dec.value());
+ }
+ SPDLOG_INFO("Found vtx: 0x{:X} Hash: 0x{:X} Path: {}", ptr, hash, dec.value());
+ N64Gfx value = gsSPVertexOTR(0, nvtx, didx);
+ writer.Write(value.words.w0);
+ writer.Write(value.words.w1);
+ w0 = hash >> 32;
+ w1 = hash & 0xFFFFFFFF;
+ }
+ return;
+ }
+
+ // Virtual segment handling
+ if (IS_VIRTUAL_SEGMENT(w1)) {
+ w0 = G_VTX_OTR_HASH << 24;
+ w1 = 0;
+ return;
+ }
+
+ // Cross-segment fallback
+ SPDLOG_WARN("VTX export: NOT FOUND vtx at 0x{:X} w1=0x{:X} replacement={}", ptr, w1, *replacement);
+ w1 = (w1 & 0x0FFFFFFF) + 1;
+}
+
+static void ExportDL(uint32_t& w0, uint32_t& w1, LUS::BinaryWriter& writer) {
+ auto ptr = w1;
+ uint8_t dlSeg = SEGMENT_NUMBER(ptr);
+
+ // Segments 8-13 are runtime-swapped and must stay unresolved
+ std::optional<std::string> dec = std::nullopt;
+ if (dlSeg < 8 || dlSeg > 13) {
+ dec = Companion::Instance->GetSafeStringByAddr(ptr, "GFX");
+ if (!dec.has_value()) {
+ auto remapped = RemapSegmentedAddr(ptr, "GFX");
+ if (remapped != ptr) {
+ dec = Companion::Instance->GetSafeStringByAddr(remapped, "GFX");
+ if (dec.has_value()) ptr = remapped;
+ }
+ }
+ }
+ auto branch = (w0 >> 16) & G_DL_NO_PUSH;
+
+ if (dec.has_value()) {
+ uint64_t hash = CRC64(dec.value().c_str());
+ SPDLOG_INFO("Found display list: 0x{:X} Hash: 0x{:X} Path: {}", ptr, hash, dec.value());
+
+ N64Gfx value = gsSPDisplayListOTRHash(ptr);
+ w0 = value.words.w0;
+ w1 = 0;
+
+ writer.Write(w0);
+ writer.Write(w1);
+
+ w0 = hash >> 32;
+ w1 = hash & 0xFFFFFFFF;
+
+ if (branch) {
+ writer.Write(w0);
+ writer.Write(w1);
+
+ N64Gfx endValue = gsSPRawOpcode(F3DEX2_ENDDL);
+ w0 = endValue.words.w0;
+ w1 = endValue.words.w1;
+ }
+ } else {
+ SPDLOG_WARN("Could not find display list at 0x{:X}", ptr);
+ w1 = (w1 & 0x0FFFFFFF) + 1;
+ }
+}
+
+static void ExportMoveMem(uint32_t& w0, uint32_t& w1) {
+ // OTRExporter has no G_MOVEMEM handler: its DisplayListExporter switch falls
+ // through to the default (undefined-opcode) case, which emits just the opcode
+ // byte with all operands zeroed (w0 = G_MOVEMEM << 24, w1 = 0). Match that
+ // for byte-parity — a single 8-byte command written by the caller. (The
+ // generic DisplayListFactory path keeps the G_MOVEMEM_OTR_HASH form for
+ // other games; OoT follows OTRExporter here.)
+ w0 = F3DEX2_MOVEMEM << 24;
+ w1 = 0;
+ // Final w0/w1 written by caller
+}
+
+static void ExportSetTImg(uint32_t& w0, uint32_t& w1,
+ LUS::BinaryWriter& writer, std::string* replacement) {
+ auto ptr = w1;
+ auto dec = Companion::Instance->GetSafeStringByAddr(ptr, "TEXTURE");
+
+ if (dec.has_value()) {
+ uint64_t hash = CRC64(dec.value().c_str());
+ if (hash == 0) {
+ throw std::runtime_error("Texture hash is 0 for " + dec.value());
+ }
+ SPDLOG_INFO("Found texture: 0x{:X} Hash: 0x{:X} Path: {}", ptr, hash, dec.value());
+
+ uint32_t newW0 = (G_SETTIMG_OTR_HASH << 24) | (w0 & 0x00FFFFFF);
+ writer.Write(newW0);
+ writer.Write(static_cast<uint32_t>(0));
+
+ w0 = hash >> 32;
+ w1 = hash & 0xFFFFFFFF;
+ } else {
+ SPDLOG_WARN("Could not find texture at 0x{:X}", ptr);
+ if (replacement && replacement->find("sShadowMaterialDL") != std::string::npos) {
+ w1 = 0x0C000001;
+ } else {
+ auto patchedPtr = Companion::Instance->PatchVirtualAddr(ptr);
+ w1 = (patchedPtr & 0x0FFFFFFF) + 1;
+ }
+ writer.Write(w0);
+ writer.Write(w1);
+ }
+ // Final w0/w1 written by caller
+}
+
+static void ExportGSunDLTextureFixup(uint8_t opcode, uint32_t& w0, uint32_t& w1,
+ std::string* replacement) {
+ if (!replacement || replacement->find("gSunDL") == std::string::npos) return;
+
+ constexpr uint8_t G_TX_LOADTILE = 7;
+ constexpr uint8_t G_IM_SIZ_4b = 0;
+
+ if (opcode == F3DEX2_SETTILE) {
+ uint8_t tile = (w1 >> 24) & 0x07;
+ if (tile != G_TX_LOADTILE) {
+ w0 = (w0 & ~(0x3 << 19)) | (G_IM_SIZ_4b << 19);
+ }
+ }
+
+ if (opcode == F3DEX2_LOADBLOCK) {
+ uint32_t ult = w0 & 0xFFF;
+ uint32_t texels = (w1 >> 12) & 0xFFF;
+ if (ult != G_TX_LOADTILE) {
+ texels = (texels + 1) / 2 - 1;
+ w1 = (w1 & ~(0xFFF << 12)) | ((texels & 0xFFF) << 12);
+ }
+ }
+}
+
+static void ExportMtx(uint32_t& w0, uint32_t& w1, LUS::BinaryWriter& writer) {
+ auto ptr = w1;
+ auto dec = Companion::Instance->GetSafeStringByAddr(ptr, "OOT:MTX");
+ if (!dec.has_value()) {
+ dec = Companion::Instance->GetSafeStringByAddr(ptr, "MTX");
+ }
+ if (!dec.has_value()) {
+ auto remapped = RemapSegmentedAddr(ptr, "OOT:MTX");
+ if (remapped == ptr) remapped = RemapSegmentedAddr(ptr, "MTX");
+ if (remapped != ptr) {
+ dec = Companion::Instance->GetSafeStringByAddr(remapped, "OOT:MTX");
+ if (!dec.has_value()) dec = Companion::Instance->GetSafeStringByAddr(remapped, "MTX");
+ if (dec.has_value()) ptr = remapped;
+ }
+ }
+
+ if (dec.has_value()) {
+ uint64_t hash = CRC64(dec.value().c_str());
+ if (hash == 0) {
+ throw std::runtime_error("Matrix hash is 0 for " + dec.value());
+ }
+ SPDLOG_INFO("Found matrix: 0x{:X} Hash: 0x{:X} Path: {}", ptr, hash, dec.value());
+
+ w0 &= 0x00FFFFFF;
+ w0 += G_MTX_OTR << 24;
+
+ writer.Write(w0);
+ writer.Write(w1);
+
+ w0 = hash >> 32;
+ w1 = hash & 0xFFFFFFFF;
+ } else {
+ SPDLOG_WARN("Could not find matrix at 0x{:X}", ptr);
+ w1 = (ptr & 0x0FFFFFFF) + 1;
+ }
+ // Final w0/w1 written by caller
+}
+
+// Writes all words and returns true (caller should continue).
+static bool ExportBranchZ(uint32_t w0, uint32_t w1,
+ size_t cmdIndex, const std::vector<uint32_t>& cmds,
+ LUS::BinaryWriter& writer) {
+ uint32_t dlAddr = (cmdIndex >= 2) ? cmds[cmdIndex - 1] : 0;
+ auto dec = Companion::Instance->GetSafeStringByAddr(dlAddr, "GFX");
+ if (!dec.has_value()) {
+ auto remapped = RemapSegmentedAddr(dlAddr, "GFX");
+ if (remapped != dlAddr) {
+ dec = Companion::Instance->GetSafeStringByAddr(remapped, "GFX");
+ }
+ }
+
+ if (dec.has_value()) {
+ uint64_t hash = CRC64(dec.value().c_str());
+ uint32_t a = (w0 >> 12) & 0xFFF;
+ uint32_t b = w0 & 0xFFF;
+ uint32_t branchW0 = (G_BRANCH_Z_OTR << 24) | _SHIFTL(a, 12, 12) | _SHIFTL(b, 0, 12);
+ writer.Write(branchW0);
+ writer.Write(w1);
+ writer.Write(static_cast<uint32_t>(hash >> 32));
+ writer.Write(static_cast<uint32_t>(hash & 0xFFFFFFFF));
+ } else {
+ SPDLOG_WARN("Could not find display list for G_BRANCH_Z at 0x{:X}", dlAddr);
+ writer.Write(w0);
+ writer.Write(w1);
+ }
+ return true; // All words written, caller should continue
+}
+
+static void ExportOpcodeFixups(uint8_t opcode, uint32_t& w0, uint32_t& w1) {
+ // G_SETOTHERMODE_H texture LUT re-encoding
+ if (opcode == F3DEX2_SETOTHERMODE_H) {
+ uint8_t ss = (w0 >> 8) & 0xFF;
+ uint8_t nn = w0 & 0xFF;
+ int32_t sft = 32 - (nn + 1) - ss;
+ if (sft == 14) {
+ w1 = w1 >> 14;
+ }
+ }
+
+ // G_NOOP zeroing
+ if (opcode == F3DEX2_NOOP) {
+ w0 = 0;
+ w1 = 0;
+ }
+
+ // Unhandled opcode zeroing
+ static const std::unordered_set<uint8_t> otrHandledOpcodes = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDE, 0xDF,
+ 0xE1, 0xE2, 0xE3, 0xE4,
+ 0xE6, 0xE7, 0xE8, 0xE9, 0xEF,
+ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5,
+ 0xFA, 0xFB, 0xFC, 0xFD,
+ };
+ if (otrHandledOpcodes.find(opcode) == otrHandledOpcodes.end()) {
+ w0 = (uint32_t)opcode << 24;
+ w1 = 0;
+ }
+}
+
+// ── Parse helpers ─────────────────────────────────────────────────────
+
+static void ParseDL(uint32_t w0, uint32_t w1, YAML::Node& node) {
+ if (C0(16, 1) == G_DL_NO_PUSH) {
+ // Branch — handled by caller (sets processing = false)
+ }
+
+ if (SEGMENT_NUMBER(node["offset"].as<uint32_t>()) == SEGMENT_NUMBER(w1)) {
+ // OoT pre-declares child DLists in YAML — skip AddAsset
+ auto parentSymbol = GetSafeNode<std::string>(node, "symbol", "");
+ auto dlPos = parentSymbol.rfind("DL_");
+ if (dlPos != std::string::npos) {
+ auto base = parentSymbol.substr(0, dlPos + 3);
+ uint32_t childOffset = SEGMENT_OFFSET(w1);
+ std::ostringstream ss;
+ ss << base << std::uppercase << std::hex
+ << std::setfill('0') << std::setw(6) << childOffset;
+ // Symbol derived but not added (OoT pre-declares in YAML)
+ }
+ }
+}
+
+static void ParseMoveMem(uint32_t w0, uint32_t w1) {
+ uint8_t subcommand = (w0 >> 16) & 0xFF;
+ uint8_t index = C0(0, 8);
+ uint8_t offset = C0(8, 8) * 8;
+ bool light = false;
+
+ // OoT is f3dex2 only — check G_MV_LIGHT
+ if (index == F3DEX2_MV_LIGHT && offset == (2 * 24 + 24)) {
+ light = true;
+ }
+
+ if (light) {
+ // OoT pre-declares lights in YAML — skip AddAsset
+ }
+}
+
+static void ParseRdpHalf1(uint32_t w0, uint32_t w1,
+ YAML::Node& node, std::vector<uint8_t>& buffer) {
+ if (!IS_SEGMENTED(w1) || SEGMENT_NUMBER(w1) != SEGMENT_NUMBER(node["offset"].as<uint32_t>())) {
+ return;
+ }
+
+ const auto decl = Companion::Instance->GetNodeByAddr(w1);
+ if (!decl.has_value()) {
+ // DList pre-declared in YAML — skip AddAsset
+ }
+
+ if (DeferredVtx::IsDeferred()) {
+ auto branchData = Decompressor::AutoDecode(w1, std::nullopt, buffer);
+ LUS::BinaryReader branchReader(branchData.segment.data, branchData.segment.size);
+ branchReader.SetEndianness(Torch::Endianness::Big);
+
+ while (branchReader.GetBaseAddress() + 8 <= branchData.segment.size) {
+ auto bw0 = branchReader.ReadUInt32();
+ auto bw1 = branchReader.ReadUInt32();
+ uint8_t bOpcode = bw0 >> 24;
+
+ if (bOpcode == F3DEX2_ENDDL) break;
+
+ if (bOpcode == F3DEX2_VTX && IS_SEGMENTED(bw1)) {
+ uint32_t bNvtx = (bw0 >> 12) & 0xFF;
+ DeferredVtx::AddPending(bw1, bNvtx);
+ }
+ }
+ }
+}
+
+static void ParseMtx(uint32_t w1, YAML::Node& node) {
+ if (IS_SEGMENTED(w1) && SEGMENT_NUMBER(w1) == SEGMENT_NUMBER(node["offset"].as<uint32_t>())) {
+ const auto decl = Companion::Instance->GetNodeByAddr(w1);
+ if (!decl.has_value()) {
+ SPDLOG_WARN("Undeclared MTX at 0x{:08X} — YAML enrichment incomplete", w1);
+ }
+ }
+}
+
+static void ParseVtx(uint32_t w0, uint32_t w1, uint32_t nvtx,
+ YAML::Node& node, std::vector<uint8_t>& buffer) {
+ const auto decl = Companion::Instance->GetNodeByAddr(w1);
+ if (decl.has_value()) {
+ SPDLOG_WARN("Found vtx at 0x{:X}", w1);
+ return;
+ }
+
+ auto adjPtr = Companion::Instance->PatchVirtualAddr(w1);
+ auto search = SearchVtx(adjPtr);
+
+ if (search.has_value()) {
+ auto [path, vtx] = search.value();
+ SPDLOG_INFO("Path: {}", path);
+
+ auto lOffset = GetSafeNode<uint32_t>(vtx, "offset");
+ auto lCount = GetSafeNode<uint32_t>(vtx, "count");
+ auto lSize = ALIGN16(lCount * 16); // sizeof(N64Vtx_t)
+
+ // Compare in absolute ROM address space
+ uint32_t absPtr = adjPtr;
+ uint32_t absOffset = lOffset;
+ if (IS_SEGMENTED(adjPtr)) {
+ auto seg = Companion::Instance->GetFileOffsetFromSegmentedAddr(SEGMENT_NUMBER(adjPtr));
+ if (seg.has_value()) absPtr = seg.value() + SEGMENT_OFFSET(adjPtr);
+ }
+ if (IS_SEGMENTED(lOffset)) {
+ auto seg = Companion::Instance->GetFileOffsetFromSegmentedAddr(SEGMENT_NUMBER(lOffset));
+ if (seg.has_value()) absOffset = seg.value() + SEGMENT_OFFSET(lOffset);
+ }
+
+ if (absPtr > absOffset && absPtr <= absOffset + lSize) {
+ SPDLOG_INFO("Found vtx at 0x{:X} matching last vtx at 0x{:X}", adjPtr, lOffset);
+ GFXDOverride::RegisterVTXOverlap(adjPtr, search.value());
+ }
+ return;
+ }
+
+ // Skip VTX auto-creation for alias segments, virtual addresses, etc.
+ bool skipVtx = !IS_SEGMENTED(adjPtr) || IsAliasSegment(adjPtr);
+
+ if (!skipVtx) {
+ if (DeferredVtx::IsDeferred()) {
+ DeferredVtx::AddPending(adjPtr, nvtx);
+ } else {
+ SPDLOG_WARN("Undeclared VTX at 0x{:08X} — YAML enrichment incomplete", adjPtr);
+ }
+ }
+}
+
+static void FlushVtx(YAML::Node& node) {
+ if (!DeferredVtx::IsDeferred()) return;
+
+ auto symbol = GetSafeNode<std::string>(node, "symbol", "");
+ auto dlPos = symbol.rfind("DL_");
+ std::string baseName = (dlPos != std::string::npos) ? symbol.substr(0, dlPos) : symbol;
+ DeferredVtx::FlushDeferred(baseName);
+}
+
+// ── Public API ────────────────────────────────────────────────────────
+
+std::optional<std::tuple<std::string, YAML::Node>> SearchVtx(uint32_t ptr) {
+ if (Companion::Instance->GetGBIMinorVersion() != GBIMinorVersion::OoT) return std::nullopt;
+
+ std::vector<std::string> vtxTypes = {"VTX", "OOT:ARRAY"};
+
+ uint32_t absPtr = ptr;
+ if (IS_SEGMENTED(ptr)) {
+ auto seg = Companion::Instance->GetFileOffsetFromSegmentedAddr(SEGMENT_NUMBER(ptr));
+ if (!seg.has_value()) {
+ return std::nullopt;
+ }
+ absPtr = seg.value() + SEGMENT_OFFSET(ptr);
+ }
+
+ for (const auto& type : vtxTypes) {
+ auto decs = Companion::Instance->GetNodesByType(type);
+ if (!decs.has_value()) continue;
+
+ for (auto& dec : decs.value()) {
+ auto [name, node] = dec;
+
+ if (type == "OOT:ARRAY") {
+ auto arrayType = GetSafeNode<std::string>(node, "array_type", "");
+ if (arrayType != "VTX") continue;
+ }
+
+ auto offset = GetSafeNode<uint32_t>(node, "offset");
+ auto count = GetSafeNode<uint32_t>(node, "count");
+ auto end = ALIGN16(count * 16); // sizeof(N64Vtx_t)
+
+ uint32_t absOffset = offset;
+ if (IS_SEGMENTED(offset)) {
+ auto seg = Companion::Instance->GetFileOffsetFromSegmentedAddr(SEGMENT_NUMBER(offset));
+ if (!seg.has_value()) continue;
+ absOffset = seg.value() + SEGMENT_OFFSET(offset);
+ }
+
+ if (absPtr > absOffset && absPtr < absOffset + end) {
+ return std::make_tuple(GetSafeNode<std::string>(node, "symbol", name), node);
+ }
+ }
+ }
+
+ return std::nullopt;
+}
+
+std::optional<ExportResult> Export(std::ostream& write, std::shared_ptr<IParsedData> raw,
+ std::string& entryName, YAML::Node& node, std::string* replacement) {
+ if (Companion::Instance->GetGBIMinorVersion() != GBIMinorVersion::OoT) return std::nullopt;
+
+ auto cmds = std::static_pointer_cast<DListData>(raw)->mGfxs;
+ auto writer = LUS::BinaryWriter();
+
+ BaseExporter::WriteHeader(writer, Torch::ResourceType::DisplayList, 0);
+
+ writer.Write((int8_t)GBIVersion::f3dex2);
+
+ while (writer.GetBaseAddress() % 8 != 0)
+ writer.Write(static_cast<int8_t>(0xFF));
+
+ auto bhash = CRC64((*replacement).c_str());
+ writer.Write(static_cast<uint32_t>((G_MARKER << 24)));
+ writer.Write(0xBEEFBEEF);
+ writer.Write(static_cast<uint32_t>(bhash >> 32));
+ writer.Write(static_cast<uint32_t>(bhash & 0xFFFFFFFF));
+
+ for (size_t i = 0; i < cmds.size(); i += 2) {
+ auto w0 = cmds[i];
+ auto w1 = cmds[i + 1];
+ uint8_t opcode = w0 >> 24;
+
+ // gSunDL VTX override — writes all words, skip rest of iteration
+ if (opcode == F3DEX2_VTX && ExportGSunDLVtx(w0, w1, writer, replacement)) {
+ continue;
+ }
+
+ if (opcode == F3DEX2_VTX) {
+ ExportVtx(w0, w1, writer, replacement);
+ }
+
+ if (opcode == F3DEX2_DL) {
+ ExportDL(w0, w1, writer);
+ }
+
+ if (opcode == F3DEX2_MOVEMEM) {
+ ExportMoveMem(w0, w1);
+ }
+
+ if (opcode == F3DEX2_SETTIMG) {
+ ExportSetTImg(w0, w1, writer, replacement);
+ }
+
+ if (opcode == F3DEX2_MTX) {
+ ExportMtx(w0, w1, writer);
+ }
+
+ // RDPHALF_1 before BRANCH_Z — zero it out, BRANCH_Z writes both
+ if (opcode == F3DEX2_RDPHALF_1 && i + 2 < cmds.size()) {
+ uint8_t nextOpcode = cmds[i + 2] >> 24;
+ if (nextOpcode == F3DEX2_BRANCH_Z) {
+ writer.Write(static_cast<uint32_t>(0));
+ writer.Write(static_cast<uint32_t>(0));
+ continue;
+ }
+ }
+
+ // BRANCH_Z — writes all words, skip rest of iteration
+ if (opcode == F3DEX2_BRANCH_Z && ExportBranchZ(w0, w1, i, cmds, writer)) {
+ continue;
+ }
+
+ // gSunDL texture format fixups (modifies w0/w1 in place)
+ ExportGSunDLTextureFixup(opcode, w0, w1, replacement);
+
+ // General opcode fixups (modifies w0/w1 in place)
+ ExportOpcodeFixups(opcode, w0, w1);
+
+ writer.Write(w0);
+ writer.Write(w1);
+ }
+
+ writer.Finish(write);
+ return ExportResult(std::nullopt);
+}
+
+std::optional<std::optional<std::shared_ptr<IParsedData>>> Parse(
+ std::vector<uint8_t>& raw_buffer, YAML::Node& node) {
+ if (Companion::Instance->GetGBIMinorVersion() != GBIMinorVersion::OoT) return std::nullopt;
+
+ auto count = GetSafeNode<int32_t>(node, "count", -1);
+ auto [_, segment] = Decompressor::AutoDecode(node, raw_buffer);
+ LUS::BinaryReader reader(segment.data, segment.size);
+ reader.SetEndianness(Torch::Endianness::Big);
+
+ std::vector<uint32_t> gfxs;
+ auto processing = true;
+ size_t length = 0;
+
+ while (processing) {
+ auto w0 = reader.ReadUInt32();
+ auto w1 = reader.ReadUInt32();
+
+ uint8_t opcode = w0 >> 24;
+
+ if (opcode == F3DEX2_ENDDL) {
+ processing = false;
+ }
+
+ if (opcode == F3DEX2_DL) {
+ if (C0(16, 1) == G_DL_NO_PUSH) {
+ SPDLOG_INFO("Branch List Command Found");
+ processing = false;
+ }
+ ParseDL(w0, w1, node);
+ }
+
+ if (opcode == F3DEX2_MOVEMEM) {
+ ParseMoveMem(w0, w1);
+ }
+
+ if (opcode == F3DEX2_RDPHALF_1) {
+ ParseRdpHalf1(w0, w1, node, raw_buffer);
+ }
+
+ if (opcode == F3DEX2_MTX) {
+ ParseMtx(w1, node);
+ }
+
+ if (opcode == F3DEX2_VTX) {
+ uint32_t nvtx = C0(12, 8);
+ ParseVtx(w0, w1, nvtx, node, raw_buffer);
+ }
+
+ if (count != -1 && length++ >= count) {
+ break;
+ }
+
+ gfxs.push_back(w0);
+ gfxs.push_back(w1);
+ }
+
+ FlushVtx(node);
+
+ return std::make_optional(std::make_optional(
+ std::static_pointer_cast<IParsedData>(std::make_shared<DListData>(gfxs))));
+}
+
+} // namespace DListHelpers
+} // namespace OoT