diff --git a/build.zig b/build.zig index 2693ff3..72bc349 100644 --- a/build.zig +++ b/build.zig @@ -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, }), diff --git a/src/main.zig b/src/main.zig index 41d0a77..7dad454 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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 .{}, diff --git a/src/transform44/math_sse.zig b/src/ssemaths/math_sse.zig similarity index 96% rename from src/transform44/math_sse.zig rename to src/ssemaths/math_sse.zig index 78fc649..a3212b6 100644 --- a/src/transform44/math_sse.zig +++ b/src/ssemaths/math_sse.zig @@ -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); diff --git a/src/ssemaths/ssemaths.zig b/src/ssemaths/ssemaths.zig new file mode 100644 index 0000000..8607851 --- /dev/null +++ b/src/ssemaths/ssemaths.zig @@ -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; +} diff --git a/src/transform44/transform44.zig b/src/transform44/transform44.zig index 22e2161..899c5d2 100644 --- a/src/transform44/transform44.zig +++ b/src/transform44/transform44.zig @@ -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); }