From d2177db3a598be3235e27fe0dc0f42816d452457 Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:38:11 -0400 Subject: [PATCH] D92: two truncated pointers on the chr/AI spawn path (a) chrAllocate's 5th param was declared s32 but callers pass ailistFindById()'s AIRecord* -- the 64-bit pointer was truncated before reaching init_GUARDdata_with_set_values, so chr->ailist held a bogus address and ai() faulted dereferencing (AiListp + Offset)->cmd on the first AI tick. Param widened to AIListRecord* under PORT. (b) Model.unka0 is a 32-bit field that on N64 holds a function pointer (always sub_GAME_7F01FC10). chr.c stored (s32)sub_GAME_7F01FC10 into it and model.c subcalcpos called it back -- truncated jump target on PC. Widening the field would shift the rest of Model, so under PORT the setter stores a flag and subcalcpos calls sub_GAME_7F01FC10 directly. BUNKER1 now loads and ticks through to the first render, where it hits D85 (room GDL/texture decode: 'Bad size for RGBA texture'). --- src/game/chr.c | 9 +++++++++ src/game/chr.h | 6 ++++++ src/game/model.c | 12 ++++++++++++ 3 files changed, 27 insertions(+) diff --git a/src/game/chr.c b/src/game/chr.c index ed981fc9..09dd0a08 100644 --- a/src/game/chr.c +++ b/src/game/chr.c @@ -1615,7 +1615,11 @@ PropRecord *init_GUARDdata_with_set_values(PropRecord *arg0, Model *arg1, struct arg0->pos.f[2] = arg2->f[2]; arg0->stan = arg4; +#ifdef PORT + sub_GAME_7F06FF5C(arg1, 1); /* D92: flag only; model.c subcalcpos calls sub_GAME_7F01FC10 directly */ +#else sub_GAME_7F06FF5C(arg1, (s32) sub_GAME_7F01FC10); +#endif arg1->unk00 = 0xA; arg1->chr = var_s0; @@ -1726,7 +1730,12 @@ PropRecord *init_GUARDdata_with_set_values(PropRecord *arg0, Model *arg1, struct /** * Address 0x7F0203B8. */ +#ifdef PORT +/* D92: see chr.h -- arg4 must stay a pointer or the ailist is truncated. */ +PropRecord * chrAllocate( Model * arg0, coord3d * arg1, f32 arg2, StandTile * arg3, struct AIListRecord *arg4) +#else PropRecord * chrAllocate( Model * arg0, coord3d * arg1, f32 arg2, StandTile * arg3, s32 arg4) +#endif { PropRecord * ret; s32 phi_a0; diff --git a/src/game/chr.h b/src/game/chr.h index 7cbb50b4..03abfe61 100644 --- a/src/game/chr.h +++ b/src/game/chr.h @@ -268,7 +268,13 @@ void chrSetMoving(ChrRecord *guard,s32 param_2); f32 getAnimationRate(void); void setAnimationRate(f32); PropRecord *init_GUARDdata_with_set_values(PropRecord *, Model *, coord3d *, f32 arg2, StandTile * arg3, struct AIListRecord *arg4); +#ifdef PORT +/* D92: arg4 is an AIListRecord* (ailistFindById's return); declaring it s32 + * truncates the 64-bit pointer on PC. */ +PropRecord *chrAllocate(struct Model * arg0, coord3d * arg1, f32 arg2, StandTile * arg3, struct AIListRecord *arg4); +#else PropRecord *chrAllocate(struct Model * arg0, coord3d * arg1, f32 arg2, StandTile * arg3, s32 arg4); +#endif void chrSetHiddenToRandom(ChrRecord *arg0); void chrRemoved7F022E1C(f32 arg0); void chrDecrementAnimationTablePointerCount(void); diff --git a/src/game/model.c b/src/game/model.c index 111968be..c7511cc8 100644 --- a/src/game/model.c +++ b/src/game/model.c @@ -1187,10 +1187,22 @@ void sub_GAME_7F06D490(Model *model, ModelNode *modelNode) sp2c.y = sp38.y; sp2c.z = sp38.z; +#ifdef PORT + /* D92: `unka0` is a 32-bit Model field that on N64 holds a function + * pointer -- always sub_GAME_7F01FC10 (the only value chr.c ever hands + * the setter). Widening the field would shift the rest of Model, so on + * PC the setter stores a nonzero flag and the single indirect call is + * made directly here. */ + if (model->unka0 && !sub_GAME_7F01FC10(model, &rw->Header.pos, &sp2c, &rw->Header.ground)) + { + return; + } +#else if (model->unka0 && !((s32 (*)(Model *, coord3d *, coord3d *, f32 *)) model->unka0)(model, &rw->Header.pos, &sp2c, &rw->Header.ground)) { return; } +#endif sp38.x = sp2c.x - sp38.x; sp38.z = sp2c.z - sp38.z;