diff --git a/mm/2s2h/BenGui/BenMenuBar.cpp b/mm/2s2h/BenGui/BenMenuBar.cpp index deb7003d4..2911b6458 100644 --- a/mm/2s2h/BenGui/BenMenuBar.cpp +++ b/mm/2s2h/BenGui/BenMenuBar.cpp @@ -8,6 +8,7 @@ #include #include #include "2s2h/Enhancements/Enhancements.h" +#include "2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.h" #include "2s2h/DeveloperTools/DeveloperTools.h" #include "HudEditor.h" @@ -605,6 +606,13 @@ void DrawEnhancementsMenu() { { .tooltip = "Fixes a bug that results in the Ikana Great Fairy fountain looking " "green instead of yellow, this was fixed in the EU version" }); + if (UIWidgets::CVarCheckbox( + "Fix Texture overflow OOB", "gEnhancements.Fixes.FixTexturesOOB", + { .tooltip = "Fixes textures that normally overflow to be patched with the correct size or format", + .defaultValue = true })) { + GfxPatcher_ApplyOverflowTexturePatches(); + } + ImGui::EndMenu(); } diff --git a/mm/2s2h/BenGui/SearchableMenuItems.h b/mm/2s2h/BenGui/SearchableMenuItems.h index fa93b8ca7..6e5d65a9f 100644 --- a/mm/2s2h/BenGui/SearchableMenuItems.h +++ b/mm/2s2h/BenGui/SearchableMenuItems.h @@ -1,5 +1,6 @@ #include "2s2h/Enhancements/Enhancements.h" #include "2s2h/DeveloperTools/DeveloperTools.h" +#include "2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.h" #include "UIWidgets.hpp" #include "BenMenuBar.h" #include "Notification.h" @@ -1366,7 +1367,13 @@ void AddEnhancements() { { "Fix Ikana Great Fairy Fountain Color", "gFixes.FixIkanaGreatFairyFountainColor", "Fixes a bug that results in the Ikana Great Fairy fountain looking green instead of yellow, this was " "fixed in the EU version", - WIDGET_CVAR_CHECKBOX } } } }); + WIDGET_CVAR_CHECKBOX }, + { .widgetName = "Fix Texture overflow OOB", + .widgetCVar = "gEnhancements.Fixes.FixTexturesOOB", + .widgetTooltip = "Fixes textures that normally overflow to be patched with the correct size or format", + .widgetType = WIDGET_CVAR_CHECKBOX, + .widgetOptions = { .defaultVariant = true }, + .widgetCallback = [](widgetInfo& info) { GfxPatcher_ApplyOverflowTexturePatches(); } } } } }); enhancementsSidebar.push_back( { "Restorations", 3, diff --git a/mm/2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.cpp b/mm/2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.cpp index 3364ff86b..eb3d86397 100644 --- a/mm/2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.cpp +++ b/mm/2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.cpp @@ -1,7 +1,11 @@ #include "AuthenticGfxPatches.h" +#include "libultraship/libultraship.h" +#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 "overlays/ovl_En_Syateki_Okuta/ovl_En_Syateki_Okuta.h" #include "overlays/ovl_Obj_Jgame_Light/ovl_Obj_Jgame_Light.h" @@ -10,6 +14,231 @@ void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName); Gfx* ResourceMgr_LoadGfxByName(const char* path); } +#define dgameplay_keep_Tex_00CA30_Overflow "__OTR__objects/gameplay_keep/gameplay_keep_Tex_00CA30_Overflow" +static const ALIGN_ASSET(2) char gameplay_keep_Tex_00CA30_Overflow[] = dgameplay_keep_Tex_00CA30_Overflow; + +#define dgEffIceFragmentTex_Overflow "__OTR__objects/gameplay_keep/gEffIceFragmentTex_Overflow" +static const ALIGN_ASSET(2) char gEffIceFragmentTex_Overflow[] = dgEffIceFragmentTex_Overflow; + +#define dgIronKnuckleFireTex_Overflow "__OTR__objects/object_ik/gIronKnuckleFireTex_Overflow" +static const ALIGN_ASSET(2) char gIronKnuckleFireTex_Overflow[] = dgIronKnuckleFireTex_Overflow; + +typedef struct { + const char* dlist; + int startInstruction; +} DListPatchInfo; + +static DListPatchInfo freezardBodyDListPatchInfos[] = { + { object_fz_DL_001130, 5 }, { object_fz_DL_0021A0, 5 }, { object_fz_DL_002CA0, 5 }, + { object_fz_DL_003260, 5 }, { object_fz_DL_0033F0, 5 }, +}; + +static DListPatchInfo ironKnuckleDListPatchInfos[] = { + { gIronKnuckleVambraceLeftDL, 39 }, + { gIronKnuckleVambraceLeftDL, 59 }, + + { gIronKnuckleArmLeftDL, 38 }, + + { gIronKnuckleVambraceRightDL, 39 }, + { gIronKnuckleVambraceRightDL, 59 }, + + { gIronKnuckleArmRightDL, 38 }, + + { gIronKnuckleWaistDL, 8 }, + { gIronKnuckleWaistDL, 28 }, + + { gIronKnucklePauldronLeftDL, 8 }, + { gIronKnucklePauldronLeftDL, 31 }, + + { gIronKnuckleBootTipLeftDL, 15 }, + { gIronKnuckleBootTipLeftDL, 37 }, + { gIronKnuckleBootTipLeftDL, 52 }, + { gIronKnuckleBootTipLeftDL, 68 }, + + { gIronKnuckleWaistArmorLeftDL, 27 }, + { gIronKnuckleWaistArmorLeftDL, 46 }, + { gIronKnuckleWaistArmorLeftDL, 121 }, + + { gIronKnucklePauldronRightDL, 8 }, + { gIronKnucklePauldronRightDL, 32 }, + + { gIronKnuckleBootTipRightDL, 15 }, + { gIronKnuckleBootTipRightDL, 37 }, + { gIronKnuckleBootTipRightDL, 52 }, + { gIronKnuckleBootTipRightDL, 68 }, + + { gIronKnuckleWaistArmorRightDL, 23 }, + { gIronKnuckleWaistArmorRightDL, 42 }, + { gIronKnuckleWaistArmorRightDL, 106 }, +}; + +static DListPatchInfo arrowTipDListPatchInfos[] = { + { gameplay_keep_DL_013FF0, 46 }, + { gameplay_keep_DL_014370, 5 }, +}; + +void PatchArrowTipTexture() { + // Custom texture for Arrow tips that accounts for overflow texture reading + Gfx arrowTipTextureWithOverflowFixGfx = + gsDPSetTextureImage(G_IM_FMT_RGBA, G_IM_SIZ_16b_LOAD_BLOCK, 1, gameplay_keep_Tex_00CA30_Overflow); + + // Gfx instructions to fix authentic vanilla bug where the Arrow tips texture is read as the wrong size + Gfx arrowTipTextureWithSizeFixGfx[] = { + gsDPLoadTextureBlock(gameplay_keep_Tex_00CA30, G_IM_FMT_RGBA, G_IM_SIZ_16b, 16, 16, 0, G_TX_MIRROR | G_TX_WRAP, + G_TX_MIRROR | G_TX_WRAP, 5, 5, 1, 1), + }; + + bool fixTexturesOOB = CVarGetInteger("gEnhancements.Fixes.FixTexturesOOB", 0); + + for (const auto& patchInfo : arrowTipDListPatchInfos) { + const char* dlist = patchInfo.dlist; + int start = patchInfo.startInstruction; + + // Patch using custom overflowed texture + if (!fixTexturesOOB) { + // Unpatch the other texture fix + for (size_t i = 4; i < 8; i++) { + int instruction = start + i; + std::string unpatchName = "arrowTipTextureWithSizeFix_" + std::to_string(instruction); + ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str()); + } + + std::string patchName = "arrowTipTextureWithOverflowFix_" + std::to_string(start); + std::string patchName2 = "arrowTipTextureWithOverflowFix_" + std::to_string(start + 1); + ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), start, arrowTipTextureWithOverflowFixGfx); + ResourceMgr_PatchGfxByName(dlist, patchName2.c_str(), start + 1, gsSPNoOp()); + } else { // Patch texture to use correct image size/fmt + // Unpatch the other texture fix + std::string unpatchName = "arrowTipTextureWithOverflowFix_" + std::to_string(start); + std::string unpatchName2 = "arrowTipTextureWithOverflowFix_" + std::to_string(start + 1); + ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str()); + ResourceMgr_UnpatchGfxByName(dlist, unpatchName2.c_str()); + + for (size_t i = 4; i < 8; i++) { + int instruction = start + i; + std::string patchName = "arrowTipTextureWithSizeFix_" + std::to_string(instruction); + + if (i == 0) { + ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), instruction, gsSPNoOp()); + } else { + ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), instruction, + arrowTipTextureWithSizeFixGfx[i - 1]); + } + } + } + } +} + +void PatchFreezardBodyTexture() { + // Custom texture for Freezard effect that accounts for overflow texture reading + Gfx freezardBodyTextureWithOverflowFixGfx = + gsDPSetTextureImage(G_IM_FMT_IA, G_IM_SIZ_16b_LOAD_BLOCK, 1, gEffIceFragmentTex_Overflow); + + // Gfx instructions to fix authentic vanilla bug where the Freezard effect texture is read as the wrong format + Gfx freezardBodyTextureWithFormatFixGfx[] = { + gsDPLoadTextureBlock(gEffIceFragmentTex, 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), + }; + + bool fixTexturesOOB = CVarGetInteger("gEnhancements.Fixes.FixTexturesOOB", 0); + + for (const auto& patchInfo : freezardBodyDListPatchInfos) { + const char* dlist = patchInfo.dlist; + int start = patchInfo.startInstruction; + + // Patch using custom overflowed texture + if (!fixTexturesOOB) { + // Unpatch the other texture fix + for (size_t i = 0; i < 8; i++) { + int instruction = start + i; + std::string unpatchName = "freezardBodyTextureWithFormatFix_" + std::to_string(instruction); + ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str()); + } + + std::string patchName = "freezardBodyTextureWithOverflowFix_" + std::to_string(start); + std::string patchName2 = "freezardBodyTextureWithOverflowFix_" + std::to_string(start + 1); + ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), start, freezardBodyTextureWithOverflowFixGfx); + ResourceMgr_PatchGfxByName(dlist, patchName2.c_str(), start + 1, gsSPNoOp()); + } else { // Patch texture to use correct image size/fmt + // Unpatch the other texture fix + std::string unpatchName = "freezardBodyTextureWithOverflowFix_" + std::to_string(start); + std::string unpatchName2 = "freezardBodyTextureWithOverflowFix_" + std::to_string(start + 1); + ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str()); + ResourceMgr_UnpatchGfxByName(dlist, unpatchName2.c_str()); + + for (size_t i = 0; i < 8; i++) { + int instruction = start + i; + std::string patchName = "freezardBodyTextureWithFormatFix_" + std::to_string(instruction); + + if (i == 0) { + ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), instruction, gsSPNoOp()); + } else { + ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), instruction, + freezardBodyTextureWithFormatFixGfx[i - 1]); + } + } + } + } +} + +void PatchIronKnuckleFireTexture() { + // Custom texture for Iron Knuckle texture that accounts for overflow texture reading + Gfx ironKnuckleFireTexWithOverflowFixGfx = + gsDPSetTextureImage(G_IM_FMT_I, G_IM_SIZ_8b_LOAD_BLOCK, 1, gIronKnuckleFireTex_Overflow); + + // Gfx instructions to fix authentic vanilla bug where the Iron Knuckle texture is read as the wrong size + Gfx ironKnuckleFireTexWithFormatFixGfx[] = { + gsDPLoadTextureBlock_4b(gIronKnuckleFireTex, G_IM_FMT_I, 32, 64, 0, G_TX_MIRROR | G_TX_WRAP, + G_TX_MIRROR | G_TX_WRAP, 5, 6, G_TX_NOLOD, G_TX_NOLOD), + }; + + bool fixTexturesOOB = CVarGetInteger("gEnhancements.Fixes.FixTexturesOOB", 0); + + for (const auto& patchInfo : ironKnuckleDListPatchInfos) { + const char* dlist = patchInfo.dlist; + int start = patchInfo.startInstruction; + + // Patch using custom overflowed texture + if (!fixTexturesOOB) { + // Unpatch the other texture fix + for (size_t i = 0; i < 8; i++) { + int instruction = start + i; + std::string unpatchName = "ironKnuckleFireTexWithSizeFix_" + std::to_string(instruction); + ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str()); + } + + std::string patchName = "ironKnuckleFireTexWithOverflowFix_" + std::to_string(start); + std::string patchName2 = "ironKnuckleFireTexWithOverflowFix_" + std::to_string(start + 1); + ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), start, ironKnuckleFireTexWithOverflowFixGfx); + ResourceMgr_PatchGfxByName(dlist, patchName2.c_str(), start + 1, gsSPNoOp()); + } else { // Patch texture to use correct image size/fmt + // Unpatch the other texture fix + std::string unpatchName = "ironKnuckleFireTexWithOverflowFix_" + std::to_string(start); + std::string unpatchName2 = "ironKnuckleFireTexWithOverflowFix_" + std::to_string(start + 1); + ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str()); + ResourceMgr_UnpatchGfxByName(dlist, unpatchName2.c_str()); + + for (size_t i = 0; i < 8; i++) { + int instruction = start + i; + std::string patchName = "ironKnuckleFireTexWithSizeFix_" + std::to_string(instruction); + + if (i == 0) { + ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), instruction, gsSPNoOp()); + } else { + ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), instruction, + ironKnuckleFireTexWithFormatFixGfx[i - 1]); + } + } + } + } +} + +void GfxPatcher_ApplyOverflowTexturePatches() { + PatchArrowTipTexture(); + PatchFreezardBodyTexture(); + PatchIronKnuckleFireTexture(); +} + void PatchMiniGameCrossAndCircleSymbols() { // The X and O displayed in mini-games are incorrectly set to FMT_I instead of FMT_IA, // Fast3D throws an assert and does nothing as FMT_I with SIZ_16 is not a valid texture type. @@ -39,4 +268,6 @@ void PatchMiniGameCrossAndCircleSymbols() { // Applies required patches for authentic bugs to allow the game to play and render properly void GfxPatcher_ApplyNecessaryAuthenticPatches() { PatchMiniGameCrossAndCircleSymbols(); + + GfxPatcher_ApplyOverflowTexturePatches(); } diff --git a/mm/2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.h b/mm/2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.h index 1a9c8b224..68eec242d 100644 --- a/mm/2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.h +++ b/mm/2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.h @@ -2,5 +2,6 @@ #define AUTHENTIC_GFX_PATCHES_H void GfxPatcher_ApplyNecessaryAuthenticPatches(); +void GfxPatcher_ApplyOverflowTexturePatches(); #endif // AUTHENTIC_GFX_PATCHES_H diff --git a/mm/assets/xml/GC_US/objects/gameplay_keep.xml b/mm/assets/xml/GC_US/objects/gameplay_keep.xml index 5da957423..f3e140c6f 100644 --- a/mm/assets/xml/GC_US/objects/gameplay_keep.xml +++ b/mm/assets/xml/GC_US/objects/gameplay_keep.xml @@ -85,6 +85,8 @@ + + @@ -1158,6 +1160,8 @@ + + diff --git a/mm/assets/xml/GC_US/objects/object_ik.xml b/mm/assets/xml/GC_US/objects/object_ik.xml index af271f081..df751f43a 100644 --- a/mm/assets/xml/GC_US/objects/object_ik.xml +++ b/mm/assets/xml/GC_US/objects/object_ik.xml @@ -58,6 +58,8 @@ + + diff --git a/mm/assets/xml/N64_US/objects/gameplay_keep.xml b/mm/assets/xml/N64_US/objects/gameplay_keep.xml index eee79cdd1..98141bcb6 100644 --- a/mm/assets/xml/N64_US/objects/gameplay_keep.xml +++ b/mm/assets/xml/N64_US/objects/gameplay_keep.xml @@ -85,6 +85,8 @@ + + @@ -1140,6 +1142,8 @@ + + diff --git a/mm/assets/xml/N64_US/objects/object_ik.xml b/mm/assets/xml/N64_US/objects/object_ik.xml index af271f081..df751f43a 100644 --- a/mm/assets/xml/N64_US/objects/object_ik.xml +++ b/mm/assets/xml/N64_US/objects/object_ik.xml @@ -58,6 +58,8 @@ + +