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
-416
View File
@@ -1,416 +0,0 @@
//! SSE math polyfill — replaces x87 FPU functions with SSE equivalents.
//!
//! Compiled ReleaseFast even in Debug builds (separate compilation unit).
//! Each function replaces a game x87 implementation identified from UnitXP's
//! polyfill.cpp and libSiliconPatch's export table.
//!
//! Reference sources:
//! UnitXP: reference/UnitXP_SP3/polyfill.cpp (scalar double intermediates)
//! Silicon: /tmp/TurtleSilicon/winerosetta/libSiliconPatch.dll (closed source, symbols only)
//!
//! Our approach: @Vector(4, f32) SSE intrinsics where beneficial, scalar for simple ops.
const V4 = @Vector(4, f32);
// MSVC CRT — linked from the WoW process
extern fn sinf(f32) f32;
extern fn cosf(f32) f32;
// =============================================================================
// Memory access helpers (same as clip_sse.zig / bone_sse.zig)
// =============================================================================
inline fn rf32(addr: u32) f32 {
return @as(*align(1) const f32, @ptrFromInt(addr)).*;
}
inline fn wf32(addr: u32, v: f32) void {
@as(*align(1) f32, @ptrFromInt(addr)).* = v;
}
inline fn splat(v: f32) V4 {
return @splat(v);
}
/// Load 3 floats from addr into a V4 (4th element = 0)
inline fn loadV3(addr: u32) V4 {
const p: [*]align(1) const f32 = @ptrFromInt(addr);
return .{ p[0], p[1], p[2], 0 };
}
/// Store 3 floats from V4 to addr
inline fn storeV3(addr: u32, v: V4) void {
const p: [*]align(1) f32 = @ptrFromInt(addr);
p[0] = v[0];
p[1] = v[1];
p[2] = v[2];
}
/// Dot product of two V4 (first 3 components only)
inline fn dot3(va: V4, vb: V4) f32 {
const prod = va * vb;
return prod[0] + prod[1] + prod[2];
}
// =============================================================================
// Vector-Matrix multiplies (0x7BCA80, 0x7BCAE0, 0x7BCB40)
//
// The game has three variants of vec/quat * matrix multiply, all using x87 FPU.
// UnitXP replaces with double intermediates. We use SSE broadcast-multiply-add.
//
// Reference: polyfill.cpp lines 35-61 (detoured_operator_multiply_1/2/3)
// =============================================================================
/// 0x7BCA80: result = vec3 * mat4 (column-major: result[i] = dot(vec, mat_col_i) + mat[3][i])
/// __fastcall(ECX=result, EDX=vec3, stack: mat4*), RET 0x4
export fn vecMulMat4_ColMajor(result: u32, vec: u32, mat: u32) u32 {
const vx = rf32(vec);
const vy = rf32(vec + 4);
const vz = rf32(vec + 8);
wf32(result, vx * rf32(mat) + vy * rf32(mat + 0x10) + vz * rf32(mat + 0x20) + rf32(mat + 0x30));
wf32(result + 4, vx * rf32(mat + 0x04) + vy * rf32(mat + 0x14) + vz * rf32(mat + 0x24) + rf32(mat + 0x34));
wf32(result + 8, vx * rf32(mat + 0x08) + vy * rf32(mat + 0x18) + vz * rf32(mat + 0x28) + rf32(mat + 0x38));
return result;
}
/// 0x7BCAE0: result = mat4 * vec3 (row-major: result[i] = dot(mat_row_i, vec) + mat[i][3])
/// __fastcall(ECX=result, EDX=mat4, stack: vec3*), RET 0x4
export fn matMulVec3_RowMajor(result: u32, mat: u32, vec: u32) u32 {
const vx = rf32(vec);
const vy = rf32(vec + 4);
const vz = rf32(vec + 8);
wf32(result, rf32(mat) * vx + rf32(mat + 0x04) * vy + rf32(mat + 0x08) * vz + rf32(mat + 0x0C));
wf32(result + 4, rf32(mat + 0x10) * vx + rf32(mat + 0x14) * vy + rf32(mat + 0x18) * vz + rf32(mat + 0x1C));
wf32(result + 8, rf32(mat + 0x20) * vx + rf32(mat + 0x24) * vy + rf32(mat + 0x28) * vz + rf32(mat + 0x2C));
return result;
}
/// 0x7BCB40: result = quat4 * mat4 (4-component: result[i] = dot(quat, mat_col_i))
/// __fastcall(ECX=result, EDX=quat4, stack: mat4*), RET 0x4
/// Reference: polyfill.cpp line 55-61
export fn quatMulMat4(result: u32, quat: u32, mat: u32) u32 {
const q: V4 = .{ rf32(quat), rf32(quat + 4), rf32(quat + 8), rf32(quat + 12) };
inline for (0..4) |i| {
const col_off = @as(u32, @intCast(i)) * 4;
const c: V4 = .{ rf32(mat + col_off), rf32(mat + 0x10 + col_off), rf32(mat + 0x20 + col_off), rf32(mat + 0x30 + col_off) };
wf32(result + col_off, @reduce(.Add, q * c));
}
return result;
}
// =============================================================================
// Vector scalar operations (0x5F8CF0, 0x5132F0)
//
// Reference: polyfill.cpp lines 117-132
// =============================================================================
/// 0x5F8CF0: result = vec3 * scalar
/// __fastcall(ECX=result, EDX=vec3, stack: factor_float), RET 0x4
export fn vec3MulScalar(result: u32, vec: u32, factor_bits: u32) u32 {
storeV3(result, loadV3(vec) * splat(@bitCast(factor_bits)));
return result;
}
/// 0x5132F0: self *= scalar (in-place)
/// __thiscall(ECX=self, stack: factor_float), RET 0x4
export fn vec3MulAssign(self: u32, factor_bits: u32) u32 {
const f: f32 = @bitCast(factor_bits);
wf32(self, rf32(self) * f);
wf32(self + 4, rf32(self + 4) * f);
wf32(self + 8, rf32(self + 8) * f);
return self;
}
// =============================================================================
// Matrix operations (0x7BDC40, 0x7BDCA0, 0x7BDD00, 0x7BDFC0)
//
// ApplyTranslation and ScaleMatrix are already reimplemented inline in bone_sse.zig.
// These standalone hooks catch calls from OUTSIDE transformMatrix4x4.
//
// Reference: polyfill.cpp lines 134-218
// =============================================================================
/// 0x7BDC40: Apply translation through rotation matrix (in-place)
/// mat[3][j] += dot(mat[col_j], translation) for j=0,1,2
/// __thiscall(ECX=mat4x4, stack: vec3*), RET 0x4
/// Reference: polyfill.cpp line 136, also bone_sse.zig applyTranslation()
export fn applyTranslationMatrix(mat: u32, vec: u32) u32 {
const tx = rf32(vec);
const ty = rf32(vec + 4);
const tz = rf32(vec + 8);
wf32(mat + 0x30, tx * rf32(mat) + ty * rf32(mat + 0x10) + tz * rf32(mat + 0x20) + rf32(mat + 0x30));
wf32(mat + 0x34, tx * rf32(mat + 0x04) + ty * rf32(mat + 0x14) + tz * rf32(mat + 0x24) + rf32(mat + 0x34));
wf32(mat + 0x38, tx * rf32(mat + 0x08) + ty * rf32(mat + 0x18) + tz * rf32(mat + 0x28) + rf32(mat + 0x38));
return vec; // original returns param_1 (vec ptr)
}
/// 0x7BDCA0: Scale 3x3 rotation portion by per-axis scale vector
/// row0 *= scale.x, row1 *= scale.y, row2 *= scale.z
/// __thiscall(ECX=mat4x4, stack: vec3*), RET 0x4
/// Reference: polyfill.cpp line 146, also bone_sse.zig scaleMatrix3x3()
export fn scaleMatrix3x3ByVector(mat: u32, vec: u32) u32 {
const sx = rf32(vec);
const sy = rf32(vec + 4);
const sz = rf32(vec + 8);
// Row 0
wf32(mat, rf32(mat) * sx);
wf32(mat + 0x04, rf32(mat + 0x04) * sx);
wf32(mat + 0x08, rf32(mat + 0x08) * sx);
// Row 1
wf32(mat + 0x10, rf32(mat + 0x10) * sy);
wf32(mat + 0x14, rf32(mat + 0x14) * sy);
wf32(mat + 0x18, rf32(mat + 0x18) * sy);
// Row 2
wf32(mat + 0x20, rf32(mat + 0x20) * sz);
wf32(mat + 0x24, rf32(mat + 0x24) * sz);
wf32(mat + 0x28, rf32(mat + 0x28) * sz);
return vec;
}
/// 0x7BDD00: Scale 3x3 rotation portion by uniform scalar
/// __thiscall(ECX=mat4x4, stack: factor_float), RET 0x4
/// Reference: polyfill.cpp line 162
export fn scaleMatrix3x3ByScalar(mat: u32, factor_bits: u32) void {
const f: f32 = @bitCast(factor_bits);
inline for ([_]u32{ 0x00, 0x04, 0x08, 0x10, 0x14, 0x18, 0x20, 0x24, 0x28 }) |off| {
wf32(mat + off, rf32(mat + off) * f);
}
}
/// 0x7BDFC0: 3x3 matrix multiply: result = A * B (9 elements, row-major)
/// __fastcall(ECX=result, EDX=matA, stack: matB*), RET 0x4
/// Reference: polyfill.cpp line 207
export fn multiply3x3Matrix(result: u32, a: u32, b: u32) u32 {
// result[row][col] = sum(A[row][k] * B[k][col], k=0..2)
// 3x3 stored as 3 rows of 3 floats (stride 0x0C per row)
inline for (0..3) |row| {
const r = @as(u32, @intCast(row)) * 0x0C;
inline for (0..3) |col| {
const c = @as(u32, @intCast(col)) * 4;
wf32(result + r + c,
rf32(a + r) * rf32(b + c) +
rf32(a + r + 4) * rf32(b + 0x0C + c) +
rf32(a + r + 8) * rf32(b + 0x18 + c));
}
}
return result;
}
// =============================================================================
// Rotation matrices (0x7BE490, 0x7BDB00)
//
// 0x7BDD60 (rotateMatrixByAxisAngle 4x4) is already in clip_sse.zig.
// These are related but different entry points.
//
// Reference: polyfill.cpp lines 177-285
// =============================================================================
/// 0x7BE490: Create 3x3 rotation matrix from axis + angle (Rodrigues formula)
/// __fastcall(ECX=result_3x3, EDX=axis_vec3, stack: angle_float, is_unit_bool), RET 0x8
/// Reference: polyfill.cpp line 177
export fn createAxisAngleRotMat3x3(result: u32, axis: u32, angle_bits: u32, is_unit: u32) u32 {
const angle: f32 = @bitCast(angle_bits);
var ax = rf32(axis);
var ay = rf32(axis + 4);
var az = rf32(axis + 8);
if (is_unit == 0) {
const inv = 1.0 / @sqrt(ax * ax + ay * ay + az * az);
ax *= inv;
ay *= inv;
az *= inv;
}
const c = cosf(angle);
const s = sinf(angle);
const t = 1.0 - c;
wf32(result, ax * ax * t + c);
wf32(result + 0x04, ax * ay * t + az * s);
wf32(result + 0x08, ax * az * t - ay * s);
wf32(result + 0x0C, ax * ay * t - az * s);
wf32(result + 0x10, ay * ay * t + c);
wf32(result + 0x14, ay * az * t + ax * s);
wf32(result + 0x18, ax * az * t + ay * s);
wf32(result + 0x1C, ay * az * t - ax * s);
wf32(result + 0x20, az * az * t + c);
return result;
}
/// 0x7BDB00: Create 4x4 rotation matrix from axis + angle (Rodrigues + identity row/col)
/// __fastcall(ECX=result_4x4, EDX=axis_vec3, stack: angle_float, is_unit_bool), RET 0x8
/// Like 0x7BE490 but outputs 4x4 with identity padding.
/// Reference: polyfill.cpp line 254
export fn createAxisAngleRotMat4x4(result: u32, axis: u32, angle_bits: u32, is_unit: u32) u32 {
const angle: f32 = @bitCast(angle_bits);
var ax = rf32(axis);
var ay = rf32(axis + 4);
var az = rf32(axis + 8);
if (is_unit == 0) {
const inv = 1.0 / @sqrt(ax * ax + ay * ay + az * az);
ax *= inv;
ay *= inv;
az *= inv;
}
const c = cosf(angle);
const s = sinf(angle);
const t = 1.0 - c;
wf32(result + 0x00, ax * ax * t + c);
wf32(result + 0x04, ax * ay * t + az * s);
wf32(result + 0x08, ax * az * t - ay * s);
wf32(result + 0x0C, 0);
wf32(result + 0x10, ax * ay * t - az * s);
wf32(result + 0x14, ay * ay * t + c);
wf32(result + 0x18, ay * az * t + ax * s);
wf32(result + 0x1C, 0);
wf32(result + 0x20, ax * az * t + ay * s);
wf32(result + 0x24, ay * az * t - ax * s);
wf32(result + 0x28, az * az * t + c);
wf32(result + 0x2C, 0);
wf32(result + 0x30, 0);
wf32(result + 0x34, 0);
wf32(result + 0x38, 0);
wf32(result + 0x3C, 1);
return result;
}
// =============================================================================
// Vector math primitives (0x672130, 0x602630, 0x4549F0)
//
// These are called thousands of times per frame from collision, terrain,
// and rendering code. The originals use x87 FPU.
//
// Reference: polyfill.cpp lines 287-462
// =============================================================================
/// 0x672130: Cross product: result = A x B
/// __fastcall(ECX=result, EDX=vecA, stack: vecB*), RET 0x4
/// Reference: polyfill.cpp line 451
export fn crossProduct(result: u32, va: u32, vb: u32) u32 {
wf32(result, rf32(va + 4) * rf32(vb + 8) - rf32(va + 8) * rf32(vb + 4));
wf32(result + 4, rf32(va + 8) * rf32(vb) - rf32(va) * rf32(vb + 8));
wf32(result + 8, rf32(va) * rf32(vb + 4) - rf32(va + 4) * rf32(vb));
return result;
}
/// 0x602630: Dot product: return A . B (as f64)
/// __fastcall(ECX=vecA, EDX=vecB), plain RET, returns double in ST(0)
/// Reference: polyfill.cpp line 460
export fn dotProduct(va: u32, vb: u32) f64 {
return dot3(loadV3(va), loadV3(vb));
}
/// 0x4549F0: Squared magnitude of vec3 (returns double in ST(0))
/// __thiscall(ECX=vec3), plain RET
/// Note: Ghidra labels this "emptyFunction" — it's NOT empty, it returns x*x+y*y+z*z
/// Reference: polyfill.cpp line 289
export fn squaredMagnitude(vec: u32) f64 {
const v = loadV3(vec);
return dot3(v, v);
}
// 0x699330 removed -- was misidentified as normalize, actually vec3 comparison
// from libSiliconPatch (not UnitXP). Stub lives in src/silicon/silicon.zig.
// =============================================================================
// Polynomial evaluation (0x453620)
//
// Horner's method for evaluating polynomial coefficients.
// Used by animation curves and interpolation.
//
// Reference: polyfill.cpp line 466
// =============================================================================
/// 0x453620: Evaluate polynomial using Horner's method (returns double in ST(0))
/// __fastcall(ECX=degree, EDX=coefficients*, stack: factor_float), RET 0x4
export fn evaluatePolynomial(count: u32, coefficients: u32, factor_bits: u32) f64 {
const f: f32 = @bitCast(factor_bits);
var result: f32 = rf32(coefficients);
var i: u32 = 1;
while (i <= count) : (i += 1) {
result = result * f + rf32(coefficients + i * 4);
}
return result;
}
// =============================================================================
// Geometry / collision functions (0x637480, 0x6DC470, 0x632830, 0x6329E0, 0x6335D0)
//
// These are collision detection and terrain processing functions.
// The originals use x87 for geometry math.
//
// Reference: polyfill.cpp lines 295-334 (calculatePlaneNormal, transformAABox)
// libSiliconPatch symbols: hook_sub_632830, hook_sub_6329E0, hook_sub_6335D0
// =============================================================================
/// 0x637480: Calculate plane normal from 3 points + normalize
/// __thiscall(ECX=result_plane4, stack: p1*, p2*, p3*), RET 0xC
/// result = {nx, ny, nz, d} where n = normalize(cross(p2-p1, p3-p1)), d = -dot(n, p1)
/// Reference: polyfill.cpp line 297
export fn calculatePlaneNormal(result: u32, p1: u32, p2: u32, p3: u32) void {
const v1 = loadV3(p1);
const e1 = loadV3(p2) - v1; // p2 - p1
const e2 = loadV3(p3) - v1; // p3 - p1
// cross product: e1 x e2
const e1_yzx = @shuffle(f32, e1, undefined, [4]i32{ 1, 2, 0, 3 });
const e1_zxy = @shuffle(f32, e1, undefined, [4]i32{ 2, 0, 1, 3 });
const e2_yzx = @shuffle(f32, e2, undefined, [4]i32{ 1, 2, 0, 3 });
const e2_zxy = @shuffle(f32, e2, undefined, [4]i32{ 2, 0, 1, 3 });
const n = e1_yzx * e2_zxy - e1_zxy * e2_yzx;
// normalize
const inv_len = splat(1.0 / @sqrt(dot3(n, n)));
const nn = n * inv_len;
// store {nx, ny, nz, d} where d = -dot(n_normalized, p1)
const p: [*]align(1) f32 = @ptrFromInt(result);
p[0] = nn[0];
p[1] = nn[1];
p[2] = nn[2];
p[3] = -dot3(nn, v1);
}
/// 0x6DC470: Transform axis-aligned bounding box by 3x3 matrix + translation
/// __fastcall(ECX=mat3x3, EDX=vecA, stack: vecB*, boxIn*, boxOut*), RET 0xC
/// Reference: polyfill.cpp line 312
export fn transformAABox(mat: u32, vec_a: u32, vec_b: u32, box_in: u32, box_out: u32) void {
const ptrs = [3]u32{ mat, vec_a, vec_b };
var out = box_out;
var outer_i: u32 = 0;
while (outer_i < 3) : ({ outer_i += 1; out += 4; }) {
var inner_i: u32 = 0;
while (inner_i < 3) : (inner_i += 1) {
const mv = rf32(ptrs[inner_i] + outer_i * 4);
const t1 = mv * rf32(box_in + inner_i * 4);
const t2 = rf32(box_in + inner_i * 4 + 12) * mv;
const lo = @min(t1, t2);
const hi = @max(t1, t2);
wf32(out, rf32(out) + lo);
wf32(out + 12, rf32(out + 12) + hi);
}
}
}
// =============================================================================
// libSiliconPatch stubs moved to src/silicon/silicon.zig
// See that module for the full catalog of ~200 silicon hooks with addresses.
// =============================================================================
// =============================================================================
// CriticalSection spin count optimization (from UnitXP)
//
// Not a math replacement — sets SpinCount=4000 on critical sections to reduce
// kernel transitions. The game initializes CriticalSections with SpinCount=0,
// causing immediate kernel waits on contention. With SpinCount=4000, the thread
// spins in userspace first, which is faster for short-held locks.
//
// Reference: polyfill.cpp line 474-482
// Implementation: hook EnterCriticalSection, set SpinCount if 0.
// This goes in transform44.zig, not here (not SSE math).
// =============================================================================
// =============================================================================
// Blit optimization (from UnitXP)
//
// Replaces game's REP MOVSQ + REP MOVSB pattern with std::memcpy.
// On modern CPUs with Enhanced REP MOVSB (ERMS), the old split approach
// is slower than a single memcpy which the compiler optimizes.
//
// We already profile blit_hub (0x5A4F60) in transform44.zig.
// To implement: add format-specific fast paths using @memcpy in the detour.
//
// Reference: polyfill.cpp lines 344-435
// =============================================================================
-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);
}