Shipwright/soh/soh/Enhancements/cosmetics/authenticGfxPatches.cpp

307 lines
14 KiB
C++

#include <libultraship/bridge.h>
#include <string>
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"
#include "objects/object_ru2/object_ru2.h"
uint32_t ResourceMgr_GameHasMasterQuest();
uint32_t ResourceMgr_GameHasOriginal();
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(gIronKnuckleMetalTex, 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 PatchPrincessRutoEaring() {
// FAST3D: This is a hack for the issue of both TEXEL0 and TEXEL1 using the same texture with different settings.
// Ruto's earring uses both TEXEL0 and TEXEL1 to render. The issue is that it never loads anything into TEXEL1, so
// it reuses whatever happens to be there, which is the water temple brick texture. It just so happens that the
// earring texture loads into the same place in TMEM as the brick texture, so when it comes to rendering, TEXEL1
// uses the earring texture with different clamp settings, and it displays without noticeable error. However, both
// texel samplers are not intended to be used for the same texture with different settings, so this misuse confuses
// our texture cache, and we load the wrong settings for the earrings texture. This patch is a hack that replaces
// TEXEL1 with TEXEL0, which is most likely the original intention, and all is well.
ResourceMgr_PatchGfxByName(gAdultRutoHeadDL, "RutoEaringTileFix", 162,
gsDPSetCombineLERP(TEXEL0, 0, PRIMITIVE, 0, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, COMBINED,
TEXEL0, 0, PRIM_LOD_FRAC, COMBINED));
}
void ApplyAuthenticGfxPatches() {
PatchDekuStickTextureOverflow();
PatchFreezardTextureOverflow();
PatchIronKnuckleTextureOverflow();
PatchPrincessRutoEaring();
}
// 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("gMirroredWorld", 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("gMirroredWorld", 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();
}