ssemaths: extract UnitXP math polyfill hooks into standalone module

Moved math_sse.zig and all 17 hook declarations + CriticalSection spin
count optimization from transform44 into new ssemaths module. Off by
default (-Dssemaths=true to enable).

transform44 retains its profiling hooks and blit_hub optimization.
ssemaths is a clean standalone module with its own mutex, install/remove
lifecycle, and lateInit for post-UnitXP hook clobbering.

Added performance note to math_sse.zig documenting that hook-based
replacement adds ~5-8 cycles overhead that makes small functions slower,
and that in-place patching is the path to realize the full 2-4x gains
shown in inlined benchmarks.
This commit is contained in:
MarcelineVQ
2026-03-14 22:28:31 -07:00
parent b9603c75f9
commit 3c9a95d51e
5 changed files with 278 additions and 219 deletions
+3 -2
View File
@@ -31,6 +31,7 @@ const module_list = [_]ModuleDesc{
.{ .name = "transform44", .desc = "Enable transformMatrix4x4 hook", .default = false },
.{ .name = "addonperf", .desc = "Enable addon memory/CPU profiling API" },
.{ .name = "filecache", .desc = "Enable MPQ archive file cache", .default = false },
.{ .name = "ssemaths", .desc = "Enable UnitXP x87 math polyfill replacements (SSE)", .default = false },
.{ .name = "silicon", .desc = "Enable SSE2 math replacements (ported from libSiliconPatch)", .default = false },
};
@@ -73,7 +74,7 @@ pub fn build(b: *std.Build) void {
const math_sse_obj = b.addObject(.{
.name = "math_sse",
.root_module = b.createModule(.{
.root_source_file = b.path("src/transform44/math_sse.zig"),
.root_source_file = b.path("src/ssemaths/math_sse.zig"),
.target = target,
.optimize = .ReleaseFast,
}),
@@ -107,7 +108,7 @@ pub fn build(b: *std.Build) void {
const bench_math_sse = b.addObject(.{
.name = "bench_math_sse",
.root_module = b.createModule(.{
.root_source_file = b.path("src/transform44/math_sse.zig"),
.root_source_file = b.path("src/ssemaths/math_sse.zig"),
.target = bench_target,
.optimize = .ReleaseFast,
}),
+7
View File
@@ -21,6 +21,8 @@ const build_opts = struct {
const transform44 = @import("build_options").enable_transform44;
const addonperf = @import("build_options").enable_addonperf;
const filecache = @import("build_options").enable_filecache;
const ssemaths = @import("build_options").enable_ssemaths;
const silicon = @import("build_options").enable_silicon;
};
// Conditional module imports
@@ -39,6 +41,7 @@ const clickthrough = if (build_opts.clickthrough) @import("clickthrough/clickthr
const dpslog = if (build_opts.dpslog) @import("dpslog/dpslog.zig") else struct {};
const transform44 = if (build_opts.transform44) @import("transform44/transform44.zig") else struct {};
const addonperf = if (build_opts.addonperf) @import("addonperf/addonperf.zig") else struct {};
const ssemaths = if (build_opts.ssemaths) @import("ssemaths/ssemaths.zig") else struct {};
const file_cache = if (build_opts.filecache) @import("filecache/filecache.zig") else struct {};
const module_active = @import("module_active.zig");
@@ -685,6 +688,9 @@ fn engineInitDetour() callconv(hook.cc.stdcall) void {
if (build_opts.transform44) {
transform44.lateInit();
}
if (build_opts.ssemaths) {
ssemaths.lateInit();
}
}
// =============================================================================
@@ -753,6 +759,7 @@ const modules = [_]ModuleHooks{
if (build_opts.dpslog) .{ .name = dpslog.module_name, .install = dpslog.installHooks, .remove = dpslog.removeHooks, .is_active = dpslog.isActive } else .{},
if (build_opts.transform44) .{ .name = transform44.module_name, .install = transform44.installHooks, .remove = transform44.removeHooks, .is_active = transform44.isActive } else .{},
if (build_opts.addonperf) .{ .name = addonperf.module_name, .install = addonperf.installHooks, .remove = addonperf.removeHooks, .is_active = addonperf.isActive } else .{},
if (build_opts.ssemaths) .{ .name = ssemaths.module_name, .install = ssemaths.installHooks, .remove = ssemaths.removeHooks, .is_active = ssemaths.isActive } else .{},
if (build_opts.filecache) .{ .name = file_cache.module_name, .install = file_cache.installHooks, .remove = file_cache.removeHooks, .is_active = file_cache.isActive } else .{},
if (build_opts.worldmarkers) .{ .name = markers.module_name, .install = markers.installHooks, .remove = markers.removeHooks, .is_active = markers.isActive } else .{},
if (build_opts.interact) .{ .name = interact.module_name, .install = interact.installHooks, .remove = interact.removeHooks, .is_active = interact.isActive } else .{},
@@ -9,6 +9,14 @@
//! Silicon: /tmp/TurtleSilicon/winerosetta/libSiliconPatch.dll (closed source, symbols only)
//!
//! Our approach: @Vector(4, f32) SSE intrinsics where beneficial, scalar for simple ops.
//!
//! PERFORMANCE NOTE: Currently installed via hook detours (CALL/RET overhead ~5-8 cycles).
//! Benchmarks show small functions (dotProduct, squaredMagnitude, evaluatePolynomial) are
//! SLOWER than the original x87 when called, but 2-4x FASTER when inlined. The x87
//! originals are 21-34 bytes with no prologue — the call overhead alone exceeds the
//! function body cost. To realize actual gains on these, we need to directly patch the
//! SSE instruction bytes over the original x87 bytes at load time (no function call).
//! See src/bench/ for the full comparison.
const V4 = @Vector(4, f32);
+260
View File
@@ -0,0 +1,260 @@
//! ssemaths — UnitXP x87 FPU math polyfill replacements
//!
//! Replaces 17 game math functions with SSE equivalents, plus CriticalSection
//! spin count optimization and blit memcpy fast paths. All from UnitXP's
//! polyfill.cpp, re-verified via Ghidra prologue/epilogue disassembly.
//!
//! Installed in lateInit() after UnitXP has hooked these functions, so we
//! clobber UnitXP's JMP prologues with the original bytes and then hook
//! ourselves. This gives us a clean trampoline to the true original code
//! for A/B testing.
//!
//! PERFORMANCE NOTE: Hook-based replacement adds ~5-8 cycles of CALL/RET
//! overhead per invocation. Large functions (rotMat, planeNormal, transformAABox)
//! see net gains (1.2-2.1x), but small functions (dotProduct, squaredMagnitude,
//! evaluatePolynomial) are slower than the originals due to call overhead alone.
//! Inlined benchmarks show ALL functions are faster with SSE (2-4x for small
//! functions). Future direction: patch SSE bytes in-place at load time.
const std = @import("std");
const hook = @import("zhook");
const logging = @import("../logging.zig");
const mod_mutex = @import("../mutex.zig");
pub const module_name: [*:0]const u8 = "ssemaths";
var g_mutex: ?*anyopaque = null;
var g_is_hook_owner: bool = false;
var log: logging.Logger = .{};
pub fn isActive() bool {
return g_is_hook_owner;
}
// math_sse.zig exports (C ABI, compiled ReleaseFast as separate object)
extern fn vecMulMat4_ColMajor(u32, u32, u32) u32;
extern fn matMulVec3_RowMajor(u32, u32, u32) u32;
extern fn quatMulMat4(u32, u32, u32) u32;
extern fn vec3MulScalar(u32, u32, u32) u32;
extern fn vec3MulAssign(u32, u32) u32;
extern fn applyTranslationMatrix(u32, u32) u32;
extern fn scaleMatrix3x3ByVector(u32, u32) u32;
extern fn scaleMatrix3x3ByScalar(u32, u32) void;
extern fn multiply3x3Matrix(u32, u32, u32) u32;
extern fn createAxisAngleRotMat3x3(u32, u32, u32, u32) u32;
extern fn createAxisAngleRotMat4x4(u32, u32, u32, u32) u32;
extern fn crossProduct(u32, u32, u32) u32;
extern fn dotProduct(u32, u32) f64;
extern fn squaredMagnitude(u32) f64;
extern fn evaluatePolynomial(u32, u32, u32) f64;
extern fn calculatePlaneNormal(u32, u32, u32, u32) void;
extern fn transformAABox(u32, u32, u32, u32, u32) void;
// =============================================================================
// Calling convention types (Ghidra-verified from prologue/epilogue disassembly)
// =============================================================================
// Fastcall fn types (ECX + EDX + stack)
const FC3r = fn (u32, u32, u32) callconv(hook.cc.fastcall) u32;
const FC4r = fn (u32, u32, u32, u32) callconv(hook.cc.fastcall) u32;
const FC5v = fn (u32, u32, u32, u32, u32) callconv(hook.cc.fastcall) void;
const FC2d = fn (u32, u32) callconv(hook.cc.fastcall) f64;
const FC3d = fn (u32, u32, u32) callconv(hook.cc.fastcall) f64;
// Thiscall fn types (ECX + stack)
const TC2r = fn (u32, u32) callconv(hook.cc.thiscall) u32;
const TC2v = fn (u32, u32) callconv(hook.cc.thiscall) void;
const TC1d = fn (u32) callconv(hook.cc.thiscall) f64;
const TC4v = fn (u32, u32, u32, u32) callconv(hook.cc.thiscall) void;
// =============================================================================
// Math hook variables
//
// 1: vecMulMat4 __fastcall(ECX=result, EDX=vec, stack:mat) RET 0x4
// 2: matMulVec3 __fastcall(ECX=result, EDX=mat, stack:vec) RET 0x4
// 3: quatMulMat4 __fastcall(ECX=result, EDX=quat, stack:mat) RET 0x4
// 4: vec3MulScalar __fastcall(ECX=result, EDX=vec, stack:factor) RET 0x4
// 5: vec3MulAssign __thiscall(ECX=self, stack:factor) RET 0x4
// 6: applyTranslation __thiscall(ECX=mat, stack:vec) RET 0x4
// 7: scaleByVec __thiscall(ECX=mat, stack:vec) RET 0x4
// 8: scaleByScalar __thiscall(ECX=mat, stack:factor) RET 0x4
// 9: mul3x3 __fastcall(ECX=result, EDX=matA, stack:matB) RET 0x4
// 10: rotMat3x3 __fastcall(ECX=result, EDX=axis, stack:angle,is_unit) RET 0x8
// 11: rotMat4x4 __fastcall(ECX=result, EDX=axis, stack:angle,is_unit) RET 0x8
// 12: cross __fastcall(ECX=result, EDX=vecA, stack:vecB) RET 0x4
// 13: dot __fastcall(ECX=vecA, EDX=vecB) RET
// 14: sqmag __thiscall(ECX=vec) RET
// 16: evalPoly __fastcall(ECX=count, EDX=coeffs, stack:factor) RET 0x4
// 17: planeNormal __thiscall(ECX=result, stack:p1,p2,p3) RET 0xC
// 18: transformAABox __fastcall(ECX=mat, EDX=vecA, stack:vecB,boxIn,boxOut) RET 0xC
// =============================================================================
var math_vecMulMat4_hook: hook.Detour(FC3r) = .{};
var math_matMulVec3_hook: hook.Detour(FC3r) = .{};
var math_quatMulMat4_hook: hook.Detour(FC3r) = .{};
var math_vec3MulScalar_hook: hook.Detour(FC3r) = .{};
var math_vec3MulAssign_hook: hook.Detour(TC2r) = .{};
var math_applyTranslation_hook: hook.Detour(TC2r) = .{};
var math_scaleByVec_hook: hook.Detour(TC2r) = .{};
var math_scaleByScalar_hook: hook.Detour(TC2v) = .{};
var math_mul3x3_hook: hook.Detour(FC3r) = .{};
var math_rotMat3x3_hook: hook.Detour(FC4r) = .{};
var math_rotMat4x4_hook: hook.Detour(FC4r) = .{};
var math_cross_hook: hook.Detour(FC3r) = .{};
var math_dot_hook: hook.Detour(FC2d) = .{};
var math_sqmag_hook: hook.Detour(TC1d) = .{};
var math_evalPoly_hook: hook.Detour(FC3d) = .{};
var math_planeNormal_hook: hook.Detour(TC4v) = .{};
var math_transformAABox_hook: hook.Detour(FC5v) = .{};
// Detour wrappers — direct dispatch (no A/B, always SSE)
fn wrapFC3r(comptime f: anytype, comptime h: anytype) *const FC3r {
return &struct { fn w(a: u32, b: u32, c: u32) callconv(hook.cc.fastcall) u32 { _ = h; return f(a, b, c); } }.w;
}
fn wrapFC4r(comptime f: anytype, comptime h: anytype) *const FC4r {
return &struct { fn w(a: u32, b: u32, c: u32, d: u32) callconv(hook.cc.fastcall) u32 { _ = h; return f(a, b, c, d); } }.w;
}
fn wrapFC5v(comptime f: anytype, comptime h: anytype) *const FC5v {
return &struct { fn w(a: u32, b: u32, c: u32, d: u32, e: u32) callconv(hook.cc.fastcall) void { _ = h; f(a, b, c, d, e); } }.w;
}
fn wrapFC2d(comptime f: anytype, comptime h: anytype) *const FC2d {
return &struct { fn w(a: u32, b: u32) callconv(hook.cc.fastcall) f64 { _ = h; return f(a, b); } }.w;
}
fn wrapFC3d(comptime f: anytype, comptime h: anytype) *const FC3d {
return &struct { fn w(a: u32, b: u32, c: u32) callconv(hook.cc.fastcall) f64 { _ = h; return f(a, b, c); } }.w;
}
fn wrapTC2r(comptime f: anytype, comptime h: anytype) *const TC2r {
return &struct { fn w(a: u32, b: u32) callconv(hook.cc.thiscall) u32 { _ = h; return f(a, b); } }.w;
}
fn wrapTC2v(comptime f: anytype, comptime h: anytype) *const TC2v {
return &struct { fn w(a: u32, b: u32) callconv(hook.cc.thiscall) void { _ = h; f(a, b); } }.w;
}
fn wrapTC1d(comptime f: anytype, comptime h: anytype) *const TC1d {
return &struct { fn w(a: u32) callconv(hook.cc.thiscall) f64 { _ = h; return f(a); } }.w;
}
fn wrapTC4v(comptime f: anytype, comptime h: anytype) *const TC4v {
return &struct { fn w(a: u32, b: u32, c: u32, d: u32) callconv(hook.cc.thiscall) void { _ = h; f(a, b, c, d); } }.w;
}
// =============================================================================
// CriticalSection spin count optimization (from UnitXP polyfill.cpp:474)
// =============================================================================
const WINAPI = std.builtin.CallingConvention.winapi;
const CritSecFn = fn (u32) callconv(WINAPI) void;
var critsec_hook: hook.Detour(CritSecFn) = .{};
extern "kernel32" fn GetModuleHandleA(name: [*:0]const u8) callconv(WINAPI) ?*anyopaque;
extern "kernel32" fn GetProcAddress(module: *anyopaque, name: [*:0]const u8) callconv(WINAPI) ?*anyopaque;
extern "kernel32" fn SetCriticalSectionSpinCount(cs: u32, spin: u32) callconv(WINAPI) u32;
fn critSecDetour(cs_ptr: u32) callconv(WINAPI) void {
if (cs_ptr != 0 and (cs_ptr & 1) == 0) {
const spin_count = hook.readMem(u32, cs_ptr + 0x18);
if (spin_count == 0) {
_ = SetCriticalSectionSpinCount(cs_ptr, 4000);
}
}
critsec_hook.callOriginal(.{cs_ptr});
}
// =============================================================================
// Install / remove
// =============================================================================
pub fn installHooks() void {
const result = mod_mutex.acquire(module_name);
g_mutex = result.handle;
g_is_hook_owner = result.is_owner;
if (!g_is_hook_owner) return;
log = logging.Logger.open(module_name, .both);
// CriticalSection spin count optimization
if (GetModuleHandleA("kernel32")) |k32| {
if (GetProcAddress(k32, "EnterCriticalSection")) |ecs_addr| {
_ = critsec_hook.attach(@intFromPtr(ecs_addr), &critSecDetour);
log.print("critsec: SpinCount=4000 hook installed\n");
}
}
log.print("ssemaths: installHooks done (math hooks deferred to lateInit)\n");
}
/// Called after UnitXP has installed its hooks.
/// Restores original prologues (clobbering UnitXP's JMP), then hooks ourselves.
pub fn lateInit() void {
if (!g_is_hook_owner) return;
const MathHook = struct { addr: u32, prologue: []const u8 };
const math_hooks = [_]MathHook{
.{ .addr = 0x7BCA80, .prologue = &.{ 0x55, 0x8b, 0xec, 0x56, 0x8b, 0x75, 0x08 } }, // 1: vecMulMat4
.{ .addr = 0x7BCAE0, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x42, 0x28 } }, // 2: matMulVec3
.{ .addr = 0x7BCB40, .prologue = &.{ 0x55, 0x8b, 0xec, 0x56, 0x8b, 0x75, 0x08 } }, // 3: quatMulMat4
.{ .addr = 0x5F8CF0, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x45, 0x08 } }, // 4: vec3MulScalar
.{ .addr = 0x5132F0, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x45, 0x08 } }, // 5: vec3MulAssign
.{ .addr = 0x7BDC40, .prologue = &.{ 0x55, 0x8b, 0xec, 0x8b, 0x45, 0x08 } }, // 6: applyTranslation
.{ .addr = 0x7BDCA0, .prologue = &.{ 0x55, 0x8b, 0xec, 0x8b, 0x45, 0x08 } }, // 7: scaleByVec
.{ .addr = 0x7BDD00, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x45, 0x08 } }, // 8: scaleByScalar
.{ .addr = 0x7BDFC0, .prologue = &.{ 0x55, 0x8b, 0xec, 0x51, 0xd9, 0x42, 0x1c } }, // 9: mul3x3
.{ .addr = 0x7BE490, .prologue = &.{ 0x55, 0x8b, 0xec, 0x83, 0xec, 0x18 } }, // 10: rotMat3x3
.{ .addr = 0x7BDB00, .prologue = &.{ 0x55, 0x8b, 0xec, 0x83, 0xec, 0x18 } }, // 11: rotMat4x4
.{ .addr = 0x672130, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x02 } }, // 12: cross
.{ .addr = 0x602630, .prologue = &.{ 0xd9, 0x41, 0x08, 0xd8, 0x4a, 0x08 } }, // 13: dot
.{ .addr = 0x4549F0, .prologue = &.{ 0xd9, 0x41, 0x08, 0xd9, 0x41, 0x04 } }, // 14: sqmag
.{ .addr = 0x453620, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x02 } }, // 15: evalPoly
.{ .addr = 0x637480, .prologue = &.{ 0x55, 0x8b, 0xec, 0x83, 0xec, 0x18 } }, // 16: planeNormal
.{ .addr = 0x6DC470, .prologue = &.{ 0x55, 0x8b, 0xec, 0x83, 0xec, 0x0c } }, // 17: transformAABox
};
var math_count: u32 = 0;
inline for (.{
.{ &math_vecMulMat4_hook, 0x7BCA80, wrapFC3r(&vecMulMat4_ColMajor, &math_vecMulMat4_hook), 0 },
.{ &math_matMulVec3_hook, 0x7BCAE0, wrapFC3r(&matMulVec3_RowMajor, &math_matMulVec3_hook), 1 },
.{ &math_quatMulMat4_hook, 0x7BCB40, wrapFC3r(&quatMulMat4, &math_quatMulMat4_hook), 2 },
.{ &math_vec3MulScalar_hook, 0x5F8CF0, wrapFC3r(&vec3MulScalar, &math_vec3MulScalar_hook), 3 },
.{ &math_vec3MulAssign_hook, 0x5132F0, wrapTC2r(&vec3MulAssign, &math_vec3MulAssign_hook), 4 },
.{ &math_applyTranslation_hook,0x7BDC40, wrapTC2r(&applyTranslationMatrix, &math_applyTranslation_hook),5 },
.{ &math_scaleByVec_hook, 0x7BDCA0, wrapTC2r(&scaleMatrix3x3ByVector, &math_scaleByVec_hook), 6 },
.{ &math_scaleByScalar_hook, 0x7BDD00, wrapTC2v(&scaleMatrix3x3ByScalar, &math_scaleByScalar_hook), 7 },
.{ &math_mul3x3_hook, 0x7BDFC0, wrapFC3r(&multiply3x3Matrix, &math_mul3x3_hook), 8 },
.{ &math_rotMat3x3_hook, 0x7BE490, wrapFC4r(&createAxisAngleRotMat3x3, &math_rotMat3x3_hook), 9 },
.{ &math_rotMat4x4_hook, 0x7BDB00, wrapFC4r(&createAxisAngleRotMat4x4, &math_rotMat4x4_hook), 10 },
.{ &math_cross_hook, 0x672130, wrapFC3r(&crossProduct, &math_cross_hook), 11 },
.{ &math_dot_hook, 0x602630, wrapFC2d(&dotProduct, &math_dot_hook), 12 },
.{ &math_sqmag_hook, 0x4549F0, wrapTC1d(&squaredMagnitude, &math_sqmag_hook), 13 },
.{ &math_evalPoly_hook, 0x453620, wrapFC3d(&evaluatePolynomial, &math_evalPoly_hook), 14 },
.{ &math_planeNormal_hook, 0x637480, wrapTC4v(&calculatePlaneNormal, &math_planeNormal_hook), 15 },
.{ &math_transformAABox_hook, 0x6DC470, wrapFC5v(&transformAABox, &math_transformAABox_hook), 16 },
}) |entry| {
hook.writeProtected(entry[1], math_hooks[entry[3]].prologue);
_ = entry[0].attach(entry[1], entry[2]);
math_count += 1;
}
log.fmt("ssemaths: {d}/17 math hooks installed\n", .{math_count});
}
pub fn removeHooks() void {
if (g_is_hook_owner) {
math_vecMulMat4_hook.detach();
math_matMulVec3_hook.detach();
math_quatMulMat4_hook.detach();
math_vec3MulScalar_hook.detach();
math_vec3MulAssign_hook.detach();
math_applyTranslation_hook.detach();
math_scaleByVec_hook.detach();
math_scaleByScalar_hook.detach();
math_mul3x3_hook.detach();
math_rotMat3x3_hook.detach();
math_rotMat4x4_hook.detach();
math_cross_hook.detach();
math_dot_hook.detach();
math_sqmag_hook.detach();
math_evalPoly_hook.detach();
math_planeNormal_hook.detach();
math_transformAABox_hook.detach();
critsec_hook.detach();
log.close();
mod_mutex.release(&g_mutex);
}
g_is_hook_owner = false;
}
-217
View File
@@ -23,25 +23,6 @@ extern fn rotateMatrixByAxisAngle(u32, u32, u32, u32) void;
extern fn multiplyMatrix4x4(u32, u32, u32) u32;
extern fn transformMatrix4x4_SSE(u32, u32, u32, u32, u32) void;
// math_sse.zig exports (UnitXP polyfill replacements)
extern fn vecMulMat4_ColMajor(u32, u32, u32) u32;
extern fn matMulVec3_RowMajor(u32, u32, u32) u32;
extern fn quatMulMat4(u32, u32, u32) u32;
extern fn vec3MulScalar(u32, u32, u32) u32;
extern fn vec3MulAssign(u32, u32) u32;
extern fn applyTranslationMatrix(u32, u32) u32;
extern fn scaleMatrix3x3ByVector(u32, u32) u32;
extern fn scaleMatrix3x3ByScalar(u32, u32) void;
extern fn multiply3x3Matrix(u32, u32, u32) u32;
extern fn createAxisAngleRotMat3x3(u32, u32, u32, u32) u32;
extern fn createAxisAngleRotMat4x4(u32, u32, u32, u32) u32;
extern fn crossProduct(u32, u32, u32) u32;
extern fn dotProduct(u32, u32) f64;
extern fn squaredMagnitude(u32) f64;
extern fn evaluatePolynomial(u32, u32, u32) f64;
extern fn calculatePlaneNormal(u32, u32, u32, u32) void;
extern fn transformAABox(u32, u32, u32, u32, u32) void;
pub const module_name: [*:0]const u8 = "transform44";
var g_mutex: ?*anyopaque = null;
@@ -1102,107 +1083,6 @@ fn textlineDetour(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) callconv(hook.
return ret;
}
// =============================================================================
// Hooks: math_sse (UnitXP polyfill replacements)
// Installed in lateInit() to clobber UnitXP's hooks. A/B tested.
// =============================================================================
// Fastcall fn types (ECX + EDX + stack)
const FC3r = fn (u32, u32, u32) callconv(hook.cc.fastcall) u32; // ECX,EDX,stack -> u32
const FC4r = fn (u32, u32, u32, u32) callconv(hook.cc.fastcall) u32;
const FC5v = fn (u32, u32, u32, u32, u32) callconv(hook.cc.fastcall) void;
const FC2d = fn (u32, u32) callconv(hook.cc.fastcall) f64;
const FC3d = fn (u32, u32, u32) callconv(hook.cc.fastcall) f64;
// Thiscall fn types (ECX + stack)
const TC2r = fn (u32, u32) callconv(hook.cc.thiscall) u32; // ECX,stack -> u32
const TC2v = fn (u32, u32) callconv(hook.cc.thiscall) void;
const TC1d = fn (u32) callconv(hook.cc.thiscall) f64; // ECX -> f64
const TC4v = fn (u32, u32, u32, u32) callconv(hook.cc.thiscall) void; // ECX,stack*3 -> void
// 1: vecMulMat4 __fastcall(ECX=result, EDX=vec, stack:mat) RET 0x4
// 2: matMulVec3 __fastcall(ECX=result, EDX=mat, stack:vec) RET 0x4
// 3: quatMulMat4 __fastcall(ECX=result, EDX=quat, stack:mat) RET 0x4
// 4: vec3MulScalar __fastcall(ECX=result, EDX=vec, stack:factor) RET 0x4
// 5: vec3MulAssign __thiscall(ECX=self, stack:factor) RET 0x4
// 6: applyTranslation __thiscall(ECX=mat, stack:vec) RET 0x4
// 7: scaleByVec __thiscall(ECX=mat, stack:vec) RET 0x4
// 8: scaleByScalar __thiscall(ECX=mat, stack:factor) RET 0x4
// 9: mul3x3 __fastcall(ECX=result, EDX=matA, stack:matB) RET 0x4
// 10: rotMat3x3 __fastcall(ECX=result, EDX=axis, stack:angle,is_unit) RET 0x8
// 11: rotMat4x4 __fastcall(ECX=result, EDX=axis, stack:angle,is_unit) RET 0x8
// 12: cross __fastcall(ECX=result, EDX=vecA, stack:vecB) RET 0x4
// 13: dot __fastcall(ECX=vecA, EDX=vecB) RET
// 14: sqmag __thiscall(ECX=vec) RET
// 15: (removed -- 0x699330 is vec3 comparison from Silicon, not UnitXP)
// 16: evalPoly __fastcall(ECX=count, EDX=coeffs, stack:factor) RET 0x4
// 17: planeNormal __thiscall(ECX=result, stack:p1,p2,p3) RET 0xC
// 18: transformAABox __fastcall(ECX=mat, EDX=vecA, stack:vecB,boxIn,boxOut) RET 0xC
var math_vecMulMat4_hook: hook.Detour(FC3r) = .{}; // 1 fastcall
var math_matMulVec3_hook: hook.Detour(FC3r) = .{}; // 2 fastcall
var math_quatMulMat4_hook: hook.Detour(FC3r) = .{}; // 3 fastcall
var math_vec3MulScalar_hook: hook.Detour(FC3r) = .{}; // 4 fastcall
var math_vec3MulAssign_hook: hook.Detour(TC2r) = .{}; // 5 thiscall
var math_applyTranslation_hook: hook.Detour(TC2r) = .{}; // 6 thiscall
var math_scaleByVec_hook: hook.Detour(TC2r) = .{}; // 7 thiscall
var math_scaleByScalar_hook: hook.Detour(TC2v) = .{}; // 8 thiscall
var math_mul3x3_hook: hook.Detour(FC3r) = .{}; // 9 fastcall
var math_rotMat3x3_hook: hook.Detour(FC4r) = .{}; // 10 fastcall
var math_rotMat4x4_hook: hook.Detour(FC4r) = .{}; // 11 fastcall
var math_cross_hook: hook.Detour(FC3r) = .{}; // 12 fastcall
var math_dot_hook: hook.Detour(FC2d) = .{}; // 13 fastcall
var math_sqmag_hook: hook.Detour(TC1d) = .{}; // 14 thiscall
var math_evalPoly_hook: hook.Detour(FC3d) = .{}; // 16 fastcall
var math_planeNormal_hook: hook.Detour(TC4v) = .{}; // 17 thiscall
var math_transformAABox_hook: hook.Detour(FC5v) = .{}; // 18 fastcall
// A/B detour wrappers -- fastcall variants
fn abFC3r(comptime custom_fn: anytype, comptime h: anytype) *const FC3r {
return &struct { fn f(a: u32, b: u32, c: u32) callconv(hook.cc.fastcall) u32 {
return if (ab_use_custom) custom_fn(a, b, c) else h.callOriginal(.{ a, b, c });
} }.f;
}
fn abFC4r(comptime custom_fn: anytype, comptime h: anytype) *const FC4r {
return &struct { fn f(a: u32, b: u32, c: u32, d: u32) callconv(hook.cc.fastcall) u32 {
return if (ab_use_custom) custom_fn(a, b, c, d) else h.callOriginal(.{ a, b, c, d });
} }.f;
}
fn abFC5v(comptime custom_fn: anytype, comptime h: anytype) *const FC5v {
return &struct { fn f(a: u32, b: u32, c: u32, d: u32, e: u32) callconv(hook.cc.fastcall) void {
if (ab_use_custom) custom_fn(a, b, c, d, e) else h.callOriginal(.{ a, b, c, d, e });
} }.f;
}
fn abFC2d(comptime custom_fn: anytype, comptime h: anytype) *const FC2d {
return &struct { fn f(a: u32, b: u32) callconv(hook.cc.fastcall) f64 {
return if (ab_use_custom) custom_fn(a, b) else h.callOriginal(.{ a, b });
} }.f;
}
fn abFC3d(comptime custom_fn: anytype, comptime h: anytype) *const FC3d {
return &struct { fn f(a: u32, b: u32, c: u32) callconv(hook.cc.fastcall) f64 {
return if (ab_use_custom) custom_fn(a, b, c) else h.callOriginal(.{ a, b, c });
} }.f;
}
// A/B detour wrappers -- thiscall variants
fn abTC2r(comptime custom_fn: anytype, comptime h: anytype) *const TC2r {
return &struct { fn f(a: u32, b: u32) callconv(hook.cc.thiscall) u32 {
return if (ab_use_custom) custom_fn(a, b) else h.callOriginal(.{ a, b });
} }.f;
}
fn abTC2v(comptime custom_fn: anytype, comptime h: anytype) *const TC2v {
return &struct { fn f(a: u32, b: u32) callconv(hook.cc.thiscall) void {
if (ab_use_custom) custom_fn(a, b) else h.callOriginal(.{ a, b });
} }.f;
}
fn abTC1d(comptime custom_fn: anytype, comptime h: anytype) *const TC1d {
return &struct { fn f(a: u32) callconv(hook.cc.thiscall) f64 {
return if (ab_use_custom) custom_fn(a) else h.callOriginal(.{a});
} }.f;
}
fn abTC4v(comptime custom_fn: anytype, comptime h: anytype) *const TC4v {
return &struct { fn f(a: u32, b: u32, c: u32, d: u32) callconv(hook.cc.thiscall) void {
if (ab_use_custom) custom_fn(a, b, c, d) else h.callOriginal(.{ a, b, c, d });
} }.f;
}
// =============================================================================
// Hook: blit_hub (0x5a4f60)
// __fastcall(ECX=int* vec2size, EDX=unknownFuncIndex,
@@ -1216,32 +1096,6 @@ const BlitHubPtr = *const BlitHubFn;
var blit_hub_hook: hook.Detour(BlitHubFn) = .{};
var unitxp_blit: ?BlitHubPtr = null; // UnitXP's detour, captured before clobber
// =============================================================================
// Hook: EnterCriticalSection spin count optimization (from UnitXP polyfill.cpp:474)
// Sets SpinCount=4000 on critical sections with SpinCount=0, reducing kernel
// transitions for short-held locks. The game initializes all CriticalSections
// with SpinCount=0, causing immediate kernel waits on any contention.
// =============================================================================
const WINAPI = std.builtin.CallingConvention.winapi;
const CritSecFn = fn (u32) callconv(WINAPI) void;
var critsec_hook: hook.Detour(CritSecFn) = .{};
extern "kernel32" fn GetModuleHandleA(name: [*:0]const u8) callconv(WINAPI) ?*anyopaque;
extern "kernel32" fn GetProcAddress(module: *anyopaque, name: [*:0]const u8) callconv(WINAPI) ?*anyopaque;
extern "kernel32" fn SetCriticalSectionSpinCount(cs: u32, spin: u32) callconv(WINAPI) u32;
fn critSecDetour(cs_ptr: u32) callconv(WINAPI) void {
if (cs_ptr != 0 and (cs_ptr & 1) == 0) {
// CRITICAL_SECTION.SpinCount is at offset +0x18 on Win32
const spin_count = hook.readMem(u32, cs_ptr + 0x18);
if (spin_count == 0) {
_ = SetCriticalSectionSpinCount(cs_ptr, 4000);
}
}
critsec_hook.callOriginal(.{cs_ptr});
}
fn blitMemcpy(w: u32, h: u32, src: u32, src_pitch: u32, dst: u32, dst_pitch: u32, pixel_size: u32) void {
const row_bytes = w * pixel_size;
if (src_pitch == dst_pitch and row_bytes == src_pitch) {
@@ -1562,14 +1416,6 @@ pub fn installHooks() void {
_ = matmul_hook.attach(0x7bc6a0, &matmulDetour);
_ = textline_hook.attach(0x5ce0c0, &textlineDetour);
// CriticalSection spin count optimization (UnitXP polyfill)
if (GetModuleHandleA("kernel32")) |k32| {
if (GetProcAddress(k32, "EnterCriticalSection")) |ecs_addr| {
_ = critsec_hook.attach(@intFromPtr(ecs_addr), &critSecDetour);
log.print("critsec: SpinCount=4000 hook installed\n");
}
}
// TSC timer calibration (ported from VanillaFixes)
timer_fix.init();
const ti = timer_fix.getInfo();
@@ -1609,51 +1455,6 @@ pub fn lateInit() void {
hook.writeProtected(BLIT_ADDR, &.{ 0x55, 0x8B, 0xEC, 0xA1, 0x58, 0xF5, 0xC0, 0x00 });
_ = blit_hub_hook.attach(BLIT_ADDR, &blitHubDetour);
log.print("blit_hub: hooked (true original baseline)\n");
// math_sse replacements -- restore original prologues (clobber UnitXP), then hook
// A/B tested: CUSTOM=our SSE, BASELINE=original x87
// Bitmask: bit N enables hook N (bit 0 unused). 0x3FFFE = all 17, 0 = none.
// Hook 15 removed (0x699330 is vec3 comparison from Silicon, not UnitXP x87 polyfill)
const MATH_HOOKS: u32 = 0x77FFE; // all 17 (bits 1-14, 16-18; skip 15)
const MathHook = struct { addr: u32, prologue: []const u8 };
const math_hooks = [_]MathHook{
.{ .addr = 0x7BCA80, .prologue = &.{ 0x55, 0x8b, 0xec, 0x56, 0x8b, 0x75, 0x08 } }, // 1: vecMulMat4
.{ .addr = 0x7BCAE0, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x42, 0x28 } }, // 2: matMulVec3
.{ .addr = 0x7BCB40, .prologue = &.{ 0x55, 0x8b, 0xec, 0x56, 0x8b, 0x75, 0x08 } }, // 3: quatMulMat4
.{ .addr = 0x5F8CF0, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x45, 0x08 } }, // 4: vec3MulScalar
.{ .addr = 0x5132F0, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x45, 0x08 } }, // 5: vec3MulAssign
.{ .addr = 0x7BDC40, .prologue = &.{ 0x55, 0x8b, 0xec, 0x8b, 0x45, 0x08 } }, // 6: applyTranslation
.{ .addr = 0x7BDCA0, .prologue = &.{ 0x55, 0x8b, 0xec, 0x8b, 0x45, 0x08 } }, // 7: scaleByVec
.{ .addr = 0x7BDD00, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x45, 0x08 } }, // 8: scaleByScalar
.{ .addr = 0x7BDFC0, .prologue = &.{ 0x55, 0x8b, 0xec, 0x51, 0xd9, 0x42, 0x1c } }, // 9: mul3x3
.{ .addr = 0x7BE490, .prologue = &.{ 0x55, 0x8b, 0xec, 0x83, 0xec, 0x18 } }, // 10: rotMat3x3
.{ .addr = 0x7BDB00, .prologue = &.{ 0x55, 0x8b, 0xec, 0x83, 0xec, 0x18 } }, // 11: rotMat4x4
.{ .addr = 0x672130, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x02 } }, // 12: cross
.{ .addr = 0x602630, .prologue = &.{ 0xd9, 0x41, 0x08, 0xd8, 0x4a, 0x08 } }, // 13: dot
.{ .addr = 0x4549F0, .prologue = &.{ 0xd9, 0x41, 0x08, 0xd9, 0x41, 0x04 } }, // 14: sqmag
.{ .addr = 0x453620, .prologue = &.{ 0x55, 0x8b, 0xec, 0xd9, 0x02 } }, // 15: evalPoly (was 16)
.{ .addr = 0x637480, .prologue = &.{ 0x55, 0x8b, 0xec, 0x83, 0xec, 0x18 } }, // 16: planeNormal (was 17)
.{ .addr = 0x6DC470, .prologue = &.{ 0x55, 0x8b, 0xec, 0x83, 0xec, 0x0c } }, // 17: transformAABox (was 18)
};
var math_count: u32 = 0;
if ((MATH_HOOKS & (1 << 1) != 0)) { hook.writeProtected(0x7BCA80, math_hooks[0].prologue); _ = math_vecMulMat4_hook.attach(0x7BCA80, abFC3r(&vecMulMat4_ColMajor, &math_vecMulMat4_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 2) != 0)) { hook.writeProtected(0x7BCAE0, math_hooks[1].prologue); _ = math_matMulVec3_hook.attach(0x7BCAE0, abFC3r(&matMulVec3_RowMajor, &math_matMulVec3_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 3) != 0)) { hook.writeProtected(0x7BCB40, math_hooks[2].prologue); _ = math_quatMulMat4_hook.attach(0x7BCB40, abFC3r(&quatMulMat4, &math_quatMulMat4_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 4) != 0)) { hook.writeProtected(0x5F8CF0, math_hooks[3].prologue); _ = math_vec3MulScalar_hook.attach(0x5F8CF0, abFC3r(&vec3MulScalar, &math_vec3MulScalar_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 5) != 0)) { hook.writeProtected(0x5132F0, math_hooks[4].prologue); _ = math_vec3MulAssign_hook.attach(0x5132F0, abTC2r(&vec3MulAssign, &math_vec3MulAssign_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 6) != 0)) { hook.writeProtected(0x7BDC40, math_hooks[5].prologue); _ = math_applyTranslation_hook.attach(0x7BDC40, abTC2r(&applyTranslationMatrix, &math_applyTranslation_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 7) != 0)) { hook.writeProtected(0x7BDCA0, math_hooks[6].prologue); _ = math_scaleByVec_hook.attach(0x7BDCA0, abTC2r(&scaleMatrix3x3ByVector, &math_scaleByVec_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 8) != 0)) { hook.writeProtected(0x7BDD00, math_hooks[7].prologue); _ = math_scaleByScalar_hook.attach(0x7BDD00, abTC2v(&scaleMatrix3x3ByScalar, &math_scaleByScalar_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 9) != 0)) { hook.writeProtected(0x7BDFC0, math_hooks[8].prologue); _ = math_mul3x3_hook.attach(0x7BDFC0, abFC3r(&multiply3x3Matrix, &math_mul3x3_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 10) != 0)) { hook.writeProtected(0x7BE490, math_hooks[9].prologue); _ = math_rotMat3x3_hook.attach(0x7BE490, abFC4r(&createAxisAngleRotMat3x3, &math_rotMat3x3_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 11) != 0)) { hook.writeProtected(0x7BDB00, math_hooks[10].prologue); _ = math_rotMat4x4_hook.attach(0x7BDB00, abFC4r(&createAxisAngleRotMat4x4, &math_rotMat4x4_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 12) != 0)) { hook.writeProtected(0x672130, math_hooks[11].prologue); _ = math_cross_hook.attach(0x672130, abFC3r(&crossProduct, &math_cross_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 13) != 0)) { hook.writeProtected(0x602630, math_hooks[12].prologue); _ = math_dot_hook.attach(0x602630, abFC2d(&dotProduct, &math_dot_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 14) != 0)) { hook.writeProtected(0x4549F0, math_hooks[13].prologue); _ = math_sqmag_hook.attach(0x4549F0, abTC1d(&squaredMagnitude, &math_sqmag_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 16) != 0)) { hook.writeProtected(0x453620, math_hooks[14].prologue); _ = math_evalPoly_hook.attach(0x453620, abFC3d(&evaluatePolynomial, &math_evalPoly_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 17) != 0)) { hook.writeProtected(0x637480, math_hooks[15].prologue); _ = math_planeNormal_hook.attach(0x637480, abTC4v(&calculatePlaneNormal, &math_planeNormal_hook)); math_count += 1; }
if ((MATH_HOOKS & (1 << 18) != 0)) { hook.writeProtected(0x6DC470, math_hooks[16].prologue); _ = math_transformAABox_hook.attach(0x6DC470, abFC5v(&transformAABox, &math_transformAABox_hook)); math_count += 1; }
log.fmt("math_sse: {d}/17 hooks installed (mask=0x{x})\n", .{ math_count, MATH_HOOKS });
}
pub fn removeHooks() void {
@@ -1699,24 +1500,6 @@ pub fn removeHooks() void {
matmul_hook.detach();
textline_hook.detach();
blit_hub_hook.detach();
critsec_hook.detach();
math_vecMulMat4_hook.detach();
math_matMulVec3_hook.detach();
math_quatMulMat4_hook.detach();
math_vec3MulScalar_hook.detach();
math_vec3MulAssign_hook.detach();
math_applyTranslation_hook.detach();
math_scaleByVec_hook.detach();
math_scaleByScalar_hook.detach();
math_mul3x3_hook.detach();
math_rotMat3x3_hook.detach();
math_rotMat4x4_hook.detach();
math_cross_hook.detach();
math_dot_hook.detach();
math_sqmag_hook.detach();
math_evalPoly_hook.detach();
math_planeNormal_hook.detach();
math_transformAABox_hook.detach();
log.close();
mod_mutex.release(&g_mutex);
}