D158: KF7 muzzle-flash stackpad2[-8] negative stack write -> crash on fire

gunUpdateAndFire's KF7 branch (`mdlhdr->Skeleton == &skeleton_gun_kf7`)
does `((f32*)stackpad2)[-8] = ...` then reads it back into
matrix_scalar_multiply -- a decomp register-allocation stackpad: a
scratch f32 the N64 compiler placed 32 bytes below the `u8 stackpad2[4]`
local. GCC's x86-64 stack layout is different, so the negative index
scribbles into an unrelated stack slot (return-address / saved-reg area)
-> 0xc000001d illegal-instruction the moment you fire a KF7.

Found via scripted-input (walk+fire) testing: 12 campaign levels survived
a firefight script, Caverns (-level_39) crashed reproducibly on a settled
machine at gunfire.c:817. addr2line -> gunUpdateAndFire. The KF7 is a
common campaign weapon (Facility/Surface/Caverns guards) so this would
hit the user early.

Fix: real `f32 kf7_flashscale` local under #ifdef PORT; N64 stackpad
expression kept under #else. Caverns now survives repeated fire to frame
1500+; -level_09 unregressed. PORT-LEARNINGS §A (stackpad hazard).
This commit is contained in:
James Dansereau committed 2026-08-31 11:59:17 -04:00
1 parent 3e5720c01b
commit d439dfeaeb
1 file changed
+18
+18
View File
@@ -368,6 +368,16 @@ void gunUpdateAndFire(GUNHAND handnum)
s32 sw7mtxidx;
s32 shellidx;
u8 stackpad2[4];
#ifdef PORT
/* D157-adjacent: `((f32 *)stackpad2)[-8]` (lines below) is a decomp
* register-allocation stackpad hack — a scratch f32 the N64 compiler
* placed 32 bytes *below* stackpad2. GCC's x86-64 stack layout is
* different, so that negative index writes into an unrelated slot
* (return-address / saved-reg area) → 0xc000001d illegal-instruction on
* the KF7-muzzle-flash path (found firing on Caverns, M-30). Use a real
* local; N64 keeps the stackpad expression under #else. */
f32 kf7_flashscale;
#endif
flashvisptr = NULL;
flashdata = NULL;
@@ -814,9 +824,17 @@ void gunUpdateAndFire(GUNHAND handnum)
flashpos.x = (((((f32 *) nodepos)[0] * flashmtx.m[0][0]) + (((f32 *) nodepos)[1] * flashmtx.m[1][0])) + (((f32 *) nodepos)[2] * flashmtx.m[2][0])) + flashmtx.m[3][0];
flashpos.y = (((((f32 *) nodepos)[0] * flashmtx.m[0][1]) + (((f32 *) nodepos)[1] * flashmtx.m[1][1])) + (((f32 *) nodepos)[2] * flashmtx.m[2][1])) + flashmtx.m[3][1];
flashpos.z = (((((f32 *) nodepos)[0] * flashmtx.m[0][2]) + (((f32 *) nodepos)[1] * flashmtx.m[1][2])) + (((f32 *) nodepos)[2] * flashmtx.m[2][2])) + flashmtx.m[3][2];
#ifdef PORT
kf7_flashscale = IDO_POINT_ONE * flashscale;
#else
((f32 *) stackpad2)[-8] = IDO_POINT_ONE * flashscale;
#endif
matrix_4x4_align(&flash2mtx, (randomGetNext() * (1.0f / M_U32_MAX_VALUE_F)) * M_TAU_F, -flashpos.x, -flashpos.y, -flashpos.z);
#ifdef PORT
matrix_scalar_multiply(kf7_flashscale, flash2mtx.m[0]);
#else
matrix_scalar_multiply(((f32 *) stackpad2)[-8], flash2mtx.m[0]);
#endif
matrix_4x4_set_rotation_axis_angle(&aimmtx, 0, gunofs.x - hand->field_A38, gunofs.y - hand->field_A3C, gunofs.z - hand->field_A40);
matrix_4x4_multiply_in_place(&aimmtx, &flash2mtx);
matrix_row_3_scalar_multiply(flashext, flash2mtx.m[0]);