307 lines
14 KiB
C++
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();
|
|
}
|