diff --git a/soh/assets/objects/gameplay_keep/gameplay_keep.h b/soh/assets/objects/gameplay_keep/gameplay_keep.h
index c67086b94..eebb19ab1 100644
--- a/soh/assets/objects/gameplay_keep/gameplay_keep.h
+++ b/soh/assets/objects/gameplay_keep/gameplay_keep.h
@@ -20,6 +20,9 @@ static const ALIGN_ASSET(2) char gBottleGlassTex[] = dgBottleGlassTex;
#define dgDekuStickTex "__OTR__objects/gameplay_keep/gDekuStickTex"
static const ALIGN_ASSET(2) char gDekuStickTex[] = dgDekuStickTex;
+#define dgDekuStickOverflowTex "__OTR__objects/gameplay_keep/gDekuStickOverflowTex"
+static const ALIGN_ASSET(2) char gDekuStickOverflowTex[] = dgDekuStickOverflowTex;
+
#define dgLinkHairTex "__OTR__objects/gameplay_keep/gLinkHairTex"
static const ALIGN_ASSET(2) char gLinkHairTex[] = dgLinkHairTex;
@@ -2375,6 +2378,9 @@ static const ALIGN_ASSET(2) char gEffUnknown11Tex[] = dgEffUnknown11Tex;
#define dgEffUnknown12Tex "__OTR__objects/gameplay_keep/gEffUnknown12Tex"
static const ALIGN_ASSET(2) char gEffUnknown12Tex[] = dgEffUnknown12Tex;
+#define dgEffUnknown12OverflowTex "__OTR__objects/gameplay_keep/gEffUnknown12OverflowTex"
+static const ALIGN_ASSET(2) char gEffUnknown12OverflowTex[] = dgEffUnknown12OverflowTex;
+
#define dgUnknownWoodBoardTex "__OTR__objects/gameplay_keep/gUnknownWoodBoardTex"
static const ALIGN_ASSET(2) char gUnknownWoodBoardTex[] = dgUnknownWoodBoardTex;
diff --git a/soh/assets/objects/object_ik/object_ik.h b/soh/assets/objects/object_ik/object_ik.h
index 5996b0cf3..37c46d207 100644
--- a/soh/assets/objects/object_ik/object_ik.h
+++ b/soh/assets/objects/object_ik/object_ik.h
@@ -101,6 +101,9 @@ static const ALIGN_ASSET(2) char object_ik_Tlut_00F630[] = dobject_ik_Tlut_00F63
#define dobject_ik_Tex_00F7A0 "__OTR__objects/object_ik/object_ik_Tex_00F7A0"
static const ALIGN_ASSET(2) char object_ik_Tex_00F7A0[] = dobject_ik_Tex_00F7A0;
+#define dgIronKnuckleMetalOverflowTex "__OTR__objects/object_ik/gIronKnuckleMetalOverflowTex"
+static const ALIGN_ASSET(2) char gIronKnuckleMetalOverflowTex[] = dgIronKnuckleMetalOverflowTex;
+
#define dobject_ik_Tex_00FBA0 "__OTR__objects/object_ik/object_ik_Tex_00FBA0"
static const ALIGN_ASSET(2) char object_ik_Tex_00FBA0[] = dobject_ik_Tex_00FBA0;
diff --git a/soh/assets/xml/GC_MQ_D/objects/gameplay_keep.xml b/soh/assets/xml/GC_MQ_D/objects/gameplay_keep.xml
index c3011f8fd..2a8ca9b44 100644
--- a/soh/assets/xml/GC_MQ_D/objects/gameplay_keep.xml
+++ b/soh/assets/xml/GC_MQ_D/objects/gameplay_keep.xml
@@ -7,6 +7,10 @@
+
+
+
@@ -809,6 +813,10 @@
+
+
+
diff --git a/soh/assets/xml/GC_MQ_D/objects/object_ik.xml b/soh/assets/xml/GC_MQ_D/objects/object_ik.xml
index 95606e6df..7d10babfa 100644
--- a/soh/assets/xml/GC_MQ_D/objects/object_ik.xml
+++ b/soh/assets/xml/GC_MQ_D/objects/object_ik.xml
@@ -38,6 +38,10 @@
+
+
+
diff --git a/soh/assets/xml/GC_NMQ_D/objects/gameplay_keep.xml b/soh/assets/xml/GC_NMQ_D/objects/gameplay_keep.xml
index fbcec6f37..28b2bab81 100644
--- a/soh/assets/xml/GC_NMQ_D/objects/gameplay_keep.xml
+++ b/soh/assets/xml/GC_NMQ_D/objects/gameplay_keep.xml
@@ -7,6 +7,10 @@
+
+
+
@@ -806,6 +810,10 @@
+
+
+
diff --git a/soh/assets/xml/GC_NMQ_D/objects/object_ik.xml b/soh/assets/xml/GC_NMQ_D/objects/object_ik.xml
index 95606e6df..7d10babfa 100644
--- a/soh/assets/xml/GC_NMQ_D/objects/object_ik.xml
+++ b/soh/assets/xml/GC_NMQ_D/objects/object_ik.xml
@@ -38,6 +38,10 @@
+
+
+
diff --git a/soh/assets/xml/GC_NMQ_PAL_F/objects/gameplay_keep.xml b/soh/assets/xml/GC_NMQ_PAL_F/objects/gameplay_keep.xml
index fbcec6f37..28b2bab81 100644
--- a/soh/assets/xml/GC_NMQ_PAL_F/objects/gameplay_keep.xml
+++ b/soh/assets/xml/GC_NMQ_PAL_F/objects/gameplay_keep.xml
@@ -7,6 +7,10 @@
+
+
+
@@ -806,6 +810,10 @@
+
+
+
diff --git a/soh/assets/xml/GC_NMQ_PAL_F/objects/object_ik.xml b/soh/assets/xml/GC_NMQ_PAL_F/objects/object_ik.xml
index 95606e6df..7d10babfa 100644
--- a/soh/assets/xml/GC_NMQ_PAL_F/objects/object_ik.xml
+++ b/soh/assets/xml/GC_NMQ_PAL_F/objects/object_ik.xml
@@ -38,6 +38,10 @@
+
+
+
diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
index d23cf73d6..7e13fd598 100644
--- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
+++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
@@ -1,4 +1,5 @@
#include "CosmeticsEditor.h"
+#include "authenticGfxPatches.h"
#include
#include "soh/Enhancements/game-interactor/GameInteractor.h"
@@ -1817,6 +1818,7 @@ void InitCosmeticsEditor() {
}
SohImGui::RequestCvarSaveOnNextTick();
ApplyOrResetCustomGfxPatches();
+ ApplyAuthenticGfxPatches();
RegisterOnLoadGameHook();
}
diff --git a/soh/soh/Enhancements/cosmetics/authenticGfxPatches.cpp b/soh/soh/Enhancements/cosmetics/authenticGfxPatches.cpp
new file mode 100644
index 000000000..1eec0c096
--- /dev/null
+++ b/soh/soh/Enhancements/cosmetics/authenticGfxPatches.cpp
@@ -0,0 +1,191 @@
+#include
+#include
+
+extern "C" {
+#include
+#include "objects/gameplay_keep/gameplay_keep.h"
+#include "objects/object_fz/object_fz.h"
+#include "objects/object_ik/object_ik.h"
+#include "objects/object_link_child/object_link_child.h"
+
+void ResourceMgr_PatchGfxByName(const char* path, const char* patchName, int index, Gfx instruction);
+void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName);
+}
+
+typedef struct {
+ const char* dlist;
+ int startInstruction;
+} DListPatchInfo;
+
+static DListPatchInfo freezardEffectDListPatchInfos[] = {
+ { gFreezardIntactDL, 5 },
+ { gFreezardTopRightHornChippedDL, 5 },
+ { gFreezardHeadChippedDL, 5 },
+ { gFreezardIceTriangleDL, 5 },
+ { gFreezardIceRockDL, 5 },
+};
+
+static DListPatchInfo ironKnuckleDListPatchInfos[] = {
+ { object_ik_DL_01BE98, 39 },
+ { object_ik_DL_01BE98, 59 },
+
+ { object_ik_DL_01C130, 38 },
+
+ { object_ik_DL_01C2B8, 39 },
+ { object_ik_DL_01C2B8, 59 },
+
+ { object_ik_DL_01C550, 38 },
+
+ { object_ik_DL_01C7B8, 8 },
+ { object_ik_DL_01C7B8, 28 },
+
+ { object_ik_DL_01CB58, 8 },
+ { object_ik_DL_01CB58, 31 },
+
+ { object_ik_DL_01CCA0, 15 },
+ { object_ik_DL_01CCA0, 37 },
+ { object_ik_DL_01CCA0, 52 },
+ { object_ik_DL_01CCA0, 68 },
+
+ { object_ik_DL_01CEE0, 27 },
+ { object_ik_DL_01CEE0, 46 },
+ { object_ik_DL_01CEE0, 125 },
+
+ { object_ik_DL_01D2B0, 8 },
+ { object_ik_DL_01D2B0, 32 },
+
+ { object_ik_DL_01D3F8, 15 },
+ { object_ik_DL_01D3F8, 37 },
+ { object_ik_DL_01D3F8, 52 },
+ { object_ik_DL_01D3F8, 68 },
+
+ { object_ik_DL_01D638, 23 },
+ { object_ik_DL_01D638, 42 },
+ { object_ik_DL_01D638, 110 },
+};
+
+void PatchDekuStickTextureOverflow() {
+ // Custom texture for holding Deku Stick that accounts for overflow texture reading
+ Gfx gDekuStickOverflowTexFix = gsDPSetTextureImage(G_IM_FMT_I, G_IM_SIZ_8b, 1, gDekuStickOverflowTex);
+
+ // Gfx instructions to fix authentic vanilla bug where the Deku Stick texture is read as the wrong size
+ Gfx gDekuStickTexFix[] = {
+ gsDPLoadTextureBlock(gDekuStickTex, G_IM_FMT_I, G_IM_SIZ_8b, 8, 8, 0, G_TX_NOMIRROR | G_TX_WRAP,
+ G_TX_NOMIRROR | G_TX_WRAP, 4, 4, G_TX_NOLOD, G_TX_NOLOD)
+ };
+
+ const char* dlist = gLinkChildLinkDekuStickDL;
+ int start = 5;
+
+ if (!CVarGetInteger("gFixTexturesOOB", 0)) {
+ // Unpatch the other texture fix
+ for (size_t i = 0; i < 7; i++) {
+ int instruction = start + (i == 0 ? 0 : i + 1);
+ std::string unpatchName = "DekuStickFix" + std::to_string(instruction);
+ ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str());
+ }
+
+ std::string patchName = "DekuStickOverflow" + std::to_string(start);
+ ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), start, gDekuStickOverflowTexFix);
+ } else {
+ // Unpatch the other texture fix
+ std::string unpatchName = "DekuStickOverflow" + std::to_string(start);
+ ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str());
+
+ for (size_t i = 0; i < 7; i++) {
+ int instruction = start + (i == 0 ? 0 : i + 1);
+ std::string patchName = "DekuStickFix" + std::to_string(instruction);
+ ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), instruction, gDekuStickTexFix[i]);
+ }
+ }
+}
+
+void PatchFreezardTextureOverflow() {
+ // Custom texture for Freezard effect that accounts for overflow texture reading
+ Gfx gEffUnknown12OverflowTextFix = gsDPSetTextureImage(G_IM_FMT_IA, G_IM_SIZ_16b, 1, gEffUnknown12OverflowTex);
+
+ // Gfx instructions to fix authentic vanilla bug where the Freezard effect texture is read as the wrong format
+ Gfx gEffUnknown12TexFix[] = {
+ gsDPLoadTextureBlock(gEffUnknown12Tex, G_IM_FMT_I, G_IM_SIZ_8b, 32, 32, 0, G_TX_NOMIRROR |
+ G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 5, 5, G_TX_NOLOD, G_TX_NOLOD)
+ };
+
+ for (const auto& patchInfo : freezardEffectDListPatchInfos) {
+ const char* dlist = patchInfo.dlist;
+ int start = patchInfo.startInstruction;
+
+ char patchNameBuf[24];
+
+ // Patch using custom overflowed texture
+ if (!CVarGetInteger("gFixTexturesOOB", 0)) {
+ // Unpatch the other texture fix
+ for (size_t i = 0; i < 7; i++) {
+ int instruction = start + (i == 0 ? 0 : i + 1);
+ std::string unpatchName = "gEffUnknown12Fix" + std::to_string(instruction);
+ ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str());
+ }
+
+ std::string patchName = "gEffUnknown12Overflow" + std::to_string(start);
+ ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), start, gEffUnknown12OverflowTextFix);
+ } else { // Patch texture to use correct image size
+ // Unpatch the other texture fix
+ std::string unpatchName = "gEffUnknown12Overflow" + std::to_string(start);
+ ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str());
+
+ for (size_t i = 0; i < 7; i++) {
+ int instruction = start + (i == 0 ? 0 : i + 1);
+ std::string patchName = "gEffUnknown12Fix" + std::to_string(instruction);
+ ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), instruction, gEffUnknown12TexFix[i]);
+ }
+ }
+ }
+}
+
+void PatchIronKnuckleTextureOverflow() {
+ // Custom texture for Iron Knuckle that accounts for overflow texture reading
+ Gfx gIronKnuckleMetalOverflowTexFix = gsDPSetTextureImage(G_IM_FMT_I, G_IM_SIZ_8b, 1, gIronKnuckleMetalOverflowTex);
+
+ // Gfx instructions to fix authentic vanilla bug where the Iron Knuckle texture is read as the wrong format
+ Gfx gIronKnuckleMetalTexFix[] = {
+ gsDPLoadTextureBlock(object_ik_Tex_00F7A0, G_IM_FMT_I, G_IM_SIZ_4b, 32, 64, 0, G_TX_MIRROR | G_TX_WRAP,
+ G_TX_MIRROR | G_TX_WRAP, 5, 6, G_TX_NOLOD, G_TX_NOLOD)
+ };
+
+ for (const auto& patchInfo : ironKnuckleDListPatchInfos) {
+ const char* dlist = patchInfo.dlist;
+ int start = patchInfo.startInstruction;
+
+ // OTRTODO: Patching to use the correct size format for Iron Knuckle causes a tile size failure
+ // Until this is solved, Iron Knuckle will be hardcoded to always display with the "authentic" texture fix
+
+ // Patch using custom overflowed texture
+ // if (!CVarGetInteger("gFixTexturesOOB", 0)) {
+ // Unpatch the other texture fix
+ for (size_t i = 0; i < 7; i++) {
+ int instruction = start + (i == 0 ? 0 : i + 1);
+ std::string unpatchName = "MetalTexFix" + std::to_string(instruction);
+ ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str());
+ }
+
+ std::string patchName = "MetalTexOverflow" + std::to_string(start);
+ ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), start, gIronKnuckleMetalOverflowTexFix);
+ // } else { // Patch texture to use correct image size
+ // // Unpatch the other texture fix
+ // std::string unpatchName = "MetalTexOverflow" + std::to_string(start);
+ // ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str());
+
+ // // Patch texture to use correct image size
+ // for (size_t i = 0; i < 7; i++) {
+ // int instruction = start + (i == 0 ? 0 : i + 1);
+ // std::string patchName = "MetalTexFix" + std::to_string(instruction);
+ // ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), instruction, gIronKnuckleMetalTexFix[i]);
+ // }
+ // }
+ }
+}
+
+void ApplyAuthenticGfxPatches() {
+ PatchDekuStickTextureOverflow();
+ PatchFreezardTextureOverflow();
+ PatchIronKnuckleTextureOverflow();
+}
diff --git a/soh/soh/Enhancements/cosmetics/authenticGfxPatches.h b/soh/soh/Enhancements/cosmetics/authenticGfxPatches.h
new file mode 100644
index 000000000..fda0f5096
--- /dev/null
+++ b/soh/soh/Enhancements/cosmetics/authenticGfxPatches.h
@@ -0,0 +1,3 @@
+#pragma once
+
+void ApplyAuthenticGfxPatches();
diff --git a/soh/soh/GameMenuBar.cpp b/soh/soh/GameMenuBar.cpp
index a6d091624..952186786 100644
--- a/soh/soh/GameMenuBar.cpp
+++ b/soh/soh/GameMenuBar.cpp
@@ -34,6 +34,7 @@
#endif
#include "Enhancements/game-interactor/GameInteractor.h"
+#include "Enhancements/cosmetics/authenticGfxPatches.h"
bool isBetaQuestEnabled = false;
@@ -909,6 +910,10 @@ namespace GameMenuBar {
UIWidgets::Tooltip(
"Adds 5 higher pitches for the Silver Rupee Jingle for the rooms with more than 5 Silver Rupees. "
"Currently only relevant in Master Quest.");
+ if (UIWidgets::PaddedEnhancementCheckbox("Fix out of bounds textures", "gFixTexturesOOB", true, false)) {
+ ApplyAuthenticGfxPatches();
+ }
+ UIWidgets::Tooltip("Fixes authentic out of bounds texture reads, instead loading textures with the correct size");
ImGui::EndMenu();
}