From b8e081d6e2d352942ba1ed501f52df543282bba7 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sat, 13 Dec 2025 20:14:03 -0500 Subject: [PATCH] Add level model hotpatch for intro scene to fix gap in widescreen Co-authored-by: Reonu <15913880+Reonu@users.noreply.github.com> --- patches/load_patches.c | 125 +++++++++++++++++++++++++++++ patches/marker_extension_patches.c | 21 ----- patches/misc_funcs.h | 1 + patches/syms.ld | 1 + src/game/recomp_api.cpp | 18 +++++ 5 files changed, 145 insertions(+), 21 deletions(-) create mode 100644 patches/load_patches.c diff --git a/patches/load_patches.c b/patches/load_patches.c new file mode 100644 index 0000000..daf3b23 --- /dev/null +++ b/patches/load_patches.c @@ -0,0 +1,125 @@ +#include "patches.h" +#include "misc_funcs.h" +#include "functions.h" +#include "bk_api.h" +#include "object_extension_funcs.h" + +extern ActorMarker *D_8036E7C8; +extern u8 D_80383428[0x1C]; + +typedef struct { + s16 map_id; //enum map_e + s16 opa_model_id; //enum asset_e level_model_id + s16 xlu_model_id; //enum asset_e level2_model_id + s16 unk6[3]; // min bounds (for cubes?) + s16 unkC[3]; // max bounds (for cubes?) + // u8 pad12[0x2]; + f32 scale; +}MapModelDescription; + +extern struct { + void *unk0; + void *unk4; + BKCollisionList *collision_opa; + BKCollisionList *collision_xlu; + BKModel *model_opa; + BKModel *model_xlu; + BKModelBin *model_bin_opa; + BKModelBin *model_bin_xlu; + s32 unk20; + struct5Bs *unk24; + MapModelDescription *description; + u8 env_red; + u8 env_green; + u8 env_blue; + f32 scale; +}mapModel; + +BKGfxList *model_getDisplayList(BKModelBin *arg0); + + +#define INTRO_OPA_DL_LENGTH 97 +#define INTRO_OPA_DL_GRASS_PATCH_INDEX 63 +#define INTRO_OPA_DL_WALL_PATCH_INDEX 84 +#define INTRO_OPA_DL_HASH 0xA912614C535FA0BBULL + +Vtx intro_grass_extension_verts[3] = { + {{ {-1262, 20, 1040}, 0, {214, 522}, {51, 190, 133, 255} }}, + {{ {-10, 20, 5107}, 0, {3108, -10083}, {0, 13, 169, 255} }}, + {{ {-10, 20, 1372}, 0, {3360, -211}, {51, 190, 133, 255} }}, +}; + +Gfx intro_grass_extension_dl[] = { + // Copy of the replaced command. + gsSP1Triangle(31, 28, 29, 0), + // New commands. + gsSPVertex(intro_grass_extension_verts + 0, 3, 0), + gsSP1Triangle(0, 1, 2, 0), + gsSPEndDisplayList(), +}; + +Vtx intro_wall_extension_verts[4] = { + {{ {-1262, 20, 1040}, 0, {-31, 33}, {51, 190, 133, 255} }}, + {{ {-1262, 1004, 1040}, 0, {-60, 1950}, {255, 255, 255, 255} }}, + {{ {189, 1004, 4315}, 0, {-7416, 1801}, {255, 255, 255, 255} }}, + {{ {189, 20, 4315}, 0, {-7387, -116}, {51, 190, 133, 255} }}, +}; + +Gfx intro_wall_extension_dl[] = { + // Copy of the replaced command. + gsSP1Triangle(22, 24, 21, 0), + // New commands. + gsSPVertex(intro_wall_extension_verts + 0, 4, 0), + gsSP2Triangles(0, 1, 2, 0, 0, 2, 3, 0), + gsSPEndDisplayList(), +}; + +void hotpatch_intro_opa_map_model(BKModelBin* model_bin) { + BKGfxList *gfx_list = model_getDisplayList(model_bin); + Gfx* dl = &gfx_list->list[0]; + + // Hash the displaylist of the model to make sure it's unmodified. This will prevent the hotpatch from + // affecting mods. + u64 hash = recomp_xxh3(dl, INTRO_OPA_DL_LENGTH * sizeof(Gfx)); + if (hash != INTRO_OPA_DL_HASH) { + return; + } + + // Patch a call to the new displaylist after the grass material. + gSPDisplayList(&dl[INTRO_OPA_DL_GRASS_PATCH_INDEX], intro_grass_extension_dl); + + // Patch a call to the new displaylist after the wall material. + gSPDisplayList(&dl[INTRO_OPA_DL_WALL_PATCH_INDEX], intro_wall_extension_dl); +} + +// @recomp Patched to act as a point to run code when a new map is loaded. +// This includes: +// * Resetting all extended marker data and skip interpolation for the next frame. +// * Hotpatching the map model for the title cutscene to fix ultrawide effects. +RECOMP_PATCH void func_803329AC(void){ + s32 i; + + D_8036E7C8 = (ActorMarker *)malloc(0xE0*sizeof(ActorMarker)); + + for( i = 0; i < 0x1C; i++){ + D_80383428[i] = 0; + } + + for(i =0; i<0xE0; i++){ + D_8036E7C8[i].unk5C = 0; + } + + // @recomp Run any new code on map load. + + // @recomp If the current map's model is ASSET_149D_MODEL_CS_START_NINTENDO_OPA, + // hotpatch it to fill in some regions for widescreen. + if (mapModel.description->opa_model_id == ASSET_149D_MODEL_CS_START_NINTENDO_OPA) { + hotpatch_intro_opa_map_model(mapModel.model_bin_opa); + } + + // @recomp Reset all actor data and skip interpolation for the next frame. + // Interpolation is skipped as the next frame will potentially reuse IDs from the previous frame, + // as the marker ID tracking gets reset here. + recomp_clear_all_object_data(EXTENSION_TYPE_MARKER); + set_all_interpolation_skipped(TRUE); +} \ No newline at end of file diff --git a/patches/marker_extension_patches.c b/patches/marker_extension_patches.c index 6327063..8ca6198 100644 --- a/patches/marker_extension_patches.c +++ b/patches/marker_extension_patches.c @@ -15,27 +15,6 @@ extern u8 D_80383428[0x1C]; void func_8032F3D4(s32 arg0[3], ActorMarker *marker, s32 arg2); ActorMarker * func_80332A60(void); -// @recomp Patched to reset all extended marker data and skip interpolation for the next frame. -RECOMP_PATCH void func_803329AC(void){ - s32 i; - - D_8036E7C8 = (ActorMarker *)malloc(0xE0*sizeof(ActorMarker)); - - for( i = 0; i < 0x1C; i++){ - D_80383428[i] = 0; - } - - for(i =0; i<0xE0; i++){ - D_8036E7C8[i].unk5C = 0; - } - - // @recomp Reset all actor data and skip interpolation for the next frame. - // Interpolation is skipped as the next frame will potentially reuse IDs from the previous frame, - // as the marker ID tracking gets reset here. - recomp_clear_all_object_data(EXTENSION_TYPE_MARKER); - set_all_interpolation_skipped(TRUE); -} - // @recomp Patched to create extension data for the marker. RECOMP_PATCH ActorMarker * marker_init(s32 *pos, MarkerDrawFunc draw_func, int arg2, int marker_id, int arg4){ ActorMarker * marker = func_80332A60(); diff --git a/patches/misc_funcs.h b/patches/misc_funcs.h index 1780c2c..ed305df 100644 --- a/patches/misc_funcs.h +++ b/patches/misc_funcs.h @@ -7,5 +7,6 @@ DECLARE_FUNC(void, recomp_load_overlays, u32 rom, void* ram, u32 size); DECLARE_FUNC(void, recomp_puts, const char* data, u32 size); DECLARE_FUNC(void, recomp_exit); DECLARE_FUNC(void, recomp_error, const char* str); +DECLARE_FUNC(u64, recomp_xxh3, void* data, u32 size); #endif diff --git a/patches/syms.ld b/patches/syms.ld index d0d116e..cee7e96 100644 --- a/patches/syms.ld +++ b/patches/syms.ld @@ -36,3 +36,4 @@ osPiStartDma_recomp = 0x8F00007C; recomp_abort = 0x8F000080; recomp_get_target_aspect_ratio = 0x8F000084; osExQueueDisplaylistEvent_recomp = 0x8F000088; +recomp_xxh3 = 0x8F00008C; diff --git a/src/game/recomp_api.cpp b/src/game/recomp_api.cpp index 04f6343..edbf651 100644 --- a/src/game/recomp_api.cpp +++ b/src/game/recomp_api.cpp @@ -14,6 +14,7 @@ #include "../patches/sound.h" #include "ultramodern/ultramodern.hpp" #include "ultramodern/config.hpp" +#include "../lib/N64ModernRuntime/thirdparty/xxHash/xxh3.h" extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) { recomp::poll_inputs(); @@ -219,3 +220,20 @@ extern "C" void recomp_abort(uint8_t* rdram, recomp_context* ctx) { assert(false); ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__); } + +extern "C" void recomp_xxh3(uint8_t* rdram, recomp_context* ctx) { + PTR(void) data = _arg<0, PTR(void)>(rdram, ctx); + u32 size = _arg<1, u32>(rdram, ctx); + XXH3_state_t xxh3; + XXH3_64bits_reset(&xxh3); + + // Hash 1 byte at a time to account for byteswapping. + for (size_t i = 0; i < size; i++) { + XXH3_64bits_update(&xxh3, TO_PTR(u8, data + i), 1); + } + + uint64_t ret = XXH3_64bits_digest(&xxh3); + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +}