From 3dfa559eac057bb240c51b6d0f98e798c729761d Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Tue, 17 Mar 2026 03:20:59 -0700 Subject: [PATCH] silicon: direct byte patch for ftol, JMP patches for all others, removed compare mode --- src/silicon/silicon.zig | 95 ++++++++----------------------------- src/silicon/silicon_sse.zig | 2 +- 2 files changed, 21 insertions(+), 76 deletions(-) diff --git a/src/silicon/silicon.zig b/src/silicon/silicon.zig index a4ffd4e..d5565bd 100644 --- a/src/silicon/silicon.zig +++ b/src/silicon/silicon.zig @@ -1895,69 +1895,7 @@ fn ftolProbe() callconv(.naked) void { ); } -/// SSE2 __ftol compare mode. Calls original, computes SSE2 result, logs mismatches. -fn ftolSSE2() callconv(.naked) void { - asm volatile ( - // Count calls - \\mov %[cnt], %%eax - \\lock addl $1, (%%eax) - // Duplicate ST(0) for both paths - \\sub $16, %%esp - \\fld %%st(0) - // Call original via trampoline (consumes one ST(0) copy, result in EAX:EDX) - \\mov %[orig], %%eax - \\mov (%%eax), %%eax - \\call *%%eax - // EAX = original result. Save it. - \\mov %%eax, 8(%%esp) - \\mov %%edx, 12(%%esp) - // Now compute SSE2 result from saved copy - \\fstpl (%%esp) - \\movsd (%%esp), %%xmm0 - \\cvttsd2si %%xmm0, %%ecx - // Compare - \\cmp %%ecx, 8(%%esp) - \\jne 1f - // Match: return original result - \\mov 8(%%esp), %%eax - \\mov 12(%%esp), %%edx - \\add $16, %%esp - \\ret - // Mismatch: log it, return original - \\1: - \\mov %[miss], %%eax - \\lock addl $1, (%%eax) - // Push args for logFtolMismatch(original_i32, sse2_i32, f64_lo, f64_hi) - \\push 4(%%esp) - \\push 4(%%esp) - \\push %%ecx - \\push 20(%%esp) - \\call %[logfn] - \\add $16, %%esp - // Return original result - \\mov 8(%%esp), %%eax - \\mov 12(%%esp), %%edx - \\add $16, %%esp - \\ret - : - : [orig] "i" (&h115.inner.trampoline), - [miss] "i" (&ftol_mismatch_count), - [cnt] "i" (&cnt[115]), - [logfn] "i" (&logFtolMismatch), - ); -} - -fn logFtolMismatch(original: i32, sse2: i32, f64_lo: u32, f64_hi: u32) callconv(.c) void { - const S = struct { var count: u32 = 0; }; - if (S.count < 50) { - S.count += 1; - const bits: u64 = @as(u64, f64_hi) << 32 | f64_lo; - const val: f64 = @bitCast(bits); - log_state.fmt("[ftol_cmp] orig={d} sse2={d} val={d}\n", .{ original, sse2, @as(i64, @intFromFloat(val)) }); - } -} - -var ftol_mismatch_count: u32 = 0; +// ftolSSE2 compare mode removed — using direct byte patching via si_ftol now const ProbeInfo = struct { name: [*:0]const u8, idx: u32 }; @@ -2152,7 +2090,12 @@ const sse = struct { extern fn si_translateBoundingVol(u32, u32) callconv(TC) void; }; -const PatchEntry = struct { target: u32, replacement: u32, name: [*:0]const u8 }; +const PatchEntry = struct { + target: u32, + replacement: u32, + name: [*:0]const u8, + direct_size: u8 = 0, // >0: copy this many bytes directly (naked asm, no JMP) +}; fn getPatchTable() []const PatchEntry { const table = [_]PatchEntry{ @@ -2176,7 +2119,7 @@ fn getPatchTable() []const PatchEntry { .{ .target = 0x71BF60, .replacement = @intFromPtr(&sse.si_addToColorAccumulator), .name = "addToColorAccumulator" }, .{ .target = 0x7B7A80, .replacement = @intFromPtr(&sse.si_packParticleColor), .name = "packParticleColor" }, .{ .target = 0x7B7B10, .replacement = @intFromPtr(&sse.si_setParticleAlpha), .name = "setParticleAlpha" }, - .{ .target = 0x40A2B0, .replacement = @intFromPtr(&sse.si_ftol), .name = "__ftol" }, + .{ .target = 0x40A2B0, .replacement = @intFromPtr(&sse.si_ftol), .name = "__ftol", .direct_size = 9 }, .{ .target = 0x602630, .replacement = @intFromPtr(&sse.si_vec3Dot), .name = "vec3Dot" }, .{ .target = 0x686820, .replacement = @intFromPtr(&sse.si_translateBoundingVol), .name = "translateBoundingVol" }, }; @@ -2186,11 +2129,17 @@ fn getPatchTable() []const PatchEntry { fn installPatches() u32 { var count: u32 = 0; for (getPatchTable()) |entry| { - // Write JMP rel32: E9 XX XX XX XX - const rel = @as(i32, @bitCast(entry.replacement -% entry.target -% 5)); - var patch = [5]u8{ 0xE9, 0, 0, 0, 0 }; - @as(*align(1) i32, @ptrCast(patch[1..5])).* = rel; - hook.writeProtected(entry.target, &patch); + if (entry.direct_size > 0) { + // Direct byte copy: naked asm replacement fits in original + const src: [*]const u8 = @ptrFromInt(entry.replacement); + hook.writeProtected(entry.target, src[0..entry.direct_size]); + } else { + // JMP rel32: E9 XX XX XX XX + const rel = @as(i32, @bitCast(entry.replacement -% entry.target -% 5)); + var patch = [5]u8{ 0xE9, 0, 0, 0, 0 }; + @as(*align(1) i32, @ptrCast(patch[1..5])).* = rel; + hook.writeProtected(entry.target, &patch); + } count += 1; } return count; @@ -2335,7 +2284,7 @@ pub fn lateInit() void { // the control word state after the original returns. Needs naked asm like ftol. // if (h114.attach(0x40CF81, probeDetour(SC2r, &h114, &cnt[114])) == .ok) installed += 1; } - if (h115.attach(0x40A2B0, @ptrCast(&ftolSSE2)) == .ok) installed += 1; + // ftol patched directly via installPatches(), no detour needed // Periodic reporting hook if (world_update_hook.attach(0x482EA0, &worldUpdateDetour) == .ok) { @@ -2355,10 +2304,6 @@ fn reportProbes() void { log_state.fmt(" {s}: {d}\n", .{ info.name, c }); } } - const miss = @atomicRmw(u32, &ftol_mismatch_count, .Xchg, 0, .monotonic); - // cnt[115] already read+reset by the probe loop above, so use the value from probe_infos - // Just report the mismatch count — the call count is in the probe list as "__ftol" - log_state.fmt(" __ftol SSE2 mismatches: {d}\n", .{miss}); } pub fn removeHooks() void { diff --git a/src/silicon/silicon_sse.zig b/src/silicon/silicon_sse.zig index b998c3f..ba0d6b8 100644 --- a/src/silicon/silicon_sse.zig +++ b/src/silicon/silicon_sse.zig @@ -443,7 +443,7 @@ export fn si_setParticleAlpha(obj: u32, alpha_bits: u32) callconv(FC) void { export fn si_ftol() callconv(.naked) void { asm volatile ( \\sub $8, %%esp - \\.byte 0xDD, 0x0C, 0x24 + \\fisttpll (%%esp) \\pop %%eax \\pop %%edx \\ret