summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBassel Alshmali <bassel@mobilabsolutions.com>2026-03-16 19:07:34 +0100
committerLywx <kiritodev01@gmail.com>2026-03-16 13:31:28 -0600
commit654b451ddc6cf25db201b2c858948f430784cc5f (patch)
tree52c35bceeba7c3155b8be4ed9c47e212978c3d14
parent91bfca307fc9f6f3fb77a3cb60653c69b6df526d (diff)
add GBIMinorVersion for width - 1 in DisplayListFactory
-rw-r--r--src/Companion.cpp5
-rw-r--r--src/Companion.h3
-rw-r--r--src/factories/DisplayListFactory.cpp21
-rw-r--r--src/factories/pm64/EffectDListFactory.cpp235
-rw-r--r--src/factories/pm64/EffectDListFactory.h24
5 files changed, 19 insertions, 269 deletions
diff --git a/src/Companion.cpp b/src/Companion.cpp
index 08022d5..5fa80a6 100644
--- a/src/Companion.cpp
+++ b/src/Companion.cpp
@@ -83,7 +83,6 @@
#include "factories/pm64/ImgFXAnimFactory.h"
#include "factories/pm64/TitleDataFactory.h"
#include "factories/pm64/EntityGfxFactory.h"
-#include "factories/pm64/EffectDListFactory.h"
#endif
#ifdef FZERO_SUPPORT
@@ -211,7 +210,6 @@ void Companion::Init(const ExportType type, std::atomic<size_t>& assetCount) {
this->RegisterFactory("PM64:IMGFX_ANIM", std::make_shared<PM64ImgFXAnimFactory>());
this->RegisterFactory("PM64:TITLE_DATA", std::make_shared<PM64TitleDataFactory>());
this->RegisterFactory("PM64:ENTITY_GFX", std::make_shared<PM64EntityGfxFactory>());
- this->RegisterFactory("PM64:EFFECT_DL", std::make_shared<PM64EffectDListFactory>());
#endif
#ifdef SF64_SUPPORT
@@ -1202,6 +1200,9 @@ void Companion::Process(std::atomic<size_t>& assetCount) {
this->gConfig.gbi.version = GBIVersion::f3db;
} else if(key == "F3DEX2") {
this->gConfig.gbi.version = GBIVersion::f3dex2;
+ } else if(key == "F3DEX2_PM64") {
+ this->gConfig.gbi.version = GBIVersion::f3dex2;
+ this->gConfig.gbi.subversion = GBIMinorVersion::PM64;
} else if(key == "F3DEXB") {
this->gConfig.gbi.version = GBIVersion::f3dexb;
} else if (key == "F3DEX_MK64") {
diff --git a/src/Companion.h b/src/Companion.h
index 5abe720..eb6de7d 100644
--- a/src/Companion.h
+++ b/src/Companion.h
@@ -35,7 +35,8 @@ enum class GBIVersion {
enum class GBIMinorVersion {
None,
Mk64,
- SM64
+ SM64,
+ PM64
};
enum class TableMode {
diff --git a/src/factories/DisplayListFactory.cpp b/src/factories/DisplayListFactory.cpp
index 2947dfe..2ee3fbf 100644
--- a/src/factories/DisplayListFactory.cpp
+++ b/src/factories/DisplayListFactory.cpp
@@ -428,13 +428,20 @@ ExportResult DListBinaryExporter::Export(std::ostream &write, std::shared_ptr<IP
auto ptr = w1;
auto dec = Companion::Instance->GetSafeStringByAddr(ptr, "TEXTURE");
- // Export texture segment addresses as segmented addresses
- N64Gfx value = gsDPSetTextureOTRImage(C0(21, 3), C0(19, 2), C0(0, 10), ptr);
- w0 = value.words.w0;
- w1 = value.words.w1;
-
- writer.Write(w0);
- writer.Write(w1);
+ if (Companion::Instance->GetGBIMinorVersion() == GBIMinorVersion::PM64) {
+ // preserve original w0 bits (fmt/siz/width) exactly, the
+ // ROM already stores width-1.
+ uint32_t newW0 = (G_SETTIMG_OTR_HASH << 24) | (w0 & 0x00FFFFFF);
+ writer.Write(newW0);
+ writer.Write(ptr);
+ } else {
+ // Export texture segment addresses as segmented addresses
+ N64Gfx value = gsDPSetTextureOTRImage(C0(21, 3), C0(19, 2), C0(0, 10), ptr);
+ w0 = value.words.w0;
+ w1 = value.words.w1;
+ writer.Write(w0);
+ writer.Write(w1);
+ }
if(dec.has_value()){
uint64_t hash = CRC64(dec.value().c_str());
diff --git a/src/factories/pm64/EffectDListFactory.cpp b/src/factories/pm64/EffectDListFactory.cpp
deleted file mode 100644
index b179c4c..0000000
--- a/src/factories/pm64/EffectDListFactory.cpp
+++ /dev/null
@@ -1,235 +0,0 @@
-// PM64 Effect Display List Factory
-//
-// Overrides the standard DListFactory binary exporter to fix a width encoding bug
-// in torch's G_SETTIMG → G_SETTIMG_OTR_HASH conversion.
-//
-// The standard DListFactory uses gsDPSetTextureOTRImage(fmt, siz, C0(0,10), ptr)
-// which double-subtracts 1 from the width (the original DL already stores width-1,
-// and the macro subtracts 1 again). This causes texture_to_load.width to be off by 1,
-// which breaks effects that use G_LOADTILE (e.g. sparkles with its 176x22 texture strip).
-//
-// This factory preserves the original w0 bits exactly:
-// newW0 = (G_SETTIMG_OTR_HASH << 24) | (w0 & 0x00FFFFFF)
-
-#include "EffectDListFactory.h"
-#include "Companion.h"
-#include "utils/Decompressor.h"
-#include "spdlog/spdlog.h"
-#include "n64/gbi-otr.h"
-#include "strhash64/StrHash64.h"
-
-// F3DEX2 opcodes
-#define F3DEX2_G_VTX 0x01
-#define F3DEX2_G_DL 0xDE
-#define F3DEX2_G_MTX 0xDA
-#define F3DEX2_G_ENDDL 0xDF
-#define F3DEX2_G_SETTIMG 0xFD
-#define F3DEX2_G_MOVEMEM 0xDC
-
-#define C0(pos, width) ((w0 >> (pos)) & ((1U << width) - 1))
-
-// Override DListFactory::parse to skip auto-discovery of sub-DL/VTX/light assets.
-// All effect assets are already defined in the YAML files, so auto-discovery via
-// Companion::Instance->AddAsset() is unnecessary and harmful: it re-registers
-// sub-DLs as "GFX" type (standard DListFactory), overwriting the YAML-defined
-// "PM64:EFFECT_DL" entries and causing them to be exported by the standard
-// DListBinaryExporter instead of PM64EffectDListBinaryExporter.
-std::optional<std::shared_ptr<IParsedData>> PM64EffectDListFactory::parse(std::vector<uint8_t>& raw_buffer, YAML::Node& node) {
- auto [_, segment] = Decompressor::AutoDecode(node, raw_buffer);
- LUS::BinaryReader reader(segment.data, segment.size);
- reader.SetEndianness(Torch::Endianness::Big);
-
- std::vector<uint32_t> gfxs;
- bool processing = true;
-
- while (processing) {
- auto w0 = reader.ReadUInt32();
- auto w1 = reader.ReadUInt32();
- uint8_t opcode = w0 >> 24;
-
- if (opcode == F3DEX2_G_ENDDL) {
- processing = false;
- }
-
- if (opcode == F3DEX2_G_DL) {
- // If this is a branch (G_DL_NO_PUSH), stop processing like the base class does
- if ((w0 >> 16) & 0x01) {
- processing = false;
- }
- // Intentionally skip Companion::Instance->AddAsset() — assets defined in YAML
- }
-
- gfxs.push_back(w0);
- gfxs.push_back(w1);
- }
-
- return std::make_shared<DListData>(gfxs);
-}
-
-ExportResult PM64EffectDListBinaryExporter::Export(std::ostream& write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node& node, std::string* replacement) {
- auto cmds = std::static_pointer_cast<DListData>(raw)->mGfxs;
- auto writer = LUS::BinaryWriter();
-
- WriteHeader(writer, Torch::ResourceType::DisplayList, 0);
-
- // GBI version byte (F3DEX2)
- writer.Write(static_cast<int8_t>(GBIVersion::f3dex2));
-
- // Pad to 8-byte alignment
- while (writer.GetBaseAddress() % 8 != 0)
- writer.Write(static_cast<int8_t>(0xFF));
-
- // G_MARKER with resource hash
- auto bhash = CRC64((*replacement).c_str());
- writer.Write(static_cast<uint32_t>(G_MARKER << 24));
- writer.Write(static_cast<uint32_t>(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;
-
- if (opcode == F3DEX2_G_VTX) {
- auto ptr = w1;
- auto dec = Companion::Instance->GetSafeStringByAddr(ptr, "VTX");
-
- if (dec.has_value()) {
- uint64_t hash = CRC64(dec.value().c_str());
- if (hash == 0) {
- throw std::runtime_error("Vtx hash is 0 for " + dec.value());
- }
- // Construct G_VTX_OTR_HASH: preserve n/v0 bits, zero the vtx offset
- uint32_t newW0 = (G_VTX_OTR_HASH << 24) | (w0 & 0x00FFFFFF);
- writer.Write(newW0);
- writer.Write(static_cast<uint32_t>(0));
- writer.Write(static_cast<uint32_t>(hash >> 32));
- writer.Write(static_cast<uint32_t>(hash & 0xFFFFFFFF));
- } else {
- SPDLOG_WARN("Could not find vtx at 0x{:X}", ptr);
- writer.Write(w0);
- writer.Write(w1);
- }
- continue;
- }
-
- if (opcode == F3DEX2_G_DL) {
- auto ptr = w1;
- // Use GetStringByAddr (no type check) since sub-DLs are registered as
- // "PM64:EFFECT_DL" in YAML, not "GFX" as the standard factory expects.
- auto dec = Companion::Instance->GetStringByAddr(ptr);
- auto branch = (w0 >> 16) & 0x01; // G_DL_NO_PUSH
-
- // Construct G_DL_OTR_HASH
- uint32_t newW0 = (G_DL_OTR_HASH << 24) | (branch << 16);
- writer.Write(newW0);
- writer.Write(static_cast<uint32_t>(0));
-
- if (dec.has_value()) {
- uint64_t hash = CRC64(dec.value().c_str());
- writer.Write(static_cast<uint32_t>(hash >> 32));
- writer.Write(static_cast<uint32_t>(hash & 0xFFFFFFFF));
- } else {
- SPDLOG_WARN("Could not find display list at 0x{:X}", ptr);
- writer.Write(w0);
- writer.Write(w1);
- }
-
- if (branch) {
- // Append G_ENDDL after a branch
- writer.Write(static_cast<uint32_t>(F3DEX2_G_ENDDL << 24));
- writer.Write(static_cast<uint32_t>(0));
- }
- continue;
- }
-
- if (opcode == F3DEX2_G_MOVEMEM) {
- auto ptr = w1;
- auto res = Companion::Instance->GetStringByAddr(ptr);
- bool hasOffset = false;
-
- if (!res.has_value()) {
- res = Companion::Instance->GetStringByAddr(ptr - 0x8);
- hasOffset = res.has_value();
- if (!hasOffset) {
- SPDLOG_WARN("Could not find light {:X}", ptr);
- }
- }
-
- uint8_t index = C0(0, 8);
- uint8_t offset = C0(8, 8) * 8;
-
- uint32_t newW0 = (G_MOVEMEM_OTR_HASH << 24) | (w0 & 0x00FFFFFF);
- uint32_t newW1 = _SHIFTL(index, 24, 8) | _SHIFTL(offset, 16, 8) | _SHIFTL((uint8_t)(hasOffset ? 1 : 0), 8, 8);
-
- writer.Write(newW0);
- writer.Write(newW1);
-
- if (res.has_value()) {
- uint64_t hash = CRC64(res.value().c_str());
- writer.Write(static_cast<uint32_t>(hash >> 32));
- writer.Write(static_cast<uint32_t>(hash & 0xFFFFFFFF));
- } else {
- SPDLOG_WARN("Could not find light at 0x{:X}", ptr);
- writer.Write(w0);
- writer.Write(w1);
- }
- continue;
- }
-
- if (opcode == F3DEX2_G_SETTIMG) {
- auto ptr = w1;
- auto dec = Companion::Instance->GetSafeStringByAddr(ptr, "TEXTURE");
-
- // FIX: Preserve original w0 bits (fmt/siz/width) exactly.
- uint32_t newW0 = (G_SETTIMG_OTR_HASH << 24) | (w0 & 0x00FFFFFF);
- writer.Write(newW0);
- writer.Write(ptr);
-
- 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());
- }
- writer.Write(static_cast<uint32_t>(hash >> 32));
- writer.Write(static_cast<uint32_t>(hash & 0xFFFFFFFF));
- } else {
- SPDLOG_WARN("Could not find texture at 0x{:X}", ptr);
- writer.Write(w0);
- writer.Write(w1);
- }
- continue;
- }
-
- if (opcode == F3DEX2_G_MTX) {
- auto ptr = w1;
- auto dec = Companion::Instance->GetSafeStringByAddr(ptr, "MTX");
-
- uint32_t newW0 = (G_MTX_OTR << 24) | (w0 & 0x00FFFFFF);
- writer.Write(newW0);
- writer.Write(static_cast<uint32_t>(0));
-
- 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());
- }
- writer.Write(static_cast<uint32_t>(hash >> 32));
- writer.Write(static_cast<uint32_t>(hash & 0xFFFFFFFF));
- } else {
- SPDLOG_WARN("Could not find matrix at 0x{:X}", ptr);
- writer.Write(w0);
- writer.Write(w1);
- }
- continue;
- }
-
- // All other opcodes: pass through unchanged
- writer.Write(w0);
- writer.Write(w1);
- }
-
- writer.Finish(write);
- return std::nullopt;
-}
diff --git a/src/factories/pm64/EffectDListFactory.h b/src/factories/pm64/EffectDListFactory.h
deleted file mode 100644
index 9f9380a..0000000
--- a/src/factories/pm64/EffectDListFactory.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma once
-
-#include "factories/DisplayListFactory.h"
-
-class PM64EffectDListBinaryExporter : public BaseExporter {
- ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override;
-};
-
-class PM64EffectDListFactory : public DListFactory {
-public:
- // Override parse to skip auto-discovery of sub-DL/VTX/light assets.
- // DListFactory::parse auto-discovers G_DL references and registers them as "GFX" type,
- // which overwrites YAML-defined "PM64:EFFECT_DL" entries and causes sub-DLs to be
- // exported by the standard DListBinaryExporter (with the width-subtraction bug).
- // All effect assets are already defined in the YAML, so no auto-discovery is needed.
- std::optional<std::shared_ptr<IParsedData>> parse(std::vector<uint8_t>& buffer, YAML::Node& data) override;
-
- std::unordered_map<ExportType, std::shared_ptr<BaseExporter>> GetExporters() override {
- return {
- REGISTER(Header, DListHeaderExporter)
- REGISTER(Binary, PM64EffectDListBinaryExporter)
- };
- }
-};