Files
Shiip-of-Hakinian-Espanol/soh/soh/Enhancements/cosmetics/authenticGfxPatches.cpp
2026-03-18 01:27:08 +00:00

401 lines
18 KiB
C++

#include <libultraship/bridge.h>
#include <string>
#include "soh/OTRGlobals.h"
#include "soh/cvar_prefixes.h"
#include "soh/ResourceManagerHelpers.h"
extern "C" {
#include <libultraship/libultra.h>
#include "objects/gameplay_keep/gameplay_keep.h"
#include "objects/object_fz/object_fz.h"
#include "objects/object_gi_soldout/object_gi_soldout.h"
#include "objects/object_ik/object_ik.h"
#include "objects/object_link_child/object_link_child.h"
}
typedef struct {
const char* dlist;
int startInstruction;
} DListPatchInfo;
static DListPatchInfo freezardBodyDListPatchInfos[] = {
{ gFreezardIntactDL, 5 }, { gFreezardTopRightHornChippedDL, 5 },
{ gFreezardHeadChippedDL, 5 }, { gFreezardIceTriangleDL, 5 },
{ gFreezardIceRockDL, 5 },
};
static DListPatchInfo ironKnuckleDListPatchInfos[] = {
// VambraceLeft
{ object_ik_DL_01BE98, 39 },
{ object_ik_DL_01BE98, 59 },
// ArmLeft
{ object_ik_DL_01C130, 38 },
// VambraceRight
{ object_ik_DL_01C2B8, 39 },
{ object_ik_DL_01C2B8, 59 },
// ArmRight
{ object_ik_DL_01C550, 38 },
// Waist
{ object_ik_DL_01C7B8, 8 },
{ object_ik_DL_01C7B8, 28 },
// PauldronLeft
{ object_ik_DL_01CB58, 8 },
{ object_ik_DL_01CB58, 31 },
// BootTipLeft
{ object_ik_DL_01CCA0, 15 },
{ object_ik_DL_01CCA0, 37 },
{ object_ik_DL_01CCA0, 52 },
{ object_ik_DL_01CCA0, 68 },
// WaistArmorLeft
{ object_ik_DL_01CEE0, 27 },
{ object_ik_DL_01CEE0, 46 },
{ object_ik_DL_01CEE0, 125 },
// PauldronRight
{ object_ik_DL_01D2B0, 8 },
{ object_ik_DL_01D2B0, 32 },
// BootTipRight
{ object_ik_DL_01D3F8, 15 },
{ object_ik_DL_01D3F8, 37 },
{ object_ik_DL_01D3F8, 52 },
{ object_ik_DL_01D3F8, 68 },
// WaistArmorRight
{ object_ik_DL_01D638, 23 },
{ object_ik_DL_01D638, 42 },
{ object_ik_DL_01D638, 110 },
};
static DListPatchInfo arrowTipDListPatchInfos[] = {
{ gArrowNearDL, 46 },
{ gArrowFarDL, 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, gHilite2Tex_Overflow);
// Gfx instructions to fix authentic vanilla bug where the Arrow tips texture is read as the wrong size
Gfx arrowTipTextureWithSizeFixGfx[] = {
gsDPLoadTextureBlock(gHilite2Tex, 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(CVAR_ENHANCEMENT("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 PatchDekuStickTextureOverflow() {
// Custom texture for holding Deku Stick that accounts for overflow texture reading
Gfx dekuSticTexkWithOverflowFixGfx = 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 dekuStickTexWithSizeFixGfx[] = {
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;
// Patch using custom overflowed texture
if (!CVarGetInteger(CVAR_ENHANCEMENT("FixTexturesOOB"), 0)) {
// Unpatch the other texture fix
for (size_t i = 0; i < 8; i++) {
int instruction = start + i;
std::string unpatchName = "dekuStickWithSizeFix_" + std::to_string(instruction);
ResourceMgr_UnpatchGfxByName(dlist, unpatchName.c_str());
}
std::string patchName = "dekuStickWithOverflowFix_" + std::to_string(start);
std::string patchName2 = "dekuStickWithOverflowFix_" + std::to_string(start + 1);
ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), start, dekuSticTexkWithOverflowFixGfx);
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 = "dekuStickWithOverflowFix_" + std::to_string(start);
std::string unpatchName2 = "dekuStickWithOverflowFix_" + 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 = "dekuStickWithSizeFix_" + std::to_string(instruction);
if (i == 0) {
ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), instruction, gsSPNoOp());
} else {
ResourceMgr_PatchGfxByName(dlist, patchName.c_str(), instruction, dekuStickTexWithSizeFixGfx[i - 1]);
}
}
}
}
void PatchFreezardTextureOverflow() {
// Custom texture for Freezard effect that accounts for overflow texture reading
Gfx freezardBodyTextureWithOverflowFixGfx =
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 freezardBodyTextureWithFormatFixGfx[] = {
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),
};
bool fixTexturesOOB = CVarGetInteger(CVAR_ENHANCEMENT("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 PatchIronKnuckleTextureOverflow() {
// Custom texture for Iron Knuckle that accounts for overflow texture reading
Gfx ironKnuckleFireTexWithOverflowFixGfx =
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 ironKnuckleFireTexWithFormatFixGfx[] = {
gsDPLoadTextureBlock_4b(gIronKnuckleMetalTex, 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(CVAR_ENHANCEMENT("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, ironKnuckleFireTexWithOverflowFixGfx);
} 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 PatchBoulderFragment() {
// The boulder fragment renders invisible due to the change made by https://github.com/Kenix3/libultraship/pull/721
// Until it is known whether this change is appropriate or something else should be done to it, the following
// patches adjust the render mode for the DL to not become invisible
ResourceMgr_PatchGfxByName(gBoulderFragmentsDL, "boulderFragmentRenderFix3", 3,
gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_OPA_SURF2));
ResourceMgr_PatchGfxByName(gBoulderFragmentsDL, "boulderFragmentRenderFix6", 6,
gsDPSetCombineMode(G_CC_MODULATEIDECALA, G_CC_MODULATEIA_PRIM2));
}
void ApplyAuthenticGfxPatches() {
// Overflow textures
PatchArrowTipTexture();
PatchDekuStickTextureOverflow();
PatchFreezardTextureOverflow();
PatchIronKnuckleTextureOverflow();
PatchBoulderFragment();
}
// Patches the Sold Out GI DL to render the texture in the mirror boundary
void PatchMirroredSoldOutGI() {
static const char gSoldOutGIVtx[] = "__OTR__objects/object_gi_soldout/object_gi_soldoutVtx_000400";
static Vtx* mirroredSoldOutVtx;
// Using a dummy texture here, but will be ignoring the texture command itself
// Only need to patch over the two SetTile commands to get the MIRROR effect
Gfx mirroredSoldOutTex[] = {
gsDPLoadTextureBlock("", G_IM_FMT_IA, G_IM_SIZ_8b, 32, 32, 0, G_TX_MIRROR | G_TX_WRAP,
G_TX_NOMIRROR | G_TX_CLAMP, 5, 5, G_TX_NOLOD, G_TX_NOLOD),
};
if (CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0)) {
if (mirroredSoldOutVtx == nullptr) {
// Copy the original vertices that we want to modify (4 at the beginning of the resource)
mirroredSoldOutVtx = (Vtx*)malloc(sizeof(Vtx) * 4);
Vtx* origVtx = (Vtx*)ResourceGetDataByName(gSoldOutGIVtx);
memcpy(mirroredSoldOutVtx, origVtx, sizeof(Vtx) * 4);
// Offset the vertex U coordinate values by the width of the texture
for (size_t i = 0; i < 4; i++) {
mirroredSoldOutVtx[i].v.tc[0] += 32 << 5;
}
}
ResourceMgr_PatchGfxByName(gGiSoldOutDL, "SoldOutGITexture_1", 9, mirroredSoldOutTex[1]);
ResourceMgr_PatchGfxByName(gGiSoldOutDL, "SoldOutGITexture_2", 13, mirroredSoldOutTex[5]);
ResourceMgr_PatchGfxByName(gGiSoldOutDL, "SoldOutGITextureCords_1", 17, gsSPVertex(mirroredSoldOutVtx, 4, 0));
// noop as the original vertex command is 128 bit wide
ResourceMgr_PatchGfxByName(gGiSoldOutDL, "SoldOutGITextureCords_2", 18, gsSPNoOp());
} else {
if (mirroredSoldOutVtx != nullptr) {
free(mirroredSoldOutVtx);
mirroredSoldOutVtx = nullptr;
}
ResourceMgr_UnpatchGfxByName(gGiSoldOutDL, "SoldOutGITexture_1");
ResourceMgr_UnpatchGfxByName(gGiSoldOutDL, "SoldOutGITexture_2");
ResourceMgr_UnpatchGfxByName(gGiSoldOutDL, "SoldOutGITextureCords_1");
ResourceMgr_UnpatchGfxByName(gGiSoldOutDL, "SoldOutGITextureCords_2");
}
}
// Patches the Sun Song Etching in the Royal Grave to be mirrored in mirror mode
// This is achieved by mirroring the texture at the boundary and overriding the vertex texture coordinates
void PatchMirroredSunSongEtching() {
// Only using these strings for graphics patching lookup, we don't need aligned assets here
static const char gRoyalGraveBackRoomDL[] = "__OTR__scenes/shared/hakaana_ouke_scene/hakaana_ouke_room_2DL_005040";
static const char gRoyalGraveBackRoomSongVtx[] =
"__OTR__scenes/shared/hakaana_ouke_scene/hakaana_ouke_room_2Vtx_004F80";
static Vtx* mirroredSunSongVtx;
// Using a dummy texture here, but will be ignoring the texture command itself
// Only need to patch over the two SetTile commands to get the MIRROR effect
Gfx mirroredSunSongTex[] = {
gsDPLoadTextureBlock("", G_IM_FMT_IA, G_IM_SIZ_8b, 128, 32, 0, G_TX_MIRROR | G_TX_WRAP,
G_TX_NOMIRROR | G_TX_CLAMP, 7, 5, G_TX_NOLOD, G_TX_NOLOD),
};
if (CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0)) {
if (mirroredSunSongVtx == nullptr) {
// Copy the original vertices that we want to modify (4 at the beginning of the resource)
mirroredSunSongVtx = (Vtx*)malloc(sizeof(Vtx) * 4);
Vtx* origVtx = (Vtx*)ResourceGetDataByName(gRoyalGraveBackRoomSongVtx);
memcpy(mirroredSunSongVtx, origVtx, sizeof(Vtx) * 4);
// Offset the vertex U coordinate values by the width of the texture
for (size_t i = 0; i < 4; i++) {
mirroredSunSongVtx[i].v.tc[0] += 128 << 5;
}
}
ResourceMgr_PatchGfxByName(gRoyalGraveBackRoomDL, "RoyalGraveSunSongTexture_1", 13, mirroredSunSongTex[1]);
ResourceMgr_PatchGfxByName(gRoyalGraveBackRoomDL, "RoyalGraveSunSongTexture_2", 17, mirroredSunSongTex[5]);
ResourceMgr_PatchGfxByName(gRoyalGraveBackRoomDL, "RoyalGraveSunSongTextureCords_1", 24,
gsSPVertex(mirroredSunSongVtx, 4, 0));
// noop as the original vertex command is 128 bit wide
ResourceMgr_PatchGfxByName(gRoyalGraveBackRoomDL, "RoyalGraveSunSongTextureCords_2", 25, gsSPNoOp());
} else {
if (mirroredSunSongVtx != nullptr) {
free(mirroredSunSongVtx);
mirroredSunSongVtx = nullptr;
}
ResourceMgr_UnpatchGfxByName(gRoyalGraveBackRoomDL, "RoyalGraveSunSongTexture_1");
ResourceMgr_UnpatchGfxByName(gRoyalGraveBackRoomDL, "RoyalGraveSunSongTexture_2");
ResourceMgr_UnpatchGfxByName(gRoyalGraveBackRoomDL, "RoyalGraveSunSongTextureCords_1");
ResourceMgr_UnpatchGfxByName(gRoyalGraveBackRoomDL, "RoyalGraveSunSongTextureCords_2");
}
}
void ApplyMirrorWorldGfxPatches() {
PatchMirroredSoldOutGI();
PatchMirroredSunSongEtching();
}