bone_sse: fix 3 more bugs from assembly verification (#15-17)

Bug #15: Short-value interpolation read AD+0x0C (nTimestamps count) as
pointer to short array. Should be AD+0x18 (ofsValues). Caused segfault
reading from address ~0x134 (a count value treated as pointer).

Bug #16: boneKeyframeLoop AnimData offsets wrong. Rotation was kf_data+0x10,
should be +0x1C. Scale was kf_data+0x28 with garbled gate, should be +0x38
with gate at +0x44. Translation was correct at +0x00. Entry structure is
3x28-byte AnimBlocks: trans(+0x00), rot(+0x1C), scale(+0x38) = stride 0x54.

Bug #17: particleEmitterLoop (model_hdr+0x124) position AnimData was
entry+0x04, should be entry+0x10 with gate at entry+0x1C. Second track
was entry+0x20, should be entry+0x38/gate +0x44. Third track at
entry+0x60/gate +0x6C was missing entirely.

Also fixed IsParticleBufferEmpty calling convention (bug #14):
was __stdcall with stack param, now __fastcall(ECX=ptr) plain RET.

SSE dispatch enabled for A/B testing.
This commit is contained in:
MarcelineVQ
2026-03-14 23:45:20 -07:00
parent c3a2f1b157
commit cb5fb46012
2 changed files with 44 additions and 31 deletions
+39 -28
View File
@@ -1575,7 +1575,7 @@ fn texAnimLoop(this: u32, model_hdr: u32) void {
const kf_data = alpha_anim + 0x08; // _padding field in AnimationData = keyframe_ranges offset
_ = kf_data;
// getIndexOffset: returns *(data+4) + idx * 2 = pointer to short
const short_base = ru32(alpha_anim + 0x0C); // keyframe_ranges = short array base
const short_base = ru32(alpha_anim + 0x18); // AD.keyframe_base = ofsValues (asm 0x715B33: [EAX+0x18])
const v0 = @as(f32, @floatFromInt(@as(i32, @intCast(@as(i16, @bitCast(ru16(short_base + ru32(alpha_out) * 2)))))));
const v1 = @as(f32, @floatFromInt(@as(i32, @intCast(@as(i16, @bitCast(ru16(short_base + ru32(alpha_out + 4) * 2)))))));
wf32(output + 0xF * 4, (v1 * SHORT_TO_FLOAT - v0 * SHORT_TO_FLOAT) * t + v0 * SHORT_TO_FLOAT);
@@ -1611,7 +1611,7 @@ fn colorAnimLoop(this: u32, model_hdr: u32) void {
wf32(output + 0x0C, sv * SHORT_TO_FLOAT);
} else {
const t = ufloat(ru32(output + 8));
const short_base = ru32(anim_data + 0x0C);
const short_base = ru32(anim_data + 0x18); // AD.keyframe_base (asm: [EDI+0x18] for short values)
const v0 = @as(f32, @floatFromInt(@as(i32, @intCast(@as(i16, @bitCast(ru16(short_base + ru32(output) * 2)))))));
const v1 = @as(f32, @floatFromInt(@as(i32, @intCast(@as(i16, @bitCast(ru16(short_base + ru32(output + 4) * 2)))))));
wf32(output + 0x0C, (v1 * SHORT_TO_FLOAT - v0 * SHORT_TO_FLOAT) * t + v0 * SHORT_TO_FLOAT);
@@ -1645,26 +1645,30 @@ fn boneKeyframeLoop(this: u32, model_hdr: u32) void {
// Init identity matrix for this keyframe entry
setIdentity(mat_out);
// Rotation
const rot_anim = kf_data + 0x10;
if (ru32(rot_anim + AD.keyframe_count) != 0) {
interpAnimKF(this, bone_rt_base, rot_anim, output + 0xC * 4);
// Apply rotation via quaternion
const lm_addr = mat_out;
applyTranslation(lm_addr, 0.5, 0.5, 0.0); // DAT_00cf043c/40/44 = {0.5, 0.5, 0.0}
rotateByQuaternion(lm_addr, ufloat(ru32(output + 0xF * 4)), ufloat(ru32(output + 0x10 * 4)), ufloat(ru32(output + 0x11 * 4)), ufloat(ru32(output + 0x12 * 4)));
applyTranslation(lm_addr, -0.5, -0.5, 0.0);
// Rotation: AnimData at kf_entry+0x1C, gate at kf_entry+0x28
// Assembly at 0x715FDB: CMP [ECX+0x28], 0; AnimData at EDX+0x1C
if (ru32(kf_data + 0x28) != 0) {
interpAnimKF(this, bone_rt_base, kf_data + 0x1C, output + 0x30);
// ApplyTranslation(0.5, 0.5, 0.0), rotateByQuaternion, ApplyTranslation(-0.5, -0.5, 0.0)
applyTranslation(mat_out, 0.5, 0.5, 0.0);
rotateByQuaternion(mat_out, ufloat(ru32(output + 0x3C)), ufloat(ru32(output + 0x40)), ufloat(ru32(output + 0x44)), ufloat(ru32(output + 0x48)));
applyTranslation(mat_out, -0.5, -0.5, 0.0);
}
// Scale
const scale_anim = kf_data + 0x28;
if (ru32(scale_anim + AD.keyframe_count - 0x28 + 0x34) != 0) {
// Translation for bone keyframe
const trans_anim = kf_data;
if (ru32(trans_anim + AD.keyframe_count) != 0) {
interpVec3Track(this, bone_rt_base, trans_anim, output, ufloat(ru32(bone_rt_base + BR.blend_weight)));
applyTranslation(mat_out, ufloat(ru32(output + 0x0C)), ufloat(ru32(output + 0x10)), ufloat(ru32(output + 0x14)));
}
// Scale: AnimData at kf_entry+0x38, gate at kf_entry+0x44
// Assembly at 0x716052: CMP [ECX+0x44], 0; AnimData at EDX+0x38
if (ru32(kf_data + 0x44) != 0) {
interpVec3Track(this, bone_rt_base, kf_data + 0x38, output + 0x68, ufloat(ru32(bone_rt_base + BR.blend_weight)));
applyTranslation(mat_out, 0.5, 0.5, 0.0);
scaleMatrix3x3(mat_out, ufloat(ru32(output + 0x74)), ufloat(ru32(output + 0x78)), ufloat(ru32(output + 0x7C)));
applyTranslation(mat_out, -0.5, -0.5, 0.0);
}
// Translation: AnimData at kf_entry+0x00, gate at kf_entry+0x0C
// Assembly at 0x716216: CMP [ECX+0x0C], 0; AnimData at kf_entry+0x00
if (ru32(kf_data + 0x0C) != 0) {
interpVec3Track(this, bone_rt_base, kf_data, output, ufloat(ru32(bone_rt_base + BR.blend_weight)));
applyTranslation(mat_out, ufloat(ru32(output + 0x0C)), ufloat(ru32(output + 0x10)), ufloat(ru32(output + 0x14)));
}
}
}
@@ -1731,14 +1735,21 @@ fn particleEmitterLoop(this: u32, model_hdr: u32) void {
const bone_idx = @as(u32, ru16(entry + 2));
const bone_rt = bone_rt_base + bone_idx * 0x118;
// Position track (Vec3 with possible spline interpolation)
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x10)) {
interpVec3Track(this, bone_rt, entry + 0x04, output, ufloat(ru32(bone_rt + BR.blend_weight)));
// All 3 tracks from assembly (0x716B00-0x717611):
// Track 1 (position): gate=entry+0x1C, AnimData=entry+0x10, output=+0x00
// Assembly: 0x716B19 CMP [EAX+0x1C]; 0x716B3B LEA ESI,[EDX+0x10]
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x1C)) {
interpVec3Track(this, bone_rt, entry + 0x10, output, ufloat(ru32(bone_rt + BR.blend_weight)));
}
// Color/alpha/speed/emission/scale tracks — all use same interpFloatTrack pattern
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x2C)) {
interpFloatTrack(this, bone_rt, entry + 0x20, output + 0x30);
// Track 2: gate=entry+0x44, AnimData=entry+0x38, output=+0x30
// Assembly: 0x716F44 MOV EDX,[ECX+0x44]; 0x716F55 LEA ECX,[EAX+0x38]
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x44)) {
interpVec3Track(this, bone_rt, entry + 0x38, output + 0x30, ufloat(ru32(bone_rt + BR.blend_weight)));
}
// Track 3: gate=entry+0x6C, AnimData=entry+0x60, output=+0x60
// Assembly: 0x71739A MOV EDX,[ECX+0x6C]; 0x7173AE LEA EDI,[EAX+0x60]
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x6C)) {
interpVec3Track(this, bone_rt, entry + 0x60, output + 0x60, ufloat(ru32(bone_rt + BR.blend_weight)));
}
}
}
@@ -1810,7 +1821,7 @@ fn additionalParticleLoops(this: u32, model_hdr: u32) void {
wf32(output + 0x3C, sv * SHORT_TO_FLOAT);
} else {
const t = ufloat(ru32(output + 0x38));
const short_ranges = ru32(entry + 0x40 + 0x0C);
const short_ranges = ru32(entry + 0x40 + 0x18); // AD.keyframe_base for short values
const v0 = @as(f32, @floatFromInt(@as(i32, @intCast(ri16(short_ranges + ru32(output + 0x30) * 2)))));
const v1 = @as(f32, @floatFromInt(@as(i32, @intCast(ri16(short_ranges + ru32(output + 0x34) * 2)))));
wf32(output + 0x3C, (v1 * SHORT_TO_FLOAT - v0 * SHORT_TO_FLOAT) * t + v0 * SHORT_TO_FLOAT);
+5 -3
View File
@@ -236,9 +236,11 @@ fn transformDetour(this: u32, edx: u32, mat1: u32, mat2: u32, mat3: u32, mat4: u
t44_depth +|= 1;
if (t44_depth > prof.t44_max_depth) prof.t44_max_depth = t44_depth;
// bone_sse disabled while verifying from assembly
_ = transformMatrix4x4_SSE;
transform_hook.callOriginal(.{ this, edx, mat1, mat2, mat3, mat4 });
if (ab_use_custom) {
transformMatrix4x4_SSE(this, mat1, mat2, mat3, mat4);
} else {
transform_hook.callOriginal(.{ this, edx, mat1, mat2, mat3, mat4 });
}
t44_depth -|= 1;
const elapsed = rdtsc() - start;