Files
WeirdUtils/src/silicon/silicon.zig
T
MarcelineVQ 035355b757 perf: FrustumCullBoundingBox SSE replacement, 1.6x speedup
- silicon_sse: si_frustumCullBBox (0x686000) — inline V4 mat*vec3
  transforms, SSE perspective divide, 4-wide horizon buffer scan.
  Benched 1.6x (117→72 cyc/call). Installed via JMP patch (544 bytes,
  won't fit in 380-byte original).
- bench: add frustumCullBBox benchmark with mapped globals, identity
  matrices, and horizon buffer test fixture.
- Remove patch table entry for frustumCullBBox, install via detour hook
  instead (allows future A/B testing if needed).
2026-03-23 20:46:28 -07:00

2353 lines
110 KiB
Zig

//! silicon -- SSE2 math replacements for WoW 1.12.1 x87 FPU code
//!
//! Ported from libSiliconPatch240.dll (turtle silicon patch).
//! Each function replaces an x87 FPU implementation with SSE2 equivalent.
//! Addresses verified via Ghidra analysis of both WoW.exe and libSiliconPatch.
//!
//! This module is a catalog of all functions that silicon hooks. Functions
//! are organized by category. Each entry includes the WoW.exe address,
//! silicon's hook name, whether silicon has an SSE2 replacement (vs just
//! an x87 rewrite), and implementation status.
//!
//! To implement: decompile the WoW.exe function via ghidra-cli skill,
//! verify calling convention from prologue/epilogue, write SSE2 replacement.
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 = "silicon";
var g_mutex: ?*anyopaque = null;
var g_is_hook_owner: bool = false;
var log_state: logging.Logger = .{};
// =============================================================================
// Category 1: Low-level scalar math (ftol, sincos, facing)
// =============================================================================
// 0x749280: calculateSinCos
// __stdcall(float angle, float* outSin, float* outCos), RET 0xC
// Uses x87 FSINCOS to compute sin/cos, stores to output pointers.
// Silicon: does not hook directly, uses SSE internally
// Status: stub
// 0x40CF81: GetFPUControlWord (NOT ftol — silicon mislabel)
// __stdcall(uint mask, uint newBits), RET (callee cleanup)
// Reads/modifies FPU control word via FSTCW/FLDCW. Returns old CW as int.
// 24 callers — FP error handling and rounding support routines.
// Silicon: hook_ftol (misleading name — this is the CW helper, not ftol itself)
// Status: probed
//
// 0x40A2B0: __ftol (the REAL float-to-long truncation)
// __cdecl(), RET (no stack cleanup). Input: x87 ST(0). Output: EAX:EDX (int64).
// Classic MSVC CRT: FSTCW/OR AH,0x0C/FLDCW/FISTP/FLDCW restore. 51+ callers
// across Lua, text rendering, camera, terrain, animation, coordinates.
// SSE2 replacement: single CVTTSS2SI eliminates the rounding mode dance.
// Status: probed
// 0x4183D0: WritePointerToStream
// __thiscall(this_ECX, pointer_stack), RET 0x4
// Writes 4-byte pointer into stream buffer. Bounds-checks write position (this+0x10),
// calls virtual resize if needed, stores and advances cursor by 4. Not pure math.
// Silicon: hook_sub_4183D0
// Status: stub
// =============================================================================
// Category 2: Vector operations (C3Vector, C2Vector)
// =============================================================================
// 0x4549C0: normalizeVector3
// __thiscall(Vec3* ECX, float length_stack), RET 0x4
// Divides each Vec3 component by pre-computed length: scale = 1.0/length, xyz *= scale
// Pure x87 scalar (FLD/FDIV/FMUL/FSTP) — trivial SSE2 candidate
// Silicon: hook_sub_4549C0, sub_4549F0_sse2
// Status: stub
// 0x602630: Vector3_DotProduct
// __fastcall(vecA_ECX, vecB_EDX), RET (no stack cleanup)
// Classic dot product: a.z*b.z + a.y*b.y + a.x*b.x. Returns f64 via x87 ST(0).
// 14 bytes, 9 instructions. Tiny leaf function.
// Silicon: not hooked (different from NTempest Dot)
// Status: stub
// 0x686640: ComputeFrustumPlanesFromVertices (misnamed SetVector3 in Ghidra)
// __fastcall(this_ECX), RET (no stack cleanup)
// Computes 4 clipping plane equations (normal+distance) from 8 corner vertices
// using cross products + NormalizeVector3_InPlace + DotProduct. Also computes negated 5th plane.
// Silicon: hook_sub_686640, sub_686640_sse2
// Status: stub
// 0x686820: TranslateBoundingVolume (misnamed NormalizeVector3 in Ghidra)
// __thiscall(this_ECX, offsetVec3_stack), RET 0x4
// Translates all 8 corner vertices (+0x60..+0xB4) by offset, recomputes 6 plane
// distances, translates min/max bounds (+0xC0, +0xCC).
// Silicon: not directly hooked
// Status: stub
// 0x6868E0: TransformBoundingVolume (misnamed DotProductVector3 in Ghidra)
// __thiscall(this_ECX, matrix3x3_stack), RET 0x4
// Transforms all 8 corners via transformVector3InPlace, calls ComputeFrustumPlanes
// (0x686640), transforms min/max bounds.
// Silicon: not directly hooked
// Status: stub
// 0x6720F0: NormalizeVector3_InPlace
// __fastcall(vec3_ECX), RET (no stack cleanup)
// In-place normalize: sqrt(x²+y²+z²), scale = 1/len, xyz *= scale.
// 58 bytes, pure FPU, no div-by-zero guard.
// Silicon: not directly hooked
// Status: stub
// =============================================================================
// Category 3: NTempest vector/matrix operators
// Silicon hooks these as named exports with SSE2 replacements.
// =============================================================================
// NTempest::C3Vector::Dot -- hooked
// NTempest::C3Vector::Cross -- hooked
// NTempest::C3Vector::Max -- hooked
// NTempest::C3Vector::Min -- hooked
// NTempest::C3Vector::Normalize -- hooked
// NTempest::C3Vector::SquaredMag -- hooked
// NTempest::C2Vector::SquaredMag -- hooked
// NTempest::C3Vector::operator+= -- hooked
// NTempest::C3Vector::operator*=(float) -- hooked
// NTempest::operator+(C3Vector, C3Vector) -- hooked
// NTempest::operator-(C3Vector, C3Vector) -- hooked
// NTempest::operator*(C3Vector, float) -- hooked
// NTempest::operator*(C44Matrix, C3Vector) -- hooked
// NTempest::operator*(c3vector, c44matrix) -- hooked (lowercase variant)
// NTempest::operator*(C34Matrix, C34Matrix) -- hooked
// NTempest::operator*=(C3Vector, C34Matrix) -- hooked
// NTempest::operator*=(C3Vector, C44Matrix) -- hooked
// NTempest::operator+(C44Matrix, C44Matrix) -- hooked
// NTempest::operator*(C44Matrix, float) -- hooked
// NTempest::operator*(C33Matrix, float) -- hooked
// NTempest::C44Matrix::Det -- hooked
// NTempest::C44Matrix::Scale -- hooked
// NTempest::C34Matrix::Translate -- hooked
// NTempest::CAaBox::Bounding -- hooked
//
// These are all small inline-style functions. Silicon replaces x87 with SSE2.
// WoW.exe addresses need to be resolved per-function via Ghidra xref from
// silicon's hook targets. The hook names encode the WoW addresses in some
// cases via the sub_XXXXXX pattern.
//
// Status: all stubs -- need address resolution + decompilation
// =============================================================================
// Category 4: Matrix multiply variants
// =============================================================================
// 0x7BC6A0: multiplyMatrix4x4 (SSE optimized)
// Silicon: hook_sub_7BC6A0, sub_7BC6A0 (hook_wrapper_7BC6A0)
// Status: DONE -- already in clip_sse.zig
// 0x7BAE60: MultiplyMatrix3x4
// __fastcall(out_ECX, matA_EDX, matB_stack), RET 0x4
// Affine 3x4 matrix multiply: 3x3 rotation block + translation row (indices 9-11 add B's translation)
// All x87 scalar — strong SSE candidate, regular structure, no branches
// Silicon: hook_MatrixMultiply
// Status: stub
// 0x7BB420: MultiplyMatrix3x4InPlace
// __thiscall(matrixA_ECX, matrixB_stack), RET 0x4. Returns this.
// Calls MultiplyMatrix3x4 (0x7BAE60) into stack temp, copies 48 bytes back to this.
// Silicon: hook_MatrixMultiply (shared hook)
// Status: stub
// 0x7BDFC0: multiplyMatrix3x3
// __fastcall(dst_ECX, lhs_EDX, rhs_stack), RET 0x4
// 3x3 matrix multiply: dst = lhs * rhs (9 floats each). Aliasing-safe (loads all
// before storing). All x87 scalar — strong SSE candidate.
// Silicon: hook_sub_7BDFC0, sub_7BDFC0 (no _sse2)
// Status: stub
// 0x7BCEF0: getTransposedMatrix4x4
// __thiscall(srcMatrix_ECX, dstMatrix_stack), RET 0x4
// Transposes 4x4 matrix from this to dst. Uses x87 loads + integer register shuffling.
// Silicon: not directly hooked
// Status: stub
// 0x7BDB00: createAxisAngleRotationMatrix (Rodrigues' formula)
// __fastcall(outMatrix4x4_ECX, axisVec3_EDX, angle_stack, isNormalized_stack), RET 0x8
// Builds 4x4 rotation matrix from axis+angle. Normalizes axis if flag==0.
// 310 bytes, bottom row = [0,0,0,1]. Returns outMatrix ptr.
// Related: rotateMatrixByAxisAngle in clip_sse.zig calls multiplyMatrix4x4 after this
// Silicon: hook_sub_7BDB00
// Status: stub
// =============================================================================
// Category 5: Collision / spatial / terrain
// =============================================================================
// 0x632830: RayPolygonIntersectionTest
// __fastcall(planes_ECX, ray_EDX, verts_stack, index_stack, maxDist_stack), RET 0xC
// Iterates polygon planes (count at ECX+0xF0, stride 0xC), calls CalculateDistanceToPlane
// per plane. Tracks min positive distance. Returns 1 (hit) / 0 (miss).
// Silicon: hook_sub_632830, sub_632830_sse2
// Status: stub
// 0x6329E0: CalculateDistanceToPlane (actually ray-plane intersection distance)
// __fastcall(point_ECX, plane{nx,ny,nz,d}_EDX, direction_stack), RET 0x4
// Returns (dot(point,normal)+d) / dot(direction,normal) via x87 ST(0)
// Early-exits if ray parallel (abs(dot2) < epsilon at 0x8029d4)
// 77 bytes, pure x87 math — excellent SSE2 candidate, called in hot loop
// Silicon: hook_sub_6329E0
// Status: stub
// 0x632700: (already in clip_sse.zig as rayTriangleIntersection)
// Silicon: hook_sub_632700, sub_632700_sse2
// Status: DONE
// 0x632460: BuildTrianglePlanes (already in clip_sse.zig)
// Silicon: hook_sub_632460
// Status: DONE
// 0x6318C0: ClipPolygonToSinglePlane (already in clip_sse.zig)
// Silicon: hook_sub_6318C0, sub_6318C0_sse2
// Status: DONE
// 0x632F80: CalculateTrianglePlanesFromVertices
// __fastcall(vertexBuffer_ECX, vertexIndices_EDX, planeNormal_stack, offsetVector_stack, outputPlanes_stack), RET 0xC
// Builds 5 clipping planes from extruded triangle: 4 side planes + 1 cap plane.
// 636 bytes. Indexes vertices by byte values (stride 0xC), cross products, plane flipping.
// Silicon: hook_sub_632F80, sub_632F80_sse2
// Status: stub
// 0x6335D0: testPointTriangleCollision
// __fastcall(triangleIndex_ECX, point_EDX), RET (no stack cleanup, 2 reg params only)
// Tests point against 3 half-planes built from triangle data at g_collisionMeshTriangleData (0xc4e534).
// Triangle stride 0x34. Returns 1 (inside) / 0 (outside). Epsilon at 0x80e004.
// Silicon: hook_sub_6335D0
// Status: stub
// 0x681B50: ProjectVerticesAndUpdateDepth
// __fastcall(vertexBasePtr_ECX, indexArray_EDX, vertexCount_stack, worldPos_stack, ???_stack), RET 0xC
// Transforms vertices through view-proj matrix (0xC7B700), perspective divides,
// updates 320-column depth/occlusion buffer at 0xC7B750. Paired with FrustumCullBoundingBox.
// Silicon: hook_sub_681B50
// Status: stub
// 0x686C20: ClassifyPointAgainstFrustum (not GetLinkedListHead)
// __thiscall(frustumPlanes_ECX, pointVec3_stack, outBitmask_stack), RET 0x8
// Classifies 3D point against 6 frustum planes, produces 6-bit outcode bitmask.
// Each bit = point behind that plane. Cohen-Sutherland style clipping.
// Silicon: hook_sub_686C20
// Status: stub
// 0x6856C0: ValidateGameObject (frustum cull + render setup)
// __fastcall(objPtr_ECX, posBounds_EDX), RET (no stack cleanup)
// Iterates linked list of WMO groups, performs IsSphereInFrustum + FrustumCullBoundingBox,
// sets up camera-relative transforms for visible objects, registers for rendering.
// Silicon: hook_sub_6856C0
// Status: stub
// 0x686000: FrustumCullBoundingBox
// __fastcall(bbox_ECX, flags_EDX, float_stack), RET 0x4
// Transforms bbox through view-proj matrix (0xc7b700), projects to screen-space
// column indices (320-column occlusion buffer at 0xc7b750). Returns 0 (culled) / 2 (visible).
// Complex: transforms + loops + global state. High-value (per-object per-frame).
// Silicon: hook_sub_686000, sub_686000_sse2 (via sub_6B8B70_sse2?)
// Status: stub
// 0x686180: FrustumCullBoundingBox (8-corner AABB variant)
// __fastcall(bbox_ECX, flags_EDX), RET (no stack cleanup, 2 reg params only)
// Tests all 8 AABB corners via lookup tables (0x868628/48/68), transforms through
// view-proj matrix (0xC7B700), perspective divides, checks 320-column occlusion buffer.
// Returns 0 (visible) / 2 (fully occluded). Software occlusion culling (horizon buffer).
// Silicon: hook_sub_686180
// Status: stub
// 0x6DC5A0: CheckBoxLineIntersection
// __fastcall(boxMin_ECX, lineStart_EDX, lineEnd_stack), RET 0x4
// Slab-method AABB-line intersection test. Box is 6 floats (min+max at +0xC).
// Iterates 3 axes, computes intersection t for each slab. Returns 1 (hit) / 0 (miss).
// Silicon: hook_sub_6DC5A0
// Status: stub
// 0x69BFF0: CMap::VectorIntersect
// __fastcall(p0_ECX, p1_EDX, hitPoint_stack, dist_stack, flags_stack), RET 0xC
// Ray intersection from p0→p1 against world map. Dispatches to terrain LOS (flags & 0xF000FF)
// and/or ADT rasterization (flags & 0xF00F0F). Interpolates hit as p0+(p1-p0)*dist.
// Silicon: hook_CWorldMath_VectorIntersectAABox2
// Status: stub
// 0x50A840: CalculateOrthonormalBasis (C3Vector::BuildOrthonormalBasis)
// __fastcall(dir_ECX, outBasis1_EDX, outBasis2_stack, outBasis3_stack), RET 0x8
// Builds orthonormal basis from direction vector. Normalizes if needed,
// constructs perpendicular via XY rotation or {1,0,0} fallback, cross product for third.
// 236 bytes. Calls SetErrorCode(0x57) on zero-length input.
// Silicon: hook_C3Vector_BuildOrthonormalBasis
// Status: stub
// =============================================================================
// Category 6: Rendering pipeline
// =============================================================================
// 0x6ABC40: processLinkedListCollision (profiled in transform44)
// __fastcall(listHead_ECX, queryBox_EDX, resultBuf_stack, flags_stack), RET 0x8
// Walks intrusive linked list (link at *ECX+4), per-node: checks flags at obj+0xC,
// copies 6-dword AABB from obj+0x14C, tests overlap against queryBox, calls
// addGeometryToBuffer on hit. 329 bytes. AABB overlap test is the SSE2 target.
// Silicon: hook_sub_6ABC40, sub_6ABC40_sse2
// Status: stub
// 0x6ABE60: processSpecialObjectsCollision
// __fastcall(listHead_ECX, bounds_EDX, geomBuf_stack, flags_stack), RET 0x8
// Linked list walk, per-node: callback via PTR_00c91f54, 6-component bbox test,
// addObjectToGeometryBuffer on hit. 322 bytes. Same AABB pattern as 0x6ABC40.
// Silicon: hook_sub_6ABE60, sub_6ABE60_sse2
// Status: stub
// 0x6AD7E0: frustumCullGeometry
// __fastcall(renderCtx_ECX, bounds_EDX, frustumPlanes_stack, ???_stack), RET 0x8
// 2D tile grid frustum culling. Builds 6-bit Cohen-Sutherland outcodes per vertex
// against 6 frustum planes (sign-bit extraction). Tests triangles, allocates visible
// geometry batches from global pools. 947 bytes. Same outcode pattern as 0x6B88E0/0x6B8C60.
// Silicon: hook_sub_6AD7E0, sub_6AD7E0_sse2
// Status: stub
// 0x6AFAD0: UpdateEntityAndChunksPositions (profiled in transform44)
// __fastcall(entityPtr_ECX), RET (no stack cleanup, 1 reg param)
// Height/position update (dot product against plane at 0xC7BCB0), render flag selection,
// fade timer accumulation, 4-chunk spatial bounds registration loop. ~770 bytes.
// SSE targets: plane dot product, center point computation from bounds.
// Silicon: hook_sub_6AFAD0_sse2
// Status: stub
// 0x6B7070: calculateWaterHeight
// __fastcall(posVec3_ECX, outHeight_EDX, threshold_stack), RET 0x4
// Water/terrain height query. Converts world pos to tile coords (origin 0x7FFAB4, scale
// 0x810D2C, 64x64 grid at 0xC96318), calls getTerrainHeightAtPoint + getFluidHeightAtPoint,
// then collision raycast. Returns 1 (hit) / 0 (miss). Orchestrator — SSE in sub-calls.
// Silicon: hook_sub_6B7070, sub_6B7070_sse2
// Status: stub
// 0x6B8B70: checkBoundingBoxIntersection (SAT test, profiled in transform44)
// __fastcall(bbox_ECX, corner1_EDX, corner2_stack, corner3_stack), RET 0x8
// Separating axis test: for each of 3 axes, checks all 3 corners against min/max bounds
// using sign-bit extraction (& 0x80000000). Returns 1 (separated) / 0 (intersecting).
// Inner loop: 6 scalar float subs — vectorizable with SUBPS + sign-bit ANDPS.
// Silicon: hook_sub_6B8B70, sub_6B8B70_sse2
// Status: stub
// 0x6B88E0: performCollisionDetection (profiled in transform44)
// __thiscall(this_ECX, ushort* keyData, float keySize), RET 0x8
// 653 bytes. Calls FindOrCreateHashEntry, builds 6-bit Cohen-Sutherland outcodes per vertex,
// then iterates triangles: if (outcode[i0]&i1&i2 & 0x3f)==0, calls ray_triangle_intersection.
// this+0x30=ray origin, this+0x48=scale, this+0x4c=best t, this+0x10=result ptr.
// SSE targets: outcode computation (6 float cmps/vertex), AABB min/max setup.
// Silicon: hook_sub_6B88E0, sub_6B88E0_sse2
// Status: stub
// 0x6B8C60: PerformSpatialCulling (profiled in transform44)
// __thiscall(this_ECX, ushort* keyData, uint keySize), RET 0x8
// 485 bytes. Same pattern as performCollisionDetection: FindOrCreateHashEntry, build
// 6-bit AABB outcodes per vertex (Cohen-Sutherland), iterate triangles checking visibility.
// Writes visible triangles to globals at 0xce26e8 / 0xcde648. Overflow at 0x2000 entries.
// SSE target: 6 float comparisons per vertex → vectorizable with packed SSE ops.
// Silicon: hook_sub_6B8C60, sub_6B8C60_sse2
// Status: stub
// 0x6BC370: SetupCylinderFrustum (BSP tree traversal for frustum culling)
// __thiscall(this_ECX, nodeIndex_stack, param2_stack, param3_stack), RET 0xC
// Recursive BSP traversal: leaf nodes call performCollisionDetection (0x6B88E0),
// interior nodes split AABB along axis, recurse children with clipped bounds.
// 16-byte BSP nodes. SSE: bound splitting + 6-float AABB copies.
// Silicon: hook_sub_6BC370, sub_6BC370_sse2
// Status: stub
// 0x6C15D0: executeRenderCommands (terrain vertex projection)
// __fastcall(chunkData_ECX), RET (no stack cleanup)
// Projects terrain chunk vertices using camera-relative offsets. Selects vertex stride
// based on +0xB8, iterates 9 vertices calling ProjectVerticesAndUpdateDepth twice.
// Silicon: hook_sub_6C15D0
// Status: stub
// =============================================================================
// Category 7: M2 model / bone transforms
// =============================================================================
// 0x714260: transformMatrix4x4 (profiled in transform44, bone_sse WIP)
// Silicon: hook_sub_714260, sub_714260 (full replacement)
// Status: WIP in bone_sse.zig
// 0x713EA0: interpKeyframe (profiled in transform44)
// Silicon: hook_sub_713EA0, sub_713EA0 (interp replacement)
// Status: evaluated -- hook overhead exceeds savings
// 0x71AE90: extractAnimationByteFromKeyframes
// __fastcall(animObj_ECX, animState_EDX, animData_stack, outBuf_stack), RET 0x8
// Looks up byte-typed keyframe value, resolves secondary track for blending when
// blend threshold at animState+0x10C is active.
// Silicon: hook_wrapper_71AE90, hook_sub_71AE90
// Status: stub
// 0x71AF20: getInterpolatedFloat
// __fastcall(sceneObj_ECX, animState_EDX, trackDef_stack, outBuf_stack), RET 0x8
// Resolves and lerps a single float keyframe value. Mode 0 = raw keyframe,
// else lerp(keyA, keyB, t). Secondary track blending support.
// Silicon: hook_wrapper_71AF20, hook_sub_71AF20
// Status: stub
// 0x71B6A0: normalizeVector3 (bone context)
// __thiscall(this_ECX, sourceVec3_stack), RET 0x4
// Copies Vec3 to this+0x24, computes magnitude, normalizes in-place.
// Skips if magnitude < epsilon (0x8029D4).
// Silicon: hook_sub_71B6A0
// Status: stub
// 0x71BC70: addVector3ToAccumulator
// __thiscall(this_ECX, vec3_stack), RET 0x4
// Adds Vec3 to translation accumulator at this+0x54, also adds scaled copy
// (global 0x81207C) into 3x3 matrix diagonal at +0x84/+0xA8/+0xCC.
// Silicon: hook_sub_71BC70
// Status: stub
// 0x71BF60: addToColorAccumulator
// __thiscall(this_ECX, colorVec3_stack), RET 0x4
// Simple Vec3 add — accumulates color/light into this+0x6C. Three float adds.
// Silicon: hook_sub_71BF60
// Status: stub
// 0x71C160: transformLightsAndPlanes
// __fastcall(sceneData_ECX), RET (no stack cleanup)
// Transforms attached light positions and clipping plane from local to world space
// using bone's 4x4 matrix at *sceneData+0x9C. Calls transformVector3ByMatrix4x4.
// Silicon: hook_sub_71C160
// Status: stub
// 0x71C2F0: calculateFrustumPlanes
// __fastcall(this_ECX), RET (no stack cleanup)
// Computes derived frustum/view-direction from bone orientation matrix.
// Copies direction from +0x3C to +0x78, normalizes, transforms through basis at +0x18.
// Silicon: hook_sub_71C2F0
// Status: stub
// 0x71C4E0: calculateSphericalHarmonics
// __thiscall(this_ECX, outCoeffs_stack), RET 0x4
// Computes 28 SH coefficients from 3x4 lighting matrix (this+0x84..+0xEC).
// Order-2 SH for RGB (3x9=27 + W=1.0). Constants at 0x8120A8-0x8120C4.
// Silicon: hook_sub_71C4E0
// Status: stub
// 0x718960: renderSceneNode (profiled in transform44)
// __thiscall(sceneObj_ECX), RET (no stack cleanup)
// ~2400 bytes. Scene graph update: light attachment transforms, particle attachment
// positioning, ribbon/emitter loop (packColor, setAlpha, rotateByAxisAngle, UpdateEmitter),
// recursive calls on children. Hot math: 4x transformVector3ByMatrix4x4, inline 3x3 matmul.
// Silicon: hook_sub_718960, sub_718960_sse2 (via sub_719370_sse2)
// Status: stub
// 0x719370: updateAnimationSystem (animation event updater)
// __thiscall(sceneObj_ECX, timeDelta_stack), RET 0x4
// 1470 bytes. Advances animation time, iterates bone chain (link via +0x114), fires scene
// callbacks at keyframe boundaries, transforms event positions via transformVector3ByMatrix4x4
// (bone matrix at +0x94, model-to-world at anim_context+0xDC). Recursive on children at +0x1DC.
// Silicon: hook_sub_719370, sub_719370_sse2
// Status: stub
// 0x712D50: GetTransformedAnimationPosition
// __thiscall(this_ECX, outPosition_stack, animId_stack), RET 0x8
// Resolves attachment position in world space: looks up animation index, gets bone matrix
// (boneIdx * 0x40 + this+0x94), two transformVector3ByMatrix4x4 calls (bone→world).
// SSE win is entirely in the two transform calls (0x7BCA80).
// Silicon: hook_sub_712D50, sub_712D50_sse2
// Status: stub
// 0x713680: GetBoundingSphere
// __thiscall(this_ECX, outSphere_stack), RET 0x4
// Reads M2 bounding box (header+0xB4, 6 floats min/max), computes center = (min+max)*0.5,
// reads radius at header+0xCC. Writes {cx,cy,cz,r} to outSphere. Trivial SSE:
// load min+max as packed, ADDPS, MULPS by 0.5, store.
// Silicon: hook_sub_713680, sub_713680_sse2
// Status: stub
// =============================================================================
// Category 8: Particle / effect rendering
// =============================================================================
// 0x7B2A50: RenderParticleSprites (profiled in transform44)
// __thiscall(this_ECX, particleData_stack, vertexBuffers_stack), RET 0x8
// Core particle sprite renderer. Per-particle: depth culling, color/scale calc,
// world matrix transform, texture atlas UV, rotation via sin/cos, builds 4 corner
// vertices with pos/color/UV. Large function (~400+ lines).
// Silicon: sub_7B2A50_sse2 (via hook_sub_7B4BF0?)
// Status: stub
// 0x7B3A10: RenderParticleSystemSorted
// __thiscall(this_ECX, renderContext_stack), RET 0x4
// 573 bytes. Unsorted path: direct RenderParticleSprites loop. Sorted path (flag 0x10
// at this+0x1AC): computes per-particle depth via dot product with view matrix row
// (0xCF5B78), max-heap sort, renders back-to-front. SSE: depth dot product.
// Silicon: hook_sub_7B3A10, sub_7B3A10_sse2
// Status: stub
// 0x7B4BF0: SetParticleLifetime
// __thiscall(this_ECX, lifetime_stack), RET 0x4
// Sets particle lifetime at this+0xA8. Clamps to zero below collision threshold.
// Silicon: hook_sub_7B4BF0
// Status: stub
// 0x7B5A10: ProcessActiveParticles (profiled in transform44)
// __thiscall(this_ECX, float deltaTime, int param2), RET 0x8
// ~560 bytes. Particle system tick: emit if param2==0, iterate active particles
// (pool at +0x3C/+0x4C, indices at +0x5C, count at +0x64), accumulate lifetime,
// kill expired (vtable+0xC), call UpdateParticlePhysics/Rotation, propagate to
// child emitters at +0x80. Hot inner loop with physics updates = SSE target.
// Silicon: hook_sub_7B5A10, sub_7B5A10_sse2
// Status: stub
// 0x7B76C0: applyParticleWorldTransform
// __thiscall(this_ECX, matrixData_stack, translationVec_stack, additionalTransform_stack), RET 0xC
// Applies world transform + translation to particle emitter. Copies 4x4 matrix,
// optionally multiplies by inverse additional transform. Handles first-frame vs interp.
// Silicon: hook_sub_7B76C0
// Status: stub
// 0x7B7A80: packParticleColorToBytes
// __fastcall(obj_ECX, floatR_stack, floatG_stack, floatB_stack), RET 0xC
// Reads alpha byte at obj+0x12F, multiplies by 255. Packs ARGB into u32 at obj+0x12C.
// NOTE: floats are normal stack params (FLD [EBP+0x8] etc), NOT FPU register params.
// Silicon: hook_sub_7B7A80
// Status: stub
// 0x7B7B10: setParticleAlphaFromFloat
// __fastcall(obj_ECX, floatAlpha_stack), RET 0x4
// Multiplies float by 255.0, rounds, stores as byte at obj+0x12F.
// Normal stack float param, not FPU register.
// Silicon: hook_sub_7B7B10
// Status: stub
// 0x7B7E60: AdvanceParticles
// __thiscall(this_ECX, deltaTime_stack, param2_stack), RET 0x8
// Main per-frame particle update. Clamps dt to [0, this+0xF8], removes expired
// particles, spawns new via InterpolateParticlePosition, advances live particles
// (gravity at +0x164, aging, color/size interpolation). 0x28-byte per-particle array.
// Silicon: hook_sub_7B7E60
// Status: stub
// 0x7B8890: GenerateSphereParticle
// __thiscall(this_ECX, particleOut_stack, speed_stack, transformMatrix_stack), RET 0xC
// Generates particle with random spherical distribution. RNG via CryptoStateUpdate
// at this+0x2C. Ellipsoid radii at +0x290/0x294, optional aim at +0x188.
// Silicon: hook_sub_7B8890
// Status: stub
// 0x7B8D70: updateRibbonParticle
// __thiscall(this_ECX, particleOut_stack, speed_stack, transformMatrix_stack), RET 0xC
// Generates ribbon/trail particle with random spherical position. Similar to
// GenerateSphereParticle but distinct angle parameterization. Flag 0x4000 = up-axis mode.
// Silicon: hook_sub_7B8D70
// Status: stub
// 0x7BA200: generateRandomParticle (plane/spray emitter type)
// __thiscall(this_ECX, particleOut_stack, lifetime_stack, originPos_stack), RET 0xC
// Sets initial position from remaining lifetime distance (this+0x14), computes velocity
// from random yaw/pitch angles (+0x24/+0x28), applies physics, stores scale at +0x20.
// Silicon: hook_sub_7BA200
// Status: stub
// 0x7BB860: CreateRotationMatrix (3x4 Rodrigues)
// __fastcall(outMatrix_ECX, axisVec_EDX, angle_stack, isNorm_stack), RET 0x8
// Builds 3x4 rotation matrix from axis+angle. Normalizes if flag==0. Returns ECX.
// Similar to 0x7BDB00 (4x4 version) and 0x7BE490 (3x3 version).
// Silicon: hook_sub_7BB860
// Status: stub
// =============================================================================
// Category 9: Matrix/geometry math (7Bxxxx-7Cxxxx range)
// =============================================================================
// 0x7BCA80: transformVector3ByMatrix4x4
// __fastcall(outVec3_ECX, inVec3_EDX, matrix_stack), RET 0x4
// Affine position transform: multiplies 3x3 rotation + adds translation column.
// Called by many other silicon hooks. Core SSE target.
// Silicon: hook_sub_7BCA80
// Status: stub
// 0x7BCB40: transformVector4ByMatrix4x4
// __fastcall(outVec4_ECX, inVec4_EDX, matrix_stack), RET 0x4
// Full 4-component vector-matrix multiply (includes W, no implicit translation add).
// Silicon: hook_sub_7BCB40
// Status: stub
// 0x7BDC40: ApplyTranslationMatrix
// __thiscall(matrix_ECX, vec3_stack), RET 0x4
// In-place: multiplies 3x3 rotation by translation vec, adds to elements [12-14].
// Silicon: hook_sub_7BDC40
// Status: stub
// 0x7BDCA0: scaleMatrix3x3ByVector
// __thiscall(matrix_ECX, scaleVec3_stack), RET 0x4
// In-place scale of 3x3 submatrix: row0 *= s.x, row1 *= s.y, row2 *= s.z.
// Silicon: hook_sub_7BDCA0
// Status: stub
// 0x7BDDB0: rotateMatrixByQuaternion
// __thiscall(matrix_ECX, quat_stack), RET 0x4
// Builds rotation matrix from quaternion, multiplies with existing matrix via
// multiplyMatrix4x4, stores back. Allocates 0x9C bytes stack for temps.
// Silicon: hook_sub_7BDDB0
// Status: stub
// 0x7BE490: createAxisAngleRotationMatrix3x3
// __fastcall(outMat3x3_ECX, axisVec3_EDX, angle_stack, isNorm_stack), RET 0x8
// Rodrigues' formula for 3x3 rotation matrix. Returns ECX. Normalizes axis if flag==0.
// Silicon: hook_sub_7BE490
// Status: stub
// 0x7BE5B0: createZRotationMatrix3x3
// __thiscall(outMat3x3_ECX, angle_stack), RET 0x4. Returns this.
// Builds 3x3 Z-axis rotation matrix via FSINCOS: [cos,sin,0,-sin,cos,0,0,0,1].
// Status: stub
//
// 0x7BE5B0-7BF7B0: series of related axis rotation matrices
// Silicon hooks all of these individually
// Status: remaining in series need decompilation
// 0x7C0570: quaternion_slerp
// __fastcall(outQuat_ECX, quatA_EDX, t_stack, quatB_stack), RET 0x8
// Spherical linear interpolation between two quaternions. Handles negative dot
// (shortest path), fallback to direct copy when sin(angle) near zero.
// Silicon: hook_sub_7C0570
// Status: stub
// 0x7C2040: point_sphere_collision_test (sphere-AABB overlap)
// __fastcall(aabb_ECX, sphere_EDX, testMode_stack), RET 0x4
// Tests point/sphere against AABB with 4 modes (switch on param3): full overlap,
// partial containment, partial overlap (min<r^2<max), outside-only. Accumulates
// squared distances per axis. Returns 1 (collision) / 0 (miss). Good SSE candidate.
// Silicon: hook_sub_7C2040, sub_7C2040_sse2
// Status: stub
// 0x7C22B0: ray_plane_intersection
// __fastcall(ray6f_ECX, plane4f_EDX, outT_stack, outHitPt_stack, epsilon_stack), RET 0xC
// Standard ray-plane intersection: t = -(dot(origin,normal)+d) / dot(dir,normal).
// Handles parallel rays (dot near zero). Outputs nullable t and hit point.
// Returns 1 (hit) / 0 (miss). Uses double-precision epsilon at 0x811658.
// Silicon: hook_sub_7C22B0, sub_7C22B0_sse2
// Status: stub
// 0x7C29F0: ray_triangle_intersection (already in clip_sse.zig)
// Silicon: hook_sub_7C29F0, sub_7C29F0_sse2
// Status: DONE
// 0x7C5880: calculate_animation_orientation
// __fastcall(animStruct_ECX), RET (no stack cleanup)
// Computes sin/cos of facing angle at +0x50, stores at +0x68/0x6c.
// If flag 0x200000 at +0x40 and pitch at +0x54 non-trivial, computes combined
// facing+pitch orientation at +0x5c/0x60/0x64.
// Silicon: hook_sub_7C5880
// Status: stub
// =============================================================================
// Category 10: Lighting
// =============================================================================
// 0x76D680: SetupModelLighting
// __fastcall(ptr_ECX, lightingCtx_EDX, sceneObj_stack), RET 0x4
// Configures lighting on model. If flag bit 0 at sceneObj+0x3A4, converts RGB byte
// color to float, clamps, calls setupCameraParameters with light direction.
// Silicon: hook_CM2Lighting_SetupSunlight, etc.
// Status: stub
// 0x7786A0: SetModelLighting (actually UI model constructor/initializer)
// __thiscall(this_ECX, initParam_stack), RET 0x4. Returns this.
// Constructor for ~0x4D8 byte UI model object. Sets up vtables (0x81C7F8, 0x81C7C8),
// initializes sub-objects at +0x33C/+0x3B8/+0x434, sets LOD=2. Ghidra mislabel.
// Silicon: hook_CM2Lighting_AddLight, AddDiffuse, SetupGxLights
// Status: stub
// =============================================================================
// Category 11: Movement / spline
// =============================================================================
// Silicon hooks: CMovement_SetOrientation, CMovement_PlotUnitSplineMovement
// Silicon hooks: CParticleKey_Interpolate
// Silicon hooks: NTempest C3Spline EvaluateDer1, ArclengthSegT, CatmullRom variants
// Status: all stubs -- spline math could benefit from SSE
// =============================================================================
// Category 12: Misc
// =============================================================================
// Silicon hooks: CalculateFacingTo, EvaluatePolynomial, InterpTable
// Silicon hooks: TransformAABox, SetArgbIntensity
// Silicon hooks: DNClouds_Update, DNClouds_BumpMap
// Silicon hooks: GxTexUpdate
// Silicon hooks: M2HeapSort
// Silicon hooks: SMemAlloc, SMemFree (allocation wrappers)
// Silicon hooks: hook_CWFrustum_Cull, hook_CMapObj_TestBounds
// Status: all stubs
// =============================================================================
// Category 13: Early-address hooks (0x40xxxx-0x5xxxxx range)
// =============================================================================
// 0x41AE40-41AE70: coordinate scaling (NDC<->pixel)
// All four: __stdcall(float), RET 0x4, returns float via x87 ST(0). 16 bytes each.
// 0x41AE40: param / 0.8 (global at 0x832a44)
// 0x41AE50: param / 0.6 (global at 0x832a48)
// 0x41AE60: 0.8 * param (inverse of 0x41AE40)
// 0x41AE70: 0.6 * param (inverse of 0x41AE50)
// Trivial — single MULSS/DIVSS each. Hook as fn(f32) callconv(.stdcall) f32.
// Silicon: hook_sub_41ae40/50/60/70, sub_41ae40_sse2 etc.
// Status: stubs
// 0x453480: AudioCallback_SetVolume
// __thiscall(this_ECX, float volume, ptr param2, int param3), RET 0xC
// Clamps volume to [0.0, 1.0] via x87 FCOMP, dispatches vtable[0x18] if param3==1.
// 74 bytes. SSE2: replace FLD/FCOMP/FNSTSW with COMISS/MAXSS/MINSS. Minimal savings.
// Silicon: hook_sub_453480, sub_453480_sse2
// Status: stub
// 0x453580: Vector3_InterpolateWeighted (cubic B-spline Vec3 evaluator)
// __thiscall(this_ECX, int startIndex, float* weights, uint knotCount, float* outVec3), RET 0x10
// 4 iterations, each calling Math_PowerFunction (0x453620, Horner polynomial, 32 bytes).
// 145 bytes. Inner loop: 3 FLD/FMUL/FADD per iter. Straightforward SSE2.
// Silicon: hook_sub_453580, sub_453580_sse2
// Status: stub
// 0x4541B0: interpolateVector3 (Vec3 lerp / weighted interpolation)
// __thiscall(this_ECX, int keyframeIdx, float* t, float* outVec3), RET 0xC
// Linear path (this+0x28==0): out = key[n] + (key[n+1] - key[n]) * t. Pure x87 lerp.
// Weighted path: delegates to Vector3_InterpolateWeighted with static table at 0xb05e10.
// Silicon: hook_sub_4541B0, sub_4541B0_sse2
// Status: stub
// 0x483340: UpdateLightingData
// __fastcall(ECX=unused, outBuf_EDX, guid_lo_stack, guid_hi_stack, param5_stack), RET 0xC
// Resolves object by GUID via ClntObjMgrObjectPtr, copies 6 dwords from obj+0x98 to outBuf,
// computes lighting scale (obj+0x24/25/26/27), copies 64 bytes via vtable[0x64].
// Returns EAX: 1 (success) / 0 (failure). Not pure math — mixed logic + data copy.
// Silicon: hook_sub_483340, sub_483340_sse2
// Status: stub
// 0x509220: CheckBoundingBoxCollision
// __fastcall(index_ECX, boxA_EDX, axis_stack, outDepth_stack), RET 0x8
// AABB overlap test against global array at 0xbe0b8c (indexed by ECX*16, entries: count+0x4,
// box_array+0x8, 16-byte stride). 4 x87 float comparisons per entry, switch on axis for
// penetration depth. 197 bytes. SSE2: CMPPS/MOVMSKPS for all 4 comparisons at once.
// Silicon: hook_sub_509220, sub_509220_sse2
// Status: stub
// 0x509BF0: ClampBounds
// __fastcall(outRect_ECX, mode_EDX, rect4f_stack), RET 0x10
// Clamps bounding rect to visible screen/map bounds. If mode==0, shifts rect to fit.
// Returns bitmask of out-of-bounds edges.
// Silicon: hook_sub_509bf0, sub_509bf0_sse2
// Status: stub
// 0x5E22D0: CheckPlayerInTriggerZone
// __fastcall(triggerZone_ECX, posVec3_EDX), RET (no stack cleanup, 2 reg params)
// Tests if position is inside trigger zone. Supports sphere (radius at +0x14) and
// oriented box (dims at +0x18-0x20, rotation at +0x24). Returns 1 (inside) / 0 (outside).
// Silicon: hook_sub_5E22D0
// Status: stub
// 0x593040: VertexData_UpdateRenderState
// __thiscall(this_ECX, renderData_stack), RET 0x4
// Updates vertex render state (~0x54 bytes) from source struct. Unpacks RGBA from
// packed u32s scaled by float intensity. Sets dirty flags at +0x50.
// Silicon: hook_sub_593040
// Status: stub
// 0x5C7010: ConvertPixelsToScreenAlt
// __thiscall(this_ECX, coord_stack), RET 0x4
// Pixel-to-screen coordinate conversion. Multiplies by global scale factor (0xC2B9A4),
// truncates via __ftol. Returns early if this != NULL.
// Silicon: hook_sub_5C7010
// Status: stub
// 0x5C8710: RenderTextToVertexBuffer
// __thiscall(this_ECX, 9 stack params: outBuf, colorArr, posPtrs, charColor, offsetXY,
// shadowFlag, alphaFlag, startIdx, count), RET 0x24
// Renders text glyphs into vertex buffer. Reads glyph geometry from font (this+0x10,
// 20 bytes/glyph), applies position, optional 3D rotation, alpha blending. 24-byte vertices.
// Silicon: hook_sub_5C8710, sub_5C8710_sse2
// Status: stub
// 0x5F6280: InterpolateSpellPosition
// __thiscall(this_ECX, outVec3_stack, timeOffset_stack), RET 0x8
// Interpolates spell projectile world position along waypoint path (0x1C bytes/waypoint).
// Applies parent object's quaternion rotation (descriptor +0x110). Triggers animation
// changes at waypoint boundaries.
// Silicon: hook_sub_5F6280, sub_5F6280_sse2
// Status: stub
// 0x5F8DC0: CalculateRenderingBounds (LOD selection)
// __thiscall(this_ECX, 5 stack params), RET 0x14
// Determines LOD level from distance: compares against this+0x18/this+0x20 ratio.
// Returns status 0xA2 (near), 0xA3 (transition), 0xA4 (far/culled).
// Silicon: hook_sub_5F8DC0
// Status: stub
// 0x614CD0: UpdateObjectTransformWithInterpolation
// __fastcall(objPtr_ECX, ???_stack), RET 0x4
// Updates game object's world transform with interpolated position and rotation.
// Reads current pos via vtable+0x20, interpolates toward target at +0x104-0x10C.
// Silicon: hook_sub_614CD0, sub_614CD0_sse2
// Status: stub
// 0x616AF0: ValidatePositionUpdate
// __thiscall(this_ECX, deltaTime_stack, flags_stack, posVec3_stack), RET 0xC
// Validates movement update. Calls update_object_animation_state, checks movement
// flags at +0x40, computes squared distance, rejects if exceeds velocity-scaled threshold.
// Silicon: hook_sub_616AF0, sub_616AF0_sse2
// Status: stub
// 0x616BF0: ValidateCoordinateBounds
// __fastcall(objPtr_ECX), RET (no stack cleanup)
// Validates XYZ position at +0x10/+0x14/+0x18 are valid floats (IsValidFloat)
// and within loaded map bounds. Returns 1 (valid) / 0 (invalid).
// Silicon: hook_sub_616BF0
// Status: stub
// 0x618920: GetUnitPositionBufferIfValid
// __fastcall(unitPtr_ECX), RET (no stack cleanup)
// Checks if float at +0x148 is nonzero; returns ptr to +0x144 (position buffer)
// or 0. Very small — essentially "has valid height" check.
// Silicon: hook_sub_618920
// Status: stub
// =============================================================================
// Category 14: Terrain / world rendering (0x67xxxx-0x6Fxxxx)
// =============================================================================
// 0x675AC0: Weather_ProcessEnvironment
// __fastcall(weatherCtx_ECX), RET (no stack cleanup)
// Large weather particle renderer. Sets up render states (blend, alpha, texture),
// iterates weather particle groups, builds billboard triangle-strip vertex buffers
// with distance-based opacity, draws with DrawPrimitive.
// Silicon: hook_sub_675AC0
// Status: stub
// 0x67C820: GetCachedHeightValue (terrain height cache)
// __thiscall(this_ECX, coordPair_stack), RET 0x4
// 32x32 float cache at this+0x0, grid origin at this+0x1000/0x1004. On cache miss calls
// SampleHeightAtPosition. 218 bytes. x87 FILD/FMUL/FSUB/FCOMP → SSE2 cvtsi2ss/mulss/comiss.
// Silicon: hook_sub_67C820, sub_67C820_sse2
// Status: stub
// 0x67CA00: GetHeightFromCachedMap (wrapper for GetCachedHeightValue)
// __thiscall(this_ECX, coordPtr_stack), RET 0x4
// Scales coords, bounds-checks 5-tile grid, dispatches to GetCachedHeightValue. 133 bytes.
// Silicon: hook_sub_67CA00, sub_67CA00_sse2
// Status: stub
// 0x67CA90: SampleAndCacheHeightData
// __thiscall(this_ECX, gridCoords_stack, outBuf_stack), RET 0x8
// Computes world-space center of grid cell, looks up cached height. 204 bytes.
// Silicon: hook_sub_67CA90, sub_67CA90_sse2
// Status: stub
// 0x67CB60: RaycastLineWithHeightCheck (terrain line-of-sight)
// __thiscall(this_ECX, startPt_stack, endPt_stack, resultPt_stack), RET 0xC
// Bresenham-style line walk across height grid, samples terrain height per cell.
// Returns 1 (hit terrain) / 0 (clear). 752 bytes. Classic LoS check.
// Silicon: hook_sub_67CB60, sub_67CB60_sse2
// Status: stub
// 0x6818B0: AddObjectToSpatialList (spatial grid insertion)
// __fastcall(objectPtr_ECX, posVec3_EDX), RET (no stack cleanup, 2 reg params)
// 186 bytes. Computes 1D grid index from 3D position via dot product with projection
// vector (0xC7CFB8), scales/truncates, clamps to [0,31]. Inserts into doubly-linked
// list at grid bucket (0xC7BD4C, stride 0x6C). SSE: 3-component dot + scale + cvt.
// Silicon: hook_sub_6818B0, sub_6818B0_sse2
// Status: stub
// 0x683F80: AllocateGameObject (doodad visibility + LOD)
// __stdcall(), RET (no stack cleanup, no params)
// Iterates global doodad list (PTR_00c7caec), calculates camera distance with LOD-based
// fade, performs visibility culling, registers visible objects for rendering.
// Not pure math — orchestrator function.
// Silicon: hook_sub_683F80, sub_683F80_sse2
// Status: stub
// 0x6869C0: TestOrientedBoundingBoxAgainstFrustum
// __thiscall(frustumPlanes_ECX, aabb_stack, rotMatrix_stack, transVec_stack), RET 0xC
// Tests OBB against 6 frustum planes. Transforms AABB corners by rotation + translation,
// dot-products against each plane. Returns 0 (rejected) / 3 (fully inside).
// Silicon: hook_sub_6869C0, sub_6869C0_sse2
// Status: stub
// 0x686B80: TestSphereAgainstFrustum
// __thiscall(frustumPlanes_ECX, sphere_stack), RET 0x4
// Tests bounding sphere (xyz center + radius at [3]) against 6 frustum planes.
// Returns 0 (fully outside) / 3 (inside). Classic sphere-frustum cull.
// Silicon: hook_sub_686B80
// Status: stub
// 0x68B0D0: CalculateDetailDistances
// __thiscall(this_ECX, pos_stack, flags_stack, outDeltas_stack, outDists_stack, param5_stack), RET 0x14
// 603 bytes. Iterates 16x16 terrain tile grid, walks 4 sub-chunks each with 8x8 detail
// grid. Computes squared distances to nearest instance of each detail type (up to 15).
// Tile grid at this+0x278. Heavy distance computation loop — SSE candidate.
// Silicon: hook_sub_68B0D0, sub_68B0D0_sse2
// Status: stub
// 0x68D540: CalculateChunkVertices
// __fastcall(chunkPtr_ECX), RET (no stack cleanup, 1 reg param)
// 329 bytes. Generates 9x9 vertex grid (81 vertices) for terrain chunk. Reads height
// data from [ECX+0xC] (8 bytes/entry, float at +4), computes world-space positions
// using scale (0x80FEE4) and origin (0x7FFAB4). Output at ECX+0x34 (Vec3 x 81).
// Silicon: hook_sub_68D540, sub_68D540_sse2
// Status: stub
// 0x699330: IsPointInsideBounds
// __fastcall(vec3A_ECX, vec3B_EDX), RET (no stack cleanup)
// Component-wise comparison: returns 1 if all components of B <= A. Bounding box
// minimum-corner containment test.
// Silicon: hook_sub_699330
// Status: stub
// 0x69B1C0: GetTerrainDataAtPosition
// __fastcall(posVec3_ECX, outPtr_EDX), RET (no stack cleanup)
// Looks up terrain chunk data via ADT grid (PTR_00c96318), navigates tile/subtile
// hierarchy. Returns terrain data pointer via param_2. Returns 1/0.
// Silicon: hook_sub_69B1C0
// Status: stub
// 0x69B6D0: GetWaterSurfaceData
// __fastcall(pointVec3_ECX, liquidType_EDX, surfaceH_stack, waterX_stack, waterDir_stack), RET 0xC
// Queries liquid surface data at world position: type, height, flow velocity/direction.
// Handles ocean (type 1), river/lake interpolation, up to 4 liquid layers.
// Silicon: hook_sub_69B6D0
// Status: stub
// 0x6A8050: SetupRenderingTransforms
// __thiscall(this_ECX, renderContext_stack), RET 0x4
// Sets up world object rendering transforms (position/rotation/scale). Applies from
// object's transform data or byte-packed orientation, recursively processes children.
// Silicon: hook_sub_6A8050
// Status: stub
// 0x6AADC0: processTerrainChunkMeshGeneration
// __fastcall(chunkX_ECX, chunkY_EDX, param3-6_stack), RET 0x10
// Generates collision mesh triangles for terrain chunk. Iterates vertices within
// bounding box, constructs triangles with normals. Bitmask controls collision layers
// (terrain 0xf00, liquid 0xf0000, objects 0xf00000).
// Silicon: hook_sub_6AADC0
// Status: stub
// 0x6CF6C0: interpolateKeyframes
// __fastcall(keyframeArray_ECX, count_EDX, t_float_stack), RET 0x4
// 3 params (not 4). Keyframe lerp: clamps t to [0,1], finds bracketing pair in
// {time,value}[count] array (8 bytes/entry), linearly interpolates. Returns f32 via x87.
// Silicon: hook_sub_6CF6C0
// Status: stub
// 0x6FA1A0: lua_table_hash_index
// __fastcall(table_ECX, key_EDX), RET (no stack cleanup)
// Computes Lua table hash bucket index. Dispatches by key type: boolean, string
// (bitmasked hash), number (lua_table_hash_double), default (modulo).
// Silicon: hook_sub_6FA1A0
// Status: stub
// 0x6FA700: lua_table_get_array_element
// __fastcall(table_ECX, intKey_EDX), RET (no stack cleanup)
// Gets value from Lua table by integer key. Direct array access if within sizearray,
// else falls back to hash lookup via lua_table_hash_double.
// Silicon: hook_sub_6FA700, sub_6FA700_sse2
// Status: stub
// 0x6FA7A0: lua_table_lookup_key
// __fastcall(table_ECX, keyPtr_EDX), RET (no stack cleanup)
// Generic Lua table key lookup dispatcher. Number keys → array access, string keys →
// hash element, other types → generic hash search.
// Silicon: hook_sub_6FA7A0
// Status: stub
// 0x70A060: compareRayHitData (sort comparator)
// __fastcall(hitIdxA_ECX, hitIdxB_EDX, hitArrayBase_stack), RET 0x4
// Compares ray intersection hits by distance (+4), secondary float (+8), then index.
// Each hit entry is 0x10 bytes. Returns -1/0/1.
// Silicon: hook_sub_70A060, sub_70A060_sse2
// Status: stub
// 0x70AE10: compareRenderItemsExtended (sort comparator)
// __fastcall(itemIdxA_ECX, itemIdxB_EDX, renderCtx_stack), RET 0x4
// Multi-key render item sort: layer, depth, material, texture, distance, batch key,
// sub-mesh index. Items are 0x40 bytes each.
// Silicon: hook_sub_70AE10, sub_70AE10_sse2
// Status: stub
// =============================================================================
// Probe hooks — call-counting detours to verify functions are actually called
// =============================================================================
// Calling convention shorthands
const FC = hook.cc.fastcall;
const TC = hook.cc.thiscall;
const SC = hook.cc.stdcall;
// Function type aliases — {CC}{paramCount}{return}: r=u32, v=void, d=f64(x87)
const FC1v = fn (u32) callconv(FC) void;
const FC1r = fn (u32) callconv(FC) u32;
const FC2v = fn (u32, u32) callconv(FC) void;
const FC2r = fn (u32, u32) callconv(FC) u32;
const FC3v = fn (u32, u32, u32) callconv(FC) void;
const FC3r = fn (u32, u32, u32) callconv(FC) u32;
const FC2d = fn (u32, u32) callconv(FC) f64;
const FC3d = fn (u32, u32, u32) callconv(FC) f64;
const FC4r = fn (u32, u32, u32, u32) callconv(FC) u32;
const FC4d = fn (u32, u32, u32, u32) callconv(FC) f64;
const FC5v = fn (u32, u32, u32, u32, u32) callconv(FC) void;
const FC5r = fn (u32, u32, u32, u32, u32) callconv(FC) u32;
const FC6r = fn (u32, u32, u32, u32, u32, u32) callconv(FC) u32;
const TC1v = fn (u32) callconv(TC) void;
const TC1r = fn (u32) callconv(TC) u32;
const TC2v = fn (u32, u32) callconv(TC) void;
const TC2r = fn (u32, u32) callconv(TC) u32;
const TC2d = fn (u32, u32) callconv(TC) f64;
const TC3v = fn (u32, u32, u32) callconv(TC) void;
const TC3r = fn (u32, u32, u32) callconv(TC) u32;
const TC4v = fn (u32, u32, u32, u32) callconv(TC) void;
const TC4r = fn (u32, u32, u32, u32) callconv(TC) u32;
const TC5v = fn (u32, u32, u32, u32, u32) callconv(TC) void;
const TC6v = fn (u32, u32, u32, u32, u32, u32) callconv(TC) void;
const SC0v = fn () callconv(SC) void;
const SC1d = fn (u32) callconv(SC) f64;
const SC2r = fn (u32, u32) callconv(SC) u32;
const SC3v = fn (u32, u32, u32) callconv(SC) void;
const FC4v = fn (u32, u32, u32, u32) callconv(FC) void;
// __cdecl for CRT functions (caller cleanup)
const CD0r = fn () callconv(hook.cc.cdecl) u32; // ftol: no params, returns EAX (ST(0) implicit)
// =============================================================================
// SSE math replacements — ordered by call frequency (highest first)
// =============================================================================
// --- transformVector3ByMatrix4x4 (10.8M calls/7.5s) ---
// __fastcall(outVec3_ECX, inVec3_EDX, matrix_stack), RET 0x4
// Affine: out[i] = dot(vec4(in, 1.0), matrix_row[i])
fn sseTransformVec3Mat4(out: u32, in_vec: u32, mat: u32) callconv(FC) u32 {
const dst: [*]f32 = @ptrFromInt(out);
const src: [*]const f32 = @ptrFromInt(in_vec);
const m: [*]const f32 = @ptrFromInt(mat);
const v: @Vector(4, f32) = .{ src[0], src[1], src[2], 1.0 };
const r0: @Vector(4, f32) = .{ m[0], m[1], m[2], m[3] };
const r1: @Vector(4, f32) = .{ m[4], m[5], m[6], m[7] };
const r2: @Vector(4, f32) = .{ m[8], m[9], m[10], m[11] };
dst[0] = @reduce(.Add, v * r0);
dst[1] = @reduce(.Add, v * r1);
dst[2] = @reduce(.Add, v * r2);
return out;
}
// --- scaleMatrix3x3ByVector (1.3M calls/7.5s) ---
// __thiscall(matrix_ECX, scaleVec3_stack), RET 0x4
// In-place: row0 *= s.x, row1 *= s.y, row2 *= s.z
fn sseScaleMat3x3(mat: u32, scale: u32) callconv(TC) u32 {
const m: [*]f32 = @ptrFromInt(mat);
const s: [*]const f32 = @ptrFromInt(scale);
// Row 0 *= s.x
m[0] *= s[0];
m[1] *= s[0];
m[2] *= s[0];
// Row 1 *= s.y
m[3] *= s[1];
m[4] *= s[1];
m[5] *= s[1];
// Row 2 *= s.z
m[6] *= s[2];
m[7] *= s[2];
m[8] *= s[2];
return mat;
}
// --- ClassifyPointAgainstFrustum (3.2M calls/7.5s) ---
// __thiscall(frustumPlanes_ECX, pointVec3_stack, outBitmask_stack), RET 0x8
// Tests point against 6 frustum planes, produces 6-bit outcode bitmask.
fn sseClassifyPointFrustum(planes_ptr: u32, point: u32, out_mask: u32) callconv(TC) u32 {
const p: [*]const f32 = @ptrFromInt(point);
const mask: *u32 = @ptrFromInt(out_mask);
const planes: [*]const f32 = @ptrFromInt(planes_ptr);
const px = p[0];
const py = p[1];
const pz = p[2];
var bits: u32 = 0;
// 6 planes, each is {nx, ny, nz, d} = 4 floats
inline for (0..6) |i| {
const pl = planes + i * 4;
const dist = px * pl[0] + py * pl[1] + pz * pl[2] + pl[3];
if (dist < 0) bits |= (@as(u32, 1) << @intCast(i));
}
mask.* = bits;
return planes_ptr;
}
// --- CheckBoxLineIntersection (2.7M calls/7.5s) ---
// __fastcall(boxMin_ECX, lineStart_EDX, lineEnd_stack), RET 0x4
// Slab-method AABB-line intersection. Box is min[3] at +0, max[3] at +0xC.
fn sseCheckBoxLineIntersect(box_ptr: u32, line_start: u32, line_end: u32) callconv(FC) u32 {
const bmin: [*]const f32 = @ptrFromInt(box_ptr);
const bmax: [*]const f32 = @ptrFromInt(box_ptr + 0xC);
const start: [*]const f32 = @ptrFromInt(line_start);
const end: [*]const f32 = @ptrFromInt(line_end);
var tmin: f32 = 0.0;
var tmax: f32 = 1.0;
inline for (0..3) |i| {
const dir = end[i] - start[i];
if (@abs(dir) < 1.0e-20) {
// Ray parallel to slab — check if origin is within
if (start[i] < bmin[i] or start[i] > bmax[i]) return 0;
} else {
const inv_dir = 1.0 / dir;
var t0 = (bmin[i] - start[i]) * inv_dir;
var t1 = (bmax[i] - start[i]) * inv_dir;
if (t0 > t1) {
const tmp = t0;
t0 = t1;
t1 = tmp;
}
if (t0 > tmin) tmin = t0;
if (t1 < tmax) tmax = t1;
if (tmin > tmax) return 0;
}
}
return 1;
}
// --- CalculateDistanceToPlane (525K calls/7.5s) ---
// __fastcall(point_ECX, plane_EDX, direction_stack), RET 0x4
// Returns (dot(point,normal)+d) / dot(direction,normal) via x87 ST(0)
fn sseDistanceToPlane(point: u32, plane: u32, direction: u32) callconv(FC) f64 {
const p: [*]const f32 = @ptrFromInt(point);
const pl: [*]const f32 = @ptrFromInt(plane);
const dir: [*]const f32 = @ptrFromInt(direction);
const dot1 = p[0] * pl[0] + p[1] * pl[1] + p[2] * pl[2] + pl[3];
const dot2 = dir[0] * pl[0] + dir[1] * pl[1] + dir[2] * pl[2];
if (@abs(dot2) < 1.0e-20) return 0.0;
return @floatCast(dot1 / dot2);
}
// --- normalizeVector3 (137K calls/7.5s) ---
// __thiscall(Vec3_ECX, length_stack), RET 0x4
// Divides each component by length: xyz *= 1.0/length
fn sseNormalizeVec3(vec: u32, length_bits: u32) callconv(TC) void {
const v: [*]f32 = @ptrFromInt(vec);
const length: f32 = @bitCast(length_bits);
const scale = 1.0 / length;
v[0] *= scale;
v[1] *= scale;
v[2] *= scale;
}
// --- ApplyTranslationMatrix (182K calls/7.5s) ---
// __thiscall(matrix_ECX, vec3_stack), RET 0x4
// mat[12] += dot(row0, vec), mat[13] += dot(row1, vec), mat[14] += dot(row2, vec)
fn sseApplyTranslation(mat: u32, vec: u32) callconv(TC) u32 {
const m: [*]f32 = @ptrFromInt(mat);
const v: [*]const f32 = @ptrFromInt(vec);
m[12] += m[0] * v[0] + m[4] * v[1] + m[8] * v[2];
m[13] += m[1] * v[0] + m[5] * v[1] + m[9] * v[2];
m[14] += m[2] * v[0] + m[6] * v[1] + m[10] * v[2];
return mat;
}
// --- MultiplyMatrix3x4 (hooked but low count — still pure math) ---
// __fastcall(out_ECX, matA_EDX, matB_stack), RET 0x4
fn sseMulMat3x4(out: u32, a_ptr: u32, b_ptr: u32) callconv(FC) u32 {
const dst: [*]f32 = @ptrFromInt(out);
const a: [*]const f32 = @ptrFromInt(a_ptr);
const b: [*]const f32 = @ptrFromInt(b_ptr);
// 3x3 rotation block
inline for (0..3) |row| {
inline for (0..3) |col| {
dst[row * 3 + col] = a[col] * b[row * 3] + a[col + 3] * b[row * 3 + 1] + a[col + 6] * b[row * 3 + 2];
}
}
// Translation row (indices 9-11): same multiply + add B's translation
inline for (0..3) |col| {
dst[9 + col] = a[9] * b[col] + a[10] * b[col + 3] + a[11] * b[col + 6] + b[9 + col];
}
return out;
}
// --- multiplyMatrix3x3 (low count but trivial) ---
// __fastcall(dst_ECX, lhs_EDX, rhs_stack), RET 0x4
fn sseMulMat3x3(out: u32, a_ptr: u32, b_ptr: u32) callconv(FC) u32 {
const dst: [*]f32 = @ptrFromInt(out);
const a: [*]const f32 = @ptrFromInt(a_ptr);
const b: [*]const f32 = @ptrFromInt(b_ptr);
inline for (0..3) |row| {
inline for (0..3) |col| {
dst[row * 3 + col] = a[col] * b[row * 3] + a[col + 3] * b[row * 3 + 1] + a[col + 6] * b[row * 3 + 2];
}
}
return out;
}
// --- transformVector4ByMatrix4x4 (120K calls/7.5s) ---
// __fastcall(outVec4_ECX, inVec4_EDX, matrix_stack), RET 0x4
fn sseTransformVec4Mat4(out: u32, in_vec: u32, mat: u32) callconv(FC) u32 {
const dst: [*]f32 = @ptrFromInt(out);
const src: [*]const f32 = @ptrFromInt(in_vec);
const m: [*]const f32 = @ptrFromInt(mat);
const v: @Vector(4, f32) = .{ src[0], src[1], src[2], src[3] };
inline for (0..4) |i| {
const row: @Vector(4, f32) = .{ m[i * 4], m[i * 4 + 1], m[i * 4 + 2], m[i * 4 + 3] };
dst[i] = @reduce(.Add, v * row);
}
return out;
}
// --- TestSphereAgainstFrustum (375K calls/7.5s) ---
// __thiscall(frustumPlanes_ECX, sphere_stack), RET 0x4
// Tests sphere (xyz + radius at [3]) against 6 planes. Returns 0 (outside) / 3 (inside).
fn sseTestSphereFrustum(planes_ptr: u32, sphere: u32) callconv(TC) u32 {
const planes: [*]const f32 = @ptrFromInt(planes_ptr);
const s: [*]const f32 = @ptrFromInt(sphere);
const cx = s[0];
const cy = s[1];
const cz = s[2];
const r = s[3];
inline for (0..6) |i| {
const pl = planes + i * 4;
const dist = cx * pl[0] + cy * pl[1] + cz * pl[2] + pl[3];
if (dist < -r) return 0; // fully outside this plane
}
return 3;
}
// --- IsPointInsideBounds (1.7M calls/7.5s) ---
// __fastcall(vec3A_ECX, vec3B_EDX), RET (no stack cleanup)
// Returns 1 if all components of B <= A
fn sseIsPointInsideBounds(a: u32, b: u32) callconv(FC) u32 {
const va: [*]const f32 = @ptrFromInt(a);
const vb: [*]const f32 = @ptrFromInt(b);
if (vb[0] <= va[0] and vb[1] <= va[1] and vb[2] <= va[2]) return 1;
return 0;
}
// --- createAxisAngleRotationMatrix (304K calls/7.5s) ---
// __fastcall(outMatrix4x4_ECX, axisVec3_EDX, angle_stack, isNormalized_stack), RET 0x8
// Rodrigues' formula: R = cos(a)*I + (1-cos(a))*outer(axis) + sin(a)*skew(axis)
fn sseCreateAxisAngleRotMat4(out: u32, axis_ptr: u32, angle_bits: u32, is_normalized: u32) callconv(FC) u32 {
const m: [*]f32 = @ptrFromInt(out);
const ax: [*]const f32 = @ptrFromInt(axis_ptr);
var x = ax[0];
var y = ax[1];
var z = ax[2];
// Normalize axis if not already
if (is_normalized == 0) {
const len = @sqrt(x * x + y * y + z * z);
if (len > 1.0e-20) {
const inv = 1.0 / len;
x *= inv;
y *= inv;
z *= inv;
}
}
const angle: f32 = @bitCast(angle_bits);
const c = @cos(angle);
const s = @sin(angle);
const t = 1.0 - c;
// Row 0
m[0] = t * x * x + c;
m[1] = t * x * y + s * z;
m[2] = t * x * z - s * y;
m[3] = 0;
// Row 1
m[4] = t * x * y - s * z;
m[5] = t * y * y + c;
m[6] = t * y * z + s * x;
m[7] = 0;
// Row 2
m[8] = t * x * z + s * y;
m[9] = t * y * z - s * x;
m[10] = t * z * z + c;
m[11] = 0;
// Row 3 — identity
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return out;
}
// --- createAxisAngleRotationMatrix3x3 (13K calls/7.5s) ---
// __fastcall(outMat3x3_ECX, axisVec3_EDX, angle_stack, isNorm_stack), RET 0x8
fn sseCreateAxisAngleRotMat3x3(out: u32, axis_ptr: u32, angle_bits: u32, is_normalized: u32) callconv(FC) u32 {
const m: [*]f32 = @ptrFromInt(out);
const ax: [*]const f32 = @ptrFromInt(axis_ptr);
var x = ax[0];
var y = ax[1];
var z = ax[2];
if (is_normalized == 0) {
const len = @sqrt(x * x + y * y + z * z);
if (len > 1.0e-20) {
const inv = 1.0 / len;
x *= inv;
y *= inv;
z *= inv;
}
}
const angle: f32 = @bitCast(angle_bits);
const c = @cos(angle);
const s = @sin(angle);
const t = 1.0 - c;
m[0] = t * x * x + c;
m[1] = t * x * y + s * z;
m[2] = t * x * z - s * y;
m[3] = t * x * y - s * z;
m[4] = t * y * y + c;
m[5] = t * y * z + s * x;
m[6] = t * x * z + s * y;
m[7] = t * y * z - s * x;
m[8] = t * z * z + c;
return out;
}
// --- CreateRotationMatrix 3x4 (probe only, same Rodrigues) ---
// __fastcall(outMatrix_ECX, axisVec_EDX, angle_stack, isNorm_stack), RET 0x8
fn sseCreateRotMat3x4(out: u32, axis_ptr: u32, angle_bits: u32, is_normalized: u32) callconv(FC) u32 {
const m: [*]f32 = @ptrFromInt(out);
const ax: [*]const f32 = @ptrFromInt(axis_ptr);
var x = ax[0];
var y = ax[1];
var z = ax[2];
if (is_normalized == 0) {
const len = @sqrt(x * x + y * y + z * z);
if (len > 1.0e-20) {
const inv = 1.0 / len;
x *= inv;
y *= inv;
z *= inv;
}
}
const angle: f32 = @bitCast(angle_bits);
const c = @cos(angle);
const s = @sin(angle);
const t = 1.0 - c;
// 3x3 rotation block
m[0] = t * x * x + c;
m[1] = t * x * y + s * z;
m[2] = t * x * z - s * y;
m[3] = t * x * y - s * z;
m[4] = t * y * y + c;
m[5] = t * y * z + s * x;
m[6] = t * x * z + s * y;
m[7] = t * y * z - s * x;
m[8] = t * z * z + c;
// Translation column zeroed
m[9] = 0;
m[10] = 0;
m[11] = 0;
return out;
}
// --- quaternion_slerp (5.7K but foundational) ---
// __fastcall(outQuat_ECX, quatA_EDX, t_stack, quatB_stack), RET 0x8
fn sseQuatSlerp(out: u32, a_ptr: u32, t_bits: u32, b_ptr: u32) callconv(FC) u32 {
const dst: [*]f32 = @ptrFromInt(out);
const a: [*]const f32 = @ptrFromInt(a_ptr);
const b_raw: [*]const f32 = @ptrFromInt(b_ptr);
const t: f32 = @bitCast(t_bits);
// Dot product
var dot = a[0] * b_raw[0] + a[1] * b_raw[1] + a[2] * b_raw[2] + a[3] * b_raw[3];
// Shortest path — negate b if dot < 0
var sign: f32 = 1.0;
if (dot < 0) {
dot = -dot;
sign = -1.0;
}
var s0: f32 = undefined;
var s1: f32 = undefined;
if (dot > 0.9995) {
// Very close — linear interpolation
s0 = 1.0 - t;
s1 = t * sign;
} else {
const theta = std.math.acos(dot);
const sin_theta = @sin(theta);
const inv_sin = 1.0 / sin_theta;
s0 = @sin((1.0 - t) * theta) * inv_sin;
s1 = @sin(t * theta) * inv_sin * sign;
}
dst[0] = s0 * a[0] + s1 * b_raw[0];
dst[1] = s0 * a[1] + s1 * b_raw[1];
dst[2] = s0 * a[2] + s1 * b_raw[2];
dst[3] = s0 * a[3] + s1 * b_raw[3];
return out;
}
// --- NormalizeVector3_InPlace (29K calls/7.5s) ---
// __fastcall(vec3_ECX), RET (no stack cleanup)
fn sseNormalizeVec3InPlace(vec: u32) callconv(FC) void {
const v: [*]f32 = @ptrFromInt(vec);
const len = @sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
if (len > 1.0e-20) {
const inv = 1.0 / len;
v[0] *= inv;
v[1] *= inv;
v[2] *= inv;
}
}
// --- Vector3_DotProduct (31K calls/7.5s) ---
// __fastcall(vecA_ECX, vecB_EDX), RET (no stack cleanup). Returns f64 via x87.
fn sseVec3Dot(a: u32, b: u32) callconv(FC) f64 {
const va: [*]const f32 = @ptrFromInt(a);
const vb: [*]const f32 = @ptrFromInt(b);
return @floatCast(va[0] * vb[0] + va[1] * vb[1] + va[2] * vb[2]);
}
// --- getTransposedMatrix4x4 ---
// __thiscall(srcMatrix_ECX, dstMatrix_stack), RET 0x4
fn sseTransposeMat4x4(src: u32, dst: u32) callconv(TC) u32 {
const s: [*]const f32 = @ptrFromInt(src);
const d: [*]f32 = @ptrFromInt(dst);
inline for (0..4) |row| {
inline for (0..4) |col| {
d[row * 4 + col] = s[col * 4 + row];
}
}
return src;
}
// --- MultiplyMatrix3x4InPlace ---
// __thiscall(matrixA_ECX, matrixB_stack), RET 0x4. Returns this.
// this = this * matB. Uses stack temp for safe aliasing.
fn sseMulMat3x4InPlace(mat_a: u32, mat_b: u32) callconv(TC) u32 {
const a: [*]f32 = @ptrFromInt(mat_a);
const b: [*]const f32 = @ptrFromInt(mat_b);
// Temp for result (12 floats)
var tmp: [12]f32 = undefined;
inline for (0..3) |row| {
inline for (0..3) |col| {
tmp[row * 3 + col] = a[col] * b[row * 3] + a[col + 3] * b[row * 3 + 1] + a[col + 6] * b[row * 3 + 2];
}
}
inline for (0..3) |col| {
tmp[9 + col] = a[9] * b[col] + a[10] * b[col + 3] + a[11] * b[col + 6] + b[9 + col];
}
// Copy back
inline for (0..12) |i| {
a[i] = tmp[i];
}
return mat_a;
}
// --- TestOrientedBoundingBoxAgainstFrustum ---
// __thiscall(frustumPlanes_ECX, aabb_stack, rotMatrix_stack, transVec_stack), RET 0xC
// Tests OBB against 6 frustum planes. Returns 0 (rejected) / 3 (inside).
fn sseTestOBBFrustum(planes_ptr: u32, aabb_ptr: u32, rot_ptr: u32, trans_ptr: u32) callconv(TC) u32 {
const planes: [*]const f32 = @ptrFromInt(planes_ptr);
const aabb: [*]const f32 = @ptrFromInt(aabb_ptr); // min[3] at +0, max[3] at +3
const rot: [*]const f32 = @ptrFromInt(rot_ptr); // 3x3 rotation matrix
const trans: [*]const f32 = @ptrFromInt(trans_ptr); // translation vec3
// Build 8 corners from AABB min/max
const min = [3]f32{ aabb[0], aabb[1], aabb[2] };
const max = [3]f32{ aabb[3], aabb[4], aabb[5] };
var corners: [8][3]f32 = undefined;
inline for (0..8) |i| {
const lx = if (i & 1 != 0) max[0] else min[0];
const ly = if (i & 2 != 0) max[1] else min[1];
const lz = if (i & 4 != 0) max[2] else min[2];
// Transform: world = rot * local + trans
corners[i][0] = rot[0] * lx + rot[3] * ly + rot[6] * lz + trans[0];
corners[i][1] = rot[1] * lx + rot[4] * ly + rot[7] * lz + trans[1];
corners[i][2] = rot[2] * lx + rot[5] * ly + rot[8] * lz + trans[2];
}
// Test against 6 planes
inline for (0..6) |p| {
const pl = planes + p * 4;
var all_outside = true;
inline for (0..8) |c| {
const dist = corners[c][0] * pl[0] + corners[c][1] * pl[1] + corners[c][2] * pl[2] + pl[3];
if (dist >= 0) all_outside = false;
}
if (all_outside) return 0;
}
return 3;
}
// --- createZRotationMatrix3x3 ---
// __thiscall(outMat3x3_ECX, angle_stack), RET 0x4. Returns this.
fn sseCreateZRotMat3x3(out: u32, angle_bits: u32) callconv(TC) u32 {
const m: [*]f32 = @ptrFromInt(out);
const angle: f32 = @bitCast(angle_bits);
const c = @cos(angle);
const s = @sin(angle);
m[0] = c;
m[1] = s;
m[2] = 0;
m[3] = -s;
m[4] = c;
m[5] = 0;
m[6] = 0;
m[7] = 0;
m[8] = 1;
return out;
}
// --- addVector3ToAccumulator (136K calls/7.5s) ---
// __thiscall(this_ECX, vec3_stack), RET 0x4
// Adds Vec3 to this+0x54, also adds scaled copy (global 0x81207C) into 3x3 diagonal
// at +0x84/+0xA8/+0xCC
fn sseAddVec3ToAccumulator(this: u32, vec: u32) callconv(TC) void {
const obj: [*]f32 = @ptrFromInt(this);
const v: [*]const f32 = @ptrFromInt(vec);
const scale: f32 = @as(*const f32, @ptrFromInt(0x81207C)).*;
// Accumulate translation at +0x54 (offset in f32 = 0x54/4 = 21)
obj[21] += v[0];
obj[22] += v[1];
obj[23] += v[2];
// Scaled copy into 3x3 matrix diagonal: +0x84=33, +0xA8=42, +0xCC=51
obj[33] += v[0] * scale;
obj[42] += v[1] * scale;
obj[51] += v[2] * scale;
}
// --- addToColorAccumulator (10K calls/7.5s) ---
// __thiscall(this_ECX, colorVec3_stack), RET 0x4
// Accumulates color/light into this+0x6C (offset in f32 = 0x6C/4 = 27)
fn sseAddToColorAccumulator(this: u32, color: u32) callconv(TC) void {
const obj: [*]f32 = @ptrFromInt(this);
const c: [*]const f32 = @ptrFromInt(color);
obj[27] += c[0];
obj[28] += c[1];
obj[29] += c[2];
}
// --- calculateSinCos ---
// __stdcall(float angle, float* outSin, float* outCos), RET 0xC
fn sseCalculateSinCos(angle_bits: u32, out_sin: u32, out_cos: u32) callconv(SC) void {
const angle: f32 = @bitCast(angle_bits);
const s: *f32 = @ptrFromInt(out_sin);
const c: *f32 = @ptrFromInt(out_cos);
s.* = @sin(angle);
c.* = @cos(angle);
}
// --- rotateMatrixByQuaternion (5.7K calls/7.5s) ---
// __thiscall(matrix_ECX, quat_stack), RET 0x4
// Builds rotation matrix from quaternion, multiplies with existing matrix.
fn sseRotateMatByQuat(mat: u32, quat: u32) callconv(TC) u32 {
const m: [*]f32 = @ptrFromInt(mat);
const q: [*]const f32 = @ptrFromInt(quat);
const x = q[0];
const y = q[1];
const z = q[2];
const w = q[3];
// Build rotation matrix from quaternion
const x2 = x + x;
const y2 = y + y;
const z2 = z + z;
const xx = x * x2;
const xy = x * y2;
const xz = x * z2;
const yy = y * y2;
const yz = y * z2;
const zz = z * z2;
const wx = w * x2;
const wy = w * y2;
const wz = w * z2;
var r: [16]f32 = undefined;
r[0] = 1.0 - (yy + zz);
r[1] = xy + wz;
r[2] = xz - wy;
r[3] = 0;
r[4] = xy - wz;
r[5] = 1.0 - (xx + zz);
r[6] = yz + wx;
r[7] = 0;
r[8] = xz + wy;
r[9] = yz - wx;
r[10] = 1.0 - (xx + yy);
r[11] = 0;
r[12] = 0;
r[13] = 0;
r[14] = 0;
r[15] = 1;
// Multiply: result = quat_matrix * existing_matrix, store back to m
var tmp: [16]f32 = undefined;
inline for (0..4) |row| {
inline for (0..4) |col| {
tmp[row * 4 + col] = r[row * 4] * m[col] + r[row * 4 + 1] * m[4 + col] + r[row * 4 + 2] * m[8 + col] + r[row * 4 + 3] * m[12 + col];
}
}
inline for (0..16) |i| {
m[i] = tmp[i];
}
return mat;
}
// --- packParticleColorToBytes (2K calls/7.5s) ---
// __fastcall(obj_ECX, unused_EDX, floatR_stack, floatG_stack, floatB_stack), RET 0xC
// Reads alpha at obj+0x12F, packs ARGB into u32 at obj+0x12C
fn ssePackParticleColor(obj: u32, _: u32, r_bits: u32, g_bits: u32, b_bits: u32) callconv(FC) void {
const base: [*]u8 = @ptrFromInt(obj);
const out: *u32 = @ptrCast(@alignCast(base + 0x12C));
const alpha = base[0x12F];
const r: f32 = @bitCast(r_bits);
const g: f32 = @bitCast(g_bits);
const b: f32 = @bitCast(b_bits);
const rb: u8 = @intFromFloat(std.math.clamp(r * 255.0, 0.0, 255.0));
const gb: u8 = @intFromFloat(std.math.clamp(g * 255.0, 0.0, 255.0));
const bb: u8 = @intFromFloat(std.math.clamp(b * 255.0, 0.0, 255.0));
out.* = @as(u32, alpha) << 24 | @as(u32, rb) << 16 | @as(u32, gb) << 8 | @as(u32, bb);
}
// --- setParticleAlphaFromFloat (2K calls/7.5s) ---
// __fastcall(obj_ECX, unused_EDX, floatAlpha_stack), RET 0x4
fn sseSetParticleAlpha(obj: u32, _: u32, alpha_bits: u32) callconv(FC) void {
const base: [*]u8 = @ptrFromInt(obj);
const alpha: f32 = @bitCast(alpha_bits);
base[0x12F] = @intFromFloat(std.math.clamp(alpha * 255.0, 0.0, 255.0));
}
// --- TranslateBoundingVolume (2K calls/7.5s) ---
// __thiscall(this_ECX, offsetVec3_stack), RET 0x4
// Translates 8 corners (+0x60..+0xB4, stride 12), recomputes 6 plane distances,
// translates min/max bounds (+0xC0, +0xCC)
fn sseTranslateBoundingVol(this: u32, offset: u32) callconv(TC) void {
const obj: [*]f32 = @ptrFromInt(this);
const off: [*]const f32 = @ptrFromInt(offset);
const dx = off[0];
const dy = off[1];
const dz = off[2];
// 8 corners: +0x60 = float offset 24, stride 3 floats
inline for (0..8) |i| {
const base = 24 + i * 3;
obj[base] += dx;
obj[base + 1] += dy;
obj[base + 2] += dz;
}
// 6 plane distances: planes start at +0x0, each plane is {nx,ny,nz,d} = 4 floats
// d -= dot(normal, offset)
inline for (0..6) |i| {
const base = i * 4;
obj[base + 3] -= obj[base] * dx + obj[base + 1] * dy + obj[base + 2] * dz;
}
// Min bounds at +0xC0 = float offset 48, Max at +0xCC = float offset 51
obj[48] += dx;
obj[49] += dy;
obj[50] += dz;
obj[51] += dx;
obj[52] += dy;
obj[53] += dz;
}
// --- TransformBoundingVolume (3K calls/7.5s) ---
// __thiscall(this_ECX, matrix3x3_stack), RET 0x4
// Transforms 8 corners via 3x3 matrix, recomputes planes and bounds.
fn sseTransformBoundingVol(this: u32, mat: u32) callconv(TC) u32 {
const obj: [*]f32 = @ptrFromInt(this);
const m: [*]const f32 = @ptrFromInt(mat);
// Transform 8 corners in-place
inline for (0..8) |i| {
const base = 24 + i * 3; // +0x60 / 4
const x = obj[base];
const y = obj[base + 1];
const z = obj[base + 2];
obj[base] = m[0] * x + m[3] * y + m[6] * z;
obj[base + 1] = m[1] * x + m[4] * y + m[7] * z;
obj[base + 2] = m[2] * x + m[5] * y + m[8] * z;
}
// Recompute planes from transformed corners — call ComputeFrustumPlanes logic
// This is equivalent to calling 0x686640 on self, which we've also hooked.
// For correctness, call the original ComputeFrustumPlanes via its hook trampoline.
h109.callOriginal(.{this});
// Transform min/max bounds
const mnx = obj[48];
const mny = obj[49];
const mnz = obj[50];
obj[48] = m[0] * mnx + m[3] * mny + m[6] * mnz;
obj[49] = m[1] * mnx + m[4] * mny + m[7] * mnz;
obj[50] = m[2] * mnx + m[5] * mny + m[8] * mnz;
const mxx = obj[51];
const mxy = obj[52];
const mxz = obj[53];
obj[51] = m[0] * mxx + m[3] * mxy + m[6] * mxz;
obj[52] = m[1] * mxx + m[4] * mxy + m[7] * mxz;
obj[53] = m[2] * mxx + m[5] * mxy + m[8] * mxz;
return this;
}
// --- ComputeFrustumPlanesFromVertices (7K calls/7.5s) ---
// __fastcall(this_ECX), RET (no stack cleanup)
// Computes 4 clipping planes from 8 corner vertices using cross products + normalize.
// Keep as probe — complex geometry with cross products, normalize calls, and
// plane distance computation. The 8 corners are already transformed by our hooks above.
// =============================================================================
// Probe infrastructure (for functions not yet SSE-replaced)
// =============================================================================
fn probeDetour(
comptime FnType: type,
comptime detour_hook: *hook.Detour(FnType),
comptime counter: *u32,
) *const FnType {
const info = @typeInfo(FnType).@"fn";
const Ret = info.return_type.?;
const nparams = info.params.len;
const cc = info.calling_convention;
const S = struct {
fn d0() callconv(cc) Ret {
_ = @atomicRmw(u32, counter, .Add, 1, .monotonic);
return detour_hook.callOriginal(.{});
}
fn d1(a: u32) callconv(cc) Ret {
_ = @atomicRmw(u32, counter, .Add, 1, .monotonic);
return detour_hook.callOriginal(.{a});
}
fn d2(a: u32, b: u32) callconv(cc) Ret {
_ = @atomicRmw(u32, counter, .Add, 1, .monotonic);
return detour_hook.callOriginal(.{ a, b });
}
fn d3(a: u32, b: u32, c: u32) callconv(cc) Ret {
_ = @atomicRmw(u32, counter, .Add, 1, .monotonic);
return detour_hook.callOriginal(.{ a, b, c });
}
fn d4(a: u32, b: u32, c: u32, d: u32) callconv(cc) Ret {
_ = @atomicRmw(u32, counter, .Add, 1, .monotonic);
return detour_hook.callOriginal(.{ a, b, c, d });
}
fn d5(a: u32, b: u32, c: u32, d: u32, e: u32) callconv(cc) Ret {
_ = @atomicRmw(u32, counter, .Add, 1, .monotonic);
return detour_hook.callOriginal(.{ a, b, c, d, e });
}
fn d6(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) callconv(cc) Ret {
_ = @atomicRmw(u32, counter, .Add, 1, .monotonic);
return detour_hook.callOriginal(.{ a, b, c, d, e, f });
}
fn d10(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32, g: u32, i: u32, j: u32, k: u32) callconv(cc) Ret {
_ = @atomicRmw(u32, counter, .Add, 1, .monotonic);
return detour_hook.callOriginal(.{ a, b, c, d, e, f, g, i, j, k });
}
};
return switch (nparams) {
0 => @ptrCast(&S.d0),
1 => @ptrCast(&S.d1),
2 => @ptrCast(&S.d2),
3 => @ptrCast(&S.d3),
4 => @ptrCast(&S.d4),
5 => @ptrCast(&S.d5),
6 => @ptrCast(&S.d6),
10 => @ptrCast(&S.d10),
else => @compileError("unsupported param count for probe"),
};
}
// --- Probe table: { index, name, address, hook_var, fn_type } ---
// Grouped by calling convention type for readability.
// Total: 60 probes covering all explored functions (minus non-math and 7+ param outliers).
const NUM_PROBES = 116;
var cnt = [1]u32{0} ** NUM_PROBES;
// Hook variables — one per probed function
// Cat 1: Scalar math
var h00: hook.Detour(TC2v) = .{}; // 0x4549C0 normalizeVector3
var h01: hook.Detour(SC1d) = .{}; // 0x41AE40 ScaleCoord div0.8
var h02: hook.Detour(SC1d) = .{}; // 0x41AE50 ScaleCoord div0.6
var h03: hook.Detour(SC1d) = .{}; // 0x41AE60 ScaleCoord mul0.8
var h04: hook.Detour(SC1d) = .{}; // 0x41AE70 ScaleCoord mul0.6
// Cat 4: Matrix multiply
var h05: hook.Detour(FC3r) = .{}; // 0x7BCA80 transformVector3ByMatrix4x4
var h06: hook.Detour(FC3r) = .{}; // 0x7BCB40 transformVector4ByMatrix4x4
var h07: hook.Detour(FC3r) = .{}; // 0x7BAE60 MultiplyMatrix3x4
var h08: hook.Detour(FC3r) = .{}; // 0x7BDFC0 multiplyMatrix3x3
var h09: hook.Detour(FC4r) = .{}; // 0x7BDB00 createAxisAngleRotationMatrix
var h10: hook.Detour(TC2r) = .{}; // 0x7BDC40 ApplyTranslationMatrix
var h11: hook.Detour(TC2r) = .{}; // 0x7BDCA0 scaleMatrix3x3ByVector
var h12: hook.Detour(TC2r) = .{}; // 0x7BDDB0 rotateMatrixByQuaternion
var h13: hook.Detour(FC4r) = .{}; // 0x7BE490 createAxisAngleRotMat3x3
var h14: hook.Detour(FC4r) = .{}; // 0x7BB860 CreateRotationMatrix 3x4
// Cat 5: Collision/spatial
var h15: hook.Detour(FC5r) = .{}; // 0x632830 RayPolygonIntersectionTest
var h16: hook.Detour(FC3d) = .{}; // 0x6329E0 CalculateDistanceToPlane
var h17: hook.Detour(FC5r) = .{}; // 0x632F80 CalcTrianglePlanesFromVerts
var h18: hook.Detour(FC2r) = .{}; // 0x6335D0 testPointTriangleCollision
var h19: hook.Detour(FC5r) = .{}; // 0x681B50 ProjectVerticesAndUpdateDepth
var h20: hook.Detour(TC3r) = .{}; // 0x686C20 ClassifyPointAgainstFrustum
var h21: hook.Detour(FC2r) = .{}; // 0x6856C0 ValidateGameObject
var h22: hook.Detour(FC3r) = .{}; // 0x686000 FrustumCullBoundingBox_0
var h23: hook.Detour(FC2r) = .{}; // 0x686180 FrustumCullBoundingBox_1
var h24: hook.Detour(FC3r) = .{}; // 0x6DC5A0 CheckBoxLineIntersection
var h25: hook.Detour(FC4r) = .{}; // 0x50A840 CalcOrthonormalBasis
var h26: hook.Detour(FC4r) = .{}; // 0x509220 CheckBoundingBoxCollision
var h27: hook.Detour(TC4r) = .{}; // 0x6869C0 TestOBBAgainstFrustum
var h28: hook.Detour(TC2r) = .{}; // 0x686B80 TestSphereAgainstFrustum
// Cat 6: Rendering pipeline
var h29: hook.Detour(FC4r) = .{}; // 0x6ABC40 processLinkedListCollision
var h30: hook.Detour(FC4r) = .{}; // 0x6ABE60 processSpecialObjCollision
var h31: hook.Detour(FC4r) = .{}; // 0x6AD7E0 frustumCullGeometry
var h32: hook.Detour(FC1v) = .{}; // 0x6AFAD0 UpdateEntityAndChunksPos
var h33: hook.Detour(FC3r) = .{}; // 0x6B7070 calculateWaterHeight
var h34: hook.Detour(FC4r) = .{}; // 0x6B8B70 checkBoundingBoxIntersect
var h35: hook.Detour(TC3r) = .{}; // 0x6B88E0 performCollisionDetection
var h36: hook.Detour(TC3r) = .{}; // 0x6B8C60 PerformSpatialCulling
var h37: hook.Detour(TC4v) = .{}; // 0x6BC370 SetupCylinderFrustum
var h38: hook.Detour(FC1v) = .{}; // 0x6C15D0 executeRenderCommands
// Cat 7: Bone/model
var h39: hook.Detour(FC4r) = .{}; // 0x71AE90 extractAnimByteFromKeyframes
var h40: hook.Detour(FC4r) = .{}; // 0x71AF20 getInterpolatedFloat
var h41: hook.Detour(TC2v) = .{}; // 0x71B6A0 normalizeVector3 (bone)
var h42: hook.Detour(TC2v) = .{}; // 0x71BC70 addVector3ToAccumulator
var h43: hook.Detour(TC2v) = .{}; // 0x71BF60 addToColorAccumulator
var h44: hook.Detour(FC1v) = .{}; // 0x71C160 transformLightsAndPlanes
var h45: hook.Detour(FC1v) = .{}; // 0x71C2F0 calculateFrustumPlanes
var h46: hook.Detour(TC2r) = .{}; // 0x71C4E0 calcSphericalHarmonics
var h47: hook.Detour(TC1v) = .{}; // 0x718960 renderSceneNode
var h48: hook.Detour(TC2v) = .{}; // 0x719370 updateAnimationSystem
var h49: hook.Detour(TC3r) = .{}; // 0x712D50 GetTransformedAnimPos
var h50: hook.Detour(TC2r) = .{}; // 0x713680 GetBoundingSphere
// Cat 8: Particle
var h51: hook.Detour(TC2r) = .{}; // 0x7B3A10 RenderParticleSystemSorted
var h52: hook.Detour(TC3v) = .{}; // 0x7B5A10 ProcessActiveParticles
var h53: hook.Detour(TC3v) = .{}; // 0x7B7E60 AdvanceParticles
// Cat 9: Geometry math
var h54: hook.Detour(FC4r) = .{}; // 0x7C0570 quaternion_slerp
var h55: hook.Detour(FC3r) = .{}; // 0x7C2040 point_sphere_collision_test
var h56: hook.Detour(FC5r) = .{}; // 0x7C22B0 ray_plane_intersection
var h57: hook.Detour(FC1v) = .{}; // 0x7C5880 calc_animation_orientation
// Misc
var h58: hook.Detour(FC3r) = .{}; // 0x70A060 compareRayHitData
var h59: hook.Detour(FC3r) = .{}; // 0x70AE10 compareRenderItemsExtended
// --- Batch 2: remaining explored functions ---
var h60: hook.Detour(TC2r) = .{}; // 0x4183D0 WritePointerToStream
var h61: hook.Detour(TC4r) = .{}; // 0x453480 AudioCallback_SetVolume
var h62: hook.Detour(TC5v) = .{}; // 0x453580 Vector3_InterpolateWeighted
var h63: hook.Detour(TC4r) = .{}; // 0x4541B0 interpolateVector3
var h64: hook.Detour(FC5r) = .{}; // 0x483340 UpdateLightingData
var h65: hook.Detour(FC6r) = .{}; // 0x509BF0 ClampBounds
var h66: hook.Detour(TC2r) = .{}; // 0x593040 VertexData_UpdateRenderState
var h67: hook.Detour(TC2d) = .{}; // 0x5C7010 ConvertPixelsToScreenAlt (returns float via x87)
var h68: hook.Detour(FC2r) = .{}; // 0x5E22D0 CheckPlayerInTriggerZone
var h69: hook.Detour(TC3r) = .{}; // 0x5F6280 InterpolateSpellPosition
var h70: hook.Detour(TC6v) = .{}; // 0x5F8DC0 CalculateRenderingBounds
var h71: hook.Detour(FC3r) = .{}; // 0x614CD0 UpdateObjectTransform (3 params, RET 0x4)
var h72: hook.Detour(TC4r) = .{}; // 0x616AF0 ValidatePositionUpdate
var h73: hook.Detour(FC1r) = .{}; // 0x616BF0 ValidateCoordinateBounds
var h74: hook.Detour(FC1r) = .{}; // 0x618920 GetUnitPositionBufIfValid
var h75: hook.Detour(FC1v) = .{}; // 0x675AC0 Weather_ProcessEnvironment
var h76: hook.Detour(TC2d) = .{}; // 0x67C820 GetCachedHeightValue
var h77: hook.Detour(TC2d) = .{}; // 0x67CA00 GetHeightFromCachedMap
var h78: hook.Detour(TC3v) = .{}; // 0x67CA90 SampleAndCacheHeightData
var h79: hook.Detour(TC4r) = .{}; // 0x67CB60 RaycastLineWithHeightCheck
var h80: hook.Detour(FC2v) = .{}; // 0x6818B0 AddObjectToSpatialList
var h81: hook.Detour(SC0v) = .{}; // 0x683F80 AllocateGameObject
var h82: hook.Detour(TC6v) = .{}; // 0x68B0D0 CalculateDetailDistances
var h83: hook.Detour(FC1v) = .{}; // 0x68D540 CalculateChunkVertices
var h84: hook.Detour(FC2r) = .{}; // 0x699330 IsPointInsideBounds
var h85: hook.Detour(FC2r) = .{}; // 0x69B1C0 GetTerrainDataAtPosition
var h86: hook.Detour(FC5r) = .{}; // 0x69B6D0 GetWaterSurfaceData
var h87: hook.Detour(TC2r) = .{}; // 0x6A8050 SetupRenderingTransforms
var h88: hook.Detour(FC6r) = .{}; // 0x6AADC0 procTerrainChunkMeshGen
var h89: hook.Detour(FC3d) = .{}; // 0x6CF6C0 interpolateKeyframes (3 params, not 4)
var h90: hook.Detour(FC2r) = .{}; // 0x6FA1A0 lua_table_hash_index
var h91: hook.Detour(FC2r) = .{}; // 0x6FA700 lua_table_get_array_elem
var h92: hook.Detour(FC2r) = .{}; // 0x6FA7A0 lua_table_lookup_key
var h93: hook.Detour(TC2r) = .{}; // 0x7B4BF0 SetParticleLifetime
var h94: hook.Detour(TC4v) = .{}; // 0x7B76C0 applyParticleWorldTransform
var h95: hook.Detour(TC4v) = .{}; // 0x7B8890 GenerateSphereParticle
var h96: hook.Detour(TC4v) = .{}; // 0x7B8D70 updateRibbonParticle
var h97: hook.Detour(TC4v) = .{}; // 0x7BA200 generateRandomParticle
// --- Batch 3: newly explored + previously skipped ---
var h98: hook.Detour(SC3v) = .{}; // 0x749280 calculateSinCos
var h99: hook.Detour(TC2r) = .{}; // 0x7BE5B0 createZRotationMatrix3x3
var h100: hook.Detour(FC3r) = .{}; // 0x76D680 SetupModelLighting
var h101: hook.Detour(TC2r) = .{}; // 0x7786A0 SetModelLighting (UI model ctor)
var h102: hook.Detour(FC5r) = .{}; // 0x69BFF0 CMap::VectorIntersect
var h103: hook.Detour(TC2r) = .{}; // 0x7BCEF0 getTransposedMatrix4x4
var h104: hook.Detour(TC2r) = .{}; // 0x7BB420 MultiplyMatrix3x4InPlace
var h105: hook.Detour(TC3r) = .{}; // 0x7B2A50 RenderParticleSprites
var h106: hook.Detour(FC5v) = .{}; // 0x7B7A80 packParticleColorToBytes (5 params, RET 0xC)
var h107: hook.Detour(FC3v) = .{}; // 0x7B7B10 setParticleAlphaFromFloat (3 params, RET 0x4)
var h108: hook.Detour(FC2d) = .{}; // 0x602630 Vector3_DotProduct
var h109: hook.Detour(FC1v) = .{}; // 0x686640 ComputeFrustumPlanesFromVerts
var h110: hook.Detour(TC2v) = .{}; // 0x686820 TranslateBoundingVolume
var h111: hook.Detour(TC2r) = .{}; // 0x6868E0 TransformBoundingVolume
var h112: hook.Detour(FC1v) = .{}; // 0x6720F0 NormalizeVector3_InPlace
// RenderTextToVertexBuffer: __thiscall, 10 params total (ECX + 9 stack)
const TC10r = fn (u32, u32, u32, u32, u32, u32, u32, u32, u32, u32) callconv(TC) u32;
var h113: hook.Detour(TC10r) = .{}; // 0x5C8710 RenderTextToVertexBuffer
var h114: hook.Detour(SC2r) = .{}; // 0x40CF81 GetFPUControlWord
var h115: hook.Detour(CD0r) = .{}; // 0x40A2B0 __ftol (real)
/// Hand-written __ftol probe. ST(0) holds the implicit float input and must be
/// preserved across the counter increment. Saves/restores EAX as scratch to
/// hold absolute addresses — EAX is caller-saved in cdecl so this is safe
/// for the counter, then we restore it before jumping to the original.
fn ftolProbe() callconv(.naked) void {
asm volatile (
\\push %%eax
\\mov %[cnt], %%eax
\\lock addl $1, (%%eax)
\\mov %[orig], %%eax
\\mov (%%eax), %%eax
\\xchg %%eax, (%%esp)
\\ret
:
: [cnt] "i" (&cnt[115]),
[orig] "i" (&h115.inner.trampoline),
);
}
// ftolSSE2 compare mode removed — using direct byte patching via si_ftol now
const ProbeInfo = struct { name: [*:0]const u8, idx: u32 };
const probe_infos = [NUM_PROBES]ProbeInfo{
.{ .name = "normalizeVector3", .idx = 0 },
.{ .name = "ScaleCoord_div0.8", .idx = 1 },
.{ .name = "ScaleCoord_div0.6", .idx = 2 },
.{ .name = "ScaleCoord_mul0.8", .idx = 3 },
.{ .name = "ScaleCoord_mul0.6", .idx = 4 },
.{ .name = "transformVec3ByMat4", .idx = 5 },
.{ .name = "transformVec4ByMat4", .idx = 6 },
.{ .name = "MultiplyMatrix3x4", .idx = 7 },
.{ .name = "multiplyMatrix3x3", .idx = 8 },
.{ .name = "createAxisAngleRotMat4", .idx = 9 },
.{ .name = "ApplyTranslationMatrix", .idx = 10 },
.{ .name = "scaleMatrix3x3ByVec", .idx = 11 },
.{ .name = "rotateMatByQuaternion", .idx = 12 },
.{ .name = "createAxisAngleRotMat3", .idx = 13 },
.{ .name = "CreateRotationMat3x4", .idx = 14 },
.{ .name = "RayPolygonIntersect", .idx = 15 },
.{ .name = "DistanceToPlane", .idx = 16 },
.{ .name = "CalcTriPlanes", .idx = 17 },
.{ .name = "testPointTriCollision", .idx = 18 },
.{ .name = "ProjectVertsUpdateDepth", .idx = 19 },
.{ .name = "ClassifyPointFrustum", .idx = 20 },
.{ .name = "ValidateGameObject", .idx = 21 },
.{ .name = "FrustumCullBBox_0", .idx = 22 },
.{ .name = "FrustumCullBBox_1", .idx = 23 },
.{ .name = "CheckBoxLineIntersect", .idx = 24 },
.{ .name = "CalcOrthonormalBasis", .idx = 25 },
.{ .name = "CheckBBoxCollision", .idx = 26 },
.{ .name = "TestOBBFrustum", .idx = 27 },
.{ .name = "TestSphereFrustum", .idx = 28 },
.{ .name = "procLinkedListColl", .idx = 29 },
.{ .name = "procSpecialObjColl", .idx = 30 },
.{ .name = "frustumCullGeometry", .idx = 31 },
.{ .name = "UpdateEntityChunksPos", .idx = 32 },
.{ .name = "calcWaterHeight", .idx = 33 },
.{ .name = "checkBBoxIntersect", .idx = 34 },
.{ .name = "performCollisionDetect", .idx = 35 },
.{ .name = "PerformSpatialCulling", .idx = 36 },
.{ .name = "SetupCylinderFrustum", .idx = 37 },
.{ .name = "executeRenderCommands", .idx = 38 },
.{ .name = "extractAnimByte", .idx = 39 },
.{ .name = "getInterpolatedFloat", .idx = 40 },
.{ .name = "normalizeVec3_bone", .idx = 41 },
.{ .name = "addVec3ToAccumulator", .idx = 42 },
.{ .name = "addToColorAccumulator", .idx = 43 },
.{ .name = "transformLightsPlanes", .idx = 44 },
.{ .name = "calcFrustumPlanes", .idx = 45 },
.{ .name = "calcSphericalHarmonics", .idx = 46 },
.{ .name = "renderSceneNode", .idx = 47 },
.{ .name = "updateAnimationSystem", .idx = 48 },
.{ .name = "GetTransformedAnimPos", .idx = 49 },
.{ .name = "GetBoundingSphere", .idx = 50 },
.{ .name = "RenderParticleSorted", .idx = 51 },
.{ .name = "ProcessActiveParticles", .idx = 52 },
.{ .name = "AdvanceParticles", .idx = 53 },
.{ .name = "quaternion_slerp", .idx = 54 },
.{ .name = "pointSphereCollision", .idx = 55 },
.{ .name = "rayPlaneIntersection", .idx = 56 },
.{ .name = "calcAnimOrientation", .idx = 57 },
.{ .name = "compareRayHitData", .idx = 58 },
.{ .name = "compareRenderItems", .idx = 59 },
.{ .name = "WritePointerToStream", .idx = 60 },
.{ .name = "AudioCB_SetVolume", .idx = 61 },
.{ .name = "Vec3_InterpWeighted", .idx = 62 },
.{ .name = "interpolateVector3", .idx = 63 },
.{ .name = "UpdateLightingData", .idx = 64 },
.{ .name = "ClampBounds", .idx = 65 },
.{ .name = "VtxData_UpdateRender", .idx = 66 },
.{ .name = "ConvertPixelsToScreen", .idx = 67 },
.{ .name = "CheckPlayerInTrigger", .idx = 68 },
.{ .name = "InterpSpellPosition", .idx = 69 },
.{ .name = "CalcRenderingBounds", .idx = 70 },
.{ .name = "UpdateObjTransform", .idx = 71 },
.{ .name = "ValidatePositionUpd", .idx = 72 },
.{ .name = "ValidateCoordBounds", .idx = 73 },
.{ .name = "GetUnitPosBufIfValid", .idx = 74 },
.{ .name = "Weather_ProcessEnv", .idx = 75 },
.{ .name = "GetCachedHeightValue", .idx = 76 },
.{ .name = "GetHeightFromCached", .idx = 77 },
.{ .name = "SampleCacheHeightData", .idx = 78 },
.{ .name = "RaycastLineHeight", .idx = 79 },
.{ .name = "AddObjToSpatialList", .idx = 80 },
.{ .name = "AllocateGameObject", .idx = 81 },
.{ .name = "CalcDetailDistances", .idx = 82 },
.{ .name = "CalcChunkVertices", .idx = 83 },
.{ .name = "IsPointInsideBounds", .idx = 84 },
.{ .name = "GetTerrainDataAtPos", .idx = 85 },
.{ .name = "GetWaterSurfaceData", .idx = 86 },
.{ .name = "SetupRenderTransforms", .idx = 87 },
.{ .name = "procTerrainChunkMesh", .idx = 88 },
.{ .name = "interpolateKeyframes", .idx = 89 },
.{ .name = "lua_table_hash_index", .idx = 90 },
.{ .name = "lua_table_get_array", .idx = 91 },
.{ .name = "lua_table_lookup_key", .idx = 92 },
.{ .name = "SetParticleLifetime", .idx = 93 },
.{ .name = "applyParticleWorldTx", .idx = 94 },
.{ .name = "GenerateSphereParticle", .idx = 95 },
.{ .name = "updateRibbonParticle", .idx = 96 },
.{ .name = "generateRandomParticle", .idx = 97 },
.{ .name = "calculateSinCos", .idx = 98 },
.{ .name = "createZRotMat3x3", .idx = 99 },
.{ .name = "SetupModelLighting", .idx = 100 },
.{ .name = "SetModelLighting_ctor", .idx = 101 },
.{ .name = "CMap_VectorIntersect", .idx = 102 },
.{ .name = "getTransposedMat4x4", .idx = 103 },
.{ .name = "MulMatrix3x4InPlace", .idx = 104 },
.{ .name = "RenderParticleSprites", .idx = 105 },
.{ .name = "packParticleColor", .idx = 106 },
.{ .name = "setParticleAlpha", .idx = 107 },
.{ .name = "Vector3_DotProduct", .idx = 108 },
.{ .name = "ComputeFrustumPlanes", .idx = 109 },
.{ .name = "TranslateBoundingVol", .idx = 110 },
.{ .name = "TransformBoundingVol", .idx = 111 },
.{ .name = "NormalizeVec3_InPlace", .idx = 112 },
.{ .name = "RenderTextToVtxBuf", .idx = 113 },
.{ .name = "GetFPUControlWord", .idx = 114 },
.{ .name = "__ftol", .idx = 115 },
};
// =============================================================================
// Periodic reporting via OnWorldUpdate (0x482EA0)
// =============================================================================
const REPORT_FRAMES: u32 = 450; // ~7.5s at 60fps
var frame_counter: u32 = 0;
const WorldUpdateFn = fn (u32) callconv(FC) void;
var world_update_hook: hook.Detour(WorldUpdateFn) = .{};
fn worldUpdateDetour(frame_count: u32) callconv(FC) void {
world_update_hook.callOriginal(.{frame_count});
frame_counter +%= 1;
if (frame_counter >= REPORT_FRAMES) {
reportProbes();
frame_counter = 0;
}
}
// =============================================================================
// Module lifecycle
// =============================================================================
pub fn isActive() bool {
return g_is_hook_owner;
}
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_state = logging.Logger.open(module_name, .both);
// Binary-patch SSE replacements immediately at DLL load
const patched = installPatches();
log_state.fmt("silicon: {d} JMP patches installed\n", .{patched});
}
// =============================================================================
// Binary patching: write JMP rel32 at each game function to our SSE replacement.
// No trampoline, no CC translation — our functions use the game's native CC.
// =============================================================================
const sse = struct {
// silicon_sse.zig exports (linked via object file)
extern fn si_normalizeVec3() callconv(.naked) void;
extern fn si_mulMat3x4(u32, u32, u32) callconv(FC) u32;
extern fn si_rotateMatByQuat(u32, u32) callconv(TC) u32;
extern fn si_createRotMat3x4(u32, u32, u32, u32) callconv(FC) u32;
extern fn si_distanceToPlane() callconv(.naked) void;
extern fn si_classifyPointFrustum(u32, u32, u32) callconv(TC) u32;
extern fn si_checkBoxLineIntersect(u32, u32, u32) callconv(FC) u32;
extern fn si_testOBBFrustum(u32, u32, u32, u32) callconv(TC) u32;
extern fn si_testSphereFrustum(u32, u32) callconv(TC) u32;
extern fn si_quatSlerp(u32, u32, u32, u32) callconv(FC) u32;
extern fn si_isPointInsideBounds() callconv(.naked) void;
extern fn si_calculateSinCos(u32, u32, u32) callconv(SC) void;
extern fn si_createZRotMat3x3(u32, u32) callconv(TC) u32;
extern fn si_transposeMat4x4() callconv(.naked) void;
extern fn si_mulMat3x4InPlace(u32, u32) callconv(TC) u32;
extern fn si_normalizeVec3InPlace(u32) callconv(TC) void;
extern fn si_addVec3ToAccumulator(u32, u32) callconv(TC) void;
extern fn si_addToColorAccumulator() callconv(.naked) void;
extern fn si_packParticleColor(u32, u32, u32, u32) callconv(TC) void;
extern fn si_setParticleAlpha() callconv(.naked) void;
extern fn si_ftol() callconv(.naked) void;
extern fn si_vec3Dot() callconv(.naked) void;
extern fn si_translateBoundingVol(u32, u32) callconv(TC) void;
extern fn si_processLinkedListCollision(u32, u32, u32, u32) callconv(FC) u32;
extern fn si_frustumCullBBox(u32, u32, u32) callconv(FC) u32;
};
const PatchEntry = struct {
target: u32,
replacement: u32,
name: [*:0]const u8,
direct_size: u8 = 0, // >0: copy this many bytes directly (naked asm, no JMP)
};
fn getPatchTable() []const PatchEntry {
const table = [_]PatchEntry{
.{ .target = 0x4549C0, .replacement = @intFromPtr(&sse.si_normalizeVec3), .name = "normalizeVec3" },
.{ .target = 0x7BAE60, .replacement = @intFromPtr(&sse.si_mulMat3x4), .name = "mulMat3x4" },
.{ .target = 0x7BDDB0, .replacement = @intFromPtr(&sse.si_rotateMatByQuat), .name = "rotateMatByQuat" },
.{ .target = 0x7BB860, .replacement = @intFromPtr(&sse.si_createRotMat3x4), .name = "createRotMat3x4" },
// distanceToPlane (0x6329E0): removed — at parity, division dominates both versions
.{ .target = 0x686C20, .replacement = @intFromPtr(&sse.si_classifyPointFrustum), .name = "classifyPointFrustum" },
.{ .target = 0x6DC5A0, .replacement = @intFromPtr(&sse.si_checkBoxLineIntersect), .name = "checkBoxLineIntersect" },
.{ .target = 0x6869C0, .replacement = @intFromPtr(&sse.si_testOBBFrustum), .name = "testOBBFrustum" },
.{ .target = 0x686B80, .replacement = @intFromPtr(&sse.si_testSphereFrustum), .name = "testSphereFrustum" },
.{ .target = 0x7C0570, .replacement = @intFromPtr(&sse.si_quatSlerp), .name = "quatSlerp" },
// isPointInsideBounds (0x699330): removed — at parity, not worth patching
.{ .target = 0x749280, .replacement = @intFromPtr(&sse.si_calculateSinCos), .name = "calculateSinCos" },
.{ .target = 0x7BE5B0, .replacement = @intFromPtr(&sse.si_createZRotMat3x3), .name = "createZRotMat3x3" },
.{ .target = 0x7BCEF0, .replacement = @intFromPtr(&sse.si_transposeMat4x4), .name = "transposeMat4x4", .direct_size = 64 },
.{ .target = 0x7BB420, .replacement = @intFromPtr(&sse.si_mulMat3x4InPlace), .name = "mulMat3x4InPlace" },
.{ .target = 0x6720F0, .replacement = @intFromPtr(&sse.si_normalizeVec3InPlace), .name = "normalizeVec3InPlace" },
.{ .target = 0x71BC70, .replacement = @intFromPtr(&sse.si_addVec3ToAccumulator), .name = "addVec3ToAccumulator" },
.{ .target = 0x71BF60, .replacement = @intFromPtr(&sse.si_addToColorAccumulator), .name = "addToColorAccumulator" },
.{ .target = 0x7B7A80, .replacement = @intFromPtr(&sse.si_packParticleColor), .name = "packParticleColor" },
.{ .target = 0x7B7B10, .replacement = @intFromPtr(&sse.si_setParticleAlpha), .name = "setParticleAlpha" },
.{ .target = 0x40A2B0, .replacement = @intFromPtr(&sse.si_ftol), .name = "__ftol", .direct_size = 9 },
// vec3Dot (0x602630): removed — 0.6x regression, x87 is optimal for this ABI
.{ .target = 0x686820, .replacement = @intFromPtr(&sse.si_translateBoundingVol), .name = "translateBoundingVol" },
.{ .target = 0x6ABC40, .replacement = @intFromPtr(&sse.si_processLinkedListCollision), .name = "processLinkedListCollision" },
// frustumCullBBox: A/B tested via detour hook, not patched
};
return &table;
}
fn patchJmp(target: u32, replacement: u32) void {
const rel = @as(i32, @bitCast(replacement -% target -% 5));
var patch = [5]u8{ 0xE9, 0, 0, 0, 0 };
@as(*align(1) i32, @ptrCast(patch[1..5])).* = rel;
hook.writeProtected(target, &patch);
}
fn patchDirect(target: u32, src: [*]const u8, size: usize) void {
hook.writeProtected(target, src[0..size]);
}
fn installPatches() u32 {
var count: u32 = 0;
const patch = struct {
fn jmp(target: u32, comptime func: anytype) void {
patchJmp(target, @intFromPtr(func));
}
fn direct(target: u32, comptime func: anytype, size: usize) void {
patchDirect(target, @as([*]const u8, @ptrCast(func)), size);
}
};
patch.jmp(0x4549C0, &sse.si_normalizeVec3);
patch.jmp(0x7BAE60, &sse.si_mulMat3x4);
patch.jmp(0x7BDDB0, &sse.si_rotateMatByQuat);
patch.jmp(0x7BB860, &sse.si_createRotMat3x4);
patch.jmp(0x686C20, &sse.si_classifyPointFrustum);
patch.jmp(0x6DC5A0, &sse.si_checkBoxLineIntersect);
patch.jmp(0x6869C0, &sse.si_testOBBFrustum);
patch.jmp(0x686B80, &sse.si_testSphereFrustum);
patch.jmp(0x7C0570, &sse.si_quatSlerp);
patch.jmp(0x749280, &sse.si_calculateSinCos);
patch.jmp(0x7BE5B0, &sse.si_createZRotMat3x3);
patch.jmp(0x7BB420, &sse.si_mulMat3x4InPlace);
patch.jmp(0x6720F0, &sse.si_normalizeVec3InPlace);
patch.jmp(0x71BC70, &sse.si_addVec3ToAccumulator);
patch.jmp(0x71BF60, &sse.si_addToColorAccumulator);
patch.jmp(0x7B7A80, &sse.si_packParticleColor);
patch.jmp(0x7B7B10, &sse.si_setParticleAlpha);
patch.jmp(0x686820, &sse.si_translateBoundingVol);
count += 18;
// Direct byte patches (naked asm that fits in original)
patch.direct(0x40A2B0, &sse.si_ftol, 9);
patch.direct(0x7BCEF0, &sse.si_transposeMat4x4, 64);
count += 2;
return count;
}
/// Called from engineInitDetour after engine is fully initialized.
pub fn lateInit() void {
if (!g_is_hook_owner) return;
var installed: u32 = 0;
// Detour hooks for profiling probes (functions without SSE replacements)
const INSTALL_PROBES = false; // disable probes when patching
if (INSTALL_PROBES) {
if (h00.attach(0x4549C0, &sseNormalizeVec3) == .ok) installed += 1; // 137K/7.5s
if (h01.attach(0x41AE40, probeDetour(SC1d, &h01, &cnt[1])) == .ok) installed += 1;
if (h02.attach(0x41AE50, probeDetour(SC1d, &h02, &cnt[2])) == .ok) installed += 1;
if (h03.attach(0x41AE60, probeDetour(SC1d, &h03, &cnt[3])) == .ok) installed += 1;
if (h04.attach(0x41AE70, probeDetour(SC1d, &h04, &cnt[4])) == .ok) installed += 1;
// Cat 4: Matrix — SSE replacements
if (h05.attach(0x7BCA80, &sseTransformVec3Mat4) == .ok) installed += 1; // 10.8M/7.5s
if (h06.attach(0x7BCB40, &sseTransformVec4Mat4) == .ok) installed += 1; // 120K/7.5s
if (h07.attach(0x7BAE60, &sseMulMat3x4) == .ok) installed += 1;
if (h08.attach(0x7BDFC0, &sseMulMat3x3) == .ok) installed += 1;
if (h09.attach(0x7BDB00, &sseCreateAxisAngleRotMat4) == .ok) installed += 1; // 304K/7.5s
if (h10.attach(0x7BDC40, &sseApplyTranslation) == .ok) installed += 1; // 182K/7.5s
if (h11.attach(0x7BDCA0, &sseScaleMat3x3) == .ok) installed += 1; // 1.3M/7.5s
if (h12.attach(0x7BDDB0, &sseRotateMatByQuat) == .ok) installed += 1;
if (h13.attach(0x7BE490, &sseCreateAxisAngleRotMat3x3) == .ok) installed += 1; // 13K/7.5s
if (h14.attach(0x7BB860, &sseCreateRotMat3x4) == .ok) installed += 1;
// Cat 5: Collision/spatial — SSE replacements
if (h15.attach(0x632830, probeDetour(FC5r, &h15, &cnt[15])) == .ok) installed += 1; // RayPolygonIntersect — complex, keep probe
if (h16.attach(0x6329E0, &sseDistanceToPlane) == .ok) installed += 1; // 525K/7.5s
if (h17.attach(0x632F80, probeDetour(FC5r, &h17, &cnt[17])) == .ok) installed += 1;
if (h18.attach(0x6335D0, probeDetour(FC2r, &h18, &cnt[18])) == .ok) installed += 1;
if (h19.attach(0x681B50, probeDetour(FC5r, &h19, &cnt[19])) == .ok) installed += 1;
if (h20.attach(0x686C20, &sseClassifyPointFrustum) == .ok) installed += 1; // 3.2M/7.5s
if (h21.attach(0x6856C0, probeDetour(FC2r, &h21, &cnt[21])) == .ok) installed += 1;
if (h22.attach(0x686000, probeDetour(FC3r, &h22, &cnt[22])) == .ok) installed += 1;
if (h23.attach(0x686180, probeDetour(FC2r, &h23, &cnt[23])) == .ok) installed += 1;
if (h24.attach(0x6DC5A0, &sseCheckBoxLineIntersect) == .ok) installed += 1; // 2.7M/7.5s
if (h25.attach(0x50A840, probeDetour(FC4r, &h25, &cnt[25])) == .ok) installed += 1;
if (h26.attach(0x509220, probeDetour(FC4r, &h26, &cnt[26])) == .ok) installed += 1;
if (h27.attach(0x6869C0, &sseTestOBBFrustum) == .ok) installed += 1;
if (h28.attach(0x686B80, &sseTestSphereFrustum) == .ok) installed += 1; // 375K/7.5s
// Cat 6: Rendering
if (h29.attach(0x6ABC40, probeDetour(FC4r, &h29, &cnt[29])) == .ok) installed += 1;
if (h30.attach(0x6ABE60, probeDetour(FC4r, &h30, &cnt[30])) == .ok) installed += 1;
if (h31.attach(0x6AD7E0, probeDetour(FC4r, &h31, &cnt[31])) == .ok) installed += 1;
if (h32.attach(0x6AFAD0, probeDetour(FC1v, &h32, &cnt[32])) == .ok) installed += 1;
if (h33.attach(0x6B7070, probeDetour(FC3r, &h33, &cnt[33])) == .ok) installed += 1;
if (h34.attach(0x6B8B70, probeDetour(FC4r, &h34, &cnt[34])) == .ok) installed += 1;
if (h35.attach(0x6B88E0, probeDetour(TC3r, &h35, &cnt[35])) == .ok) installed += 1;
if (h36.attach(0x6B8C60, probeDetour(TC3r, &h36, &cnt[36])) == .ok) installed += 1;
if (h37.attach(0x6BC370, probeDetour(TC4v, &h37, &cnt[37])) == .ok) installed += 1;
if (h38.attach(0x6C15D0, probeDetour(FC1v, &h38, &cnt[38])) == .ok) installed += 1;
// Cat 7: Bone/model
if (h39.attach(0x71AE90, probeDetour(FC4r, &h39, &cnt[39])) == .ok) installed += 1;
if (h40.attach(0x71AF20, probeDetour(FC4r, &h40, &cnt[40])) == .ok) installed += 1;
if (h41.attach(0x71B6A0, probeDetour(TC2v, &h41, &cnt[41])) == .ok) installed += 1;
if (h42.attach(0x71BC70, &sseAddVec3ToAccumulator) == .ok) installed += 1; // 136K/7.5s
if (h43.attach(0x71BF60, &sseAddToColorAccumulator) == .ok) installed += 1;
if (h44.attach(0x71C160, probeDetour(FC1v, &h44, &cnt[44])) == .ok) installed += 1;
if (h45.attach(0x71C2F0, probeDetour(FC1v, &h45, &cnt[45])) == .ok) installed += 1;
if (h46.attach(0x71C4E0, probeDetour(TC2r, &h46, &cnt[46])) == .ok) installed += 1;
if (h47.attach(0x718960, probeDetour(TC1v, &h47, &cnt[47])) == .ok) installed += 1;
if (h48.attach(0x719370, probeDetour(TC2v, &h48, &cnt[48])) == .ok) installed += 1;
if (h49.attach(0x712D50, probeDetour(TC3r, &h49, &cnt[49])) == .ok) installed += 1;
if (h50.attach(0x713680, probeDetour(TC2r, &h50, &cnt[50])) == .ok) installed += 1;
// Cat 8: Particle
if (h51.attach(0x7B3A10, probeDetour(TC2r, &h51, &cnt[51])) == .ok) installed += 1;
if (h52.attach(0x7B5A10, probeDetour(TC3v, &h52, &cnt[52])) == .ok) installed += 1;
if (h53.attach(0x7B7E60, probeDetour(TC3v, &h53, &cnt[53])) == .ok) installed += 1;
// Cat 9: Geometry
if (h54.attach(0x7C0570, &sseQuatSlerp) == .ok) installed += 1;
if (h55.attach(0x7C2040, probeDetour(FC3r, &h55, &cnt[55])) == .ok) installed += 1;
if (h56.attach(0x7C22B0, probeDetour(FC5r, &h56, &cnt[56])) == .ok) installed += 1;
if (h57.attach(0x7C5880, probeDetour(FC1v, &h57, &cnt[57])) == .ok) installed += 1;
// Misc
if (h58.attach(0x70A060, probeDetour(FC3r, &h58, &cnt[58])) == .ok) installed += 1;
if (h59.attach(0x70AE10, probeDetour(FC3r, &h59, &cnt[59])) == .ok) installed += 1;
// Batch 2: remaining explored functions
if (h60.attach(0x4183D0, probeDetour(TC2r, &h60, &cnt[60])) == .ok) installed += 1;
if (h61.attach(0x453480, probeDetour(TC4r, &h61, &cnt[61])) == .ok) installed += 1;
if (h62.attach(0x453580, probeDetour(TC5v, &h62, &cnt[62])) == .ok) installed += 1;
if (h63.attach(0x4541B0, probeDetour(TC4r, &h63, &cnt[63])) == .ok) installed += 1;
if (h64.attach(0x483340, probeDetour(FC5r, &h64, &cnt[64])) == .ok) installed += 1;
if (h65.attach(0x509BF0, probeDetour(FC6r, &h65, &cnt[65])) == .ok) installed += 1;
if (h66.attach(0x593040, probeDetour(TC2r, &h66, &cnt[66])) == .ok) installed += 1;
// h67 disabled: 0x5C7010 ConvertPixelsToScreenAlt called with ECX=0 (valid), crashes thiscall probe
// if (h67.attach(0x5C7010, probeDetour(TC2d, &h67, &cnt[67])) == .ok) installed += 1;
if (h68.attach(0x5E22D0, probeDetour(FC2r, &h68, &cnt[68])) == .ok) installed += 1;
if (h69.attach(0x5F6280, probeDetour(TC3r, &h69, &cnt[69])) == .ok) installed += 1;
if (h70.attach(0x5F8DC0, probeDetour(TC6v, &h70, &cnt[70])) == .ok) installed += 1;
if (h71.attach(0x614CD0, probeDetour(FC3r, &h71, &cnt[71])) == .ok) installed += 1;
if (h72.attach(0x616AF0, probeDetour(TC4r, &h72, &cnt[72])) == .ok) installed += 1;
if (h73.attach(0x616BF0, probeDetour(FC1r, &h73, &cnt[73])) == .ok) installed += 1;
if (h74.attach(0x618920, probeDetour(FC1r, &h74, &cnt[74])) == .ok) installed += 1;
if (h75.attach(0x675AC0, probeDetour(FC1v, &h75, &cnt[75])) == .ok) installed += 1;
if (h76.attach(0x67C820, probeDetour(TC2d, &h76, &cnt[76])) == .ok) installed += 1;
if (h77.attach(0x67CA00, probeDetour(TC2d, &h77, &cnt[77])) == .ok) installed += 1;
if (h78.attach(0x67CA90, probeDetour(TC3v, &h78, &cnt[78])) == .ok) installed += 1;
if (h79.attach(0x67CB60, probeDetour(TC4r, &h79, &cnt[79])) == .ok) installed += 1;
if (h80.attach(0x6818B0, probeDetour(FC2v, &h80, &cnt[80])) == .ok) installed += 1;
if (h81.attach(0x683F80, probeDetour(SC0v, &h81, &cnt[81])) == .ok) installed += 1;
if (h82.attach(0x68B0D0, probeDetour(TC6v, &h82, &cnt[82])) == .ok) installed += 1;
if (h83.attach(0x68D540, probeDetour(FC1v, &h83, &cnt[83])) == .ok) installed += 1;
if (h84.attach(0x699330, &sseIsPointInsideBounds) == .ok) installed += 1; // 1.7M/7.5s
if (h85.attach(0x69B1C0, probeDetour(FC2r, &h85, &cnt[85])) == .ok) installed += 1;
if (h86.attach(0x69B6D0, probeDetour(FC5r, &h86, &cnt[86])) == .ok) installed += 1;
if (h87.attach(0x6A8050, probeDetour(TC2r, &h87, &cnt[87])) == .ok) installed += 1;
if (h88.attach(0x6AADC0, probeDetour(FC6r, &h88, &cnt[88])) == .ok) installed += 1;
if (h89.attach(0x6CF6C0, probeDetour(FC3d, &h89, &cnt[89])) == .ok) installed += 1;
if (h90.attach(0x6FA1A0, probeDetour(FC2r, &h90, &cnt[90])) == .ok) installed += 1;
if (h91.attach(0x6FA700, probeDetour(FC2r, &h91, &cnt[91])) == .ok) installed += 1;
if (h92.attach(0x6FA7A0, probeDetour(FC2r, &h92, &cnt[92])) == .ok) installed += 1;
if (h93.attach(0x7B4BF0, probeDetour(TC2r, &h93, &cnt[93])) == .ok) installed += 1;
if (h94.attach(0x7B76C0, probeDetour(TC4v, &h94, &cnt[94])) == .ok) installed += 1;
if (h95.attach(0x7B8890, probeDetour(TC4v, &h95, &cnt[95])) == .ok) installed += 1;
if (h96.attach(0x7B8D70, probeDetour(TC4v, &h96, &cnt[96])) == .ok) installed += 1;
if (h97.attach(0x7BA200, probeDetour(TC4v, &h97, &cnt[97])) == .ok) installed += 1;
// Batch 3: newly explored + previously skipped
if (h98.attach(0x749280, &sseCalculateSinCos) == .ok) installed += 1;
if (h99.attach(0x7BE5B0, &sseCreateZRotMat3x3) == .ok) installed += 1;
if (h100.attach(0x76D680, probeDetour(FC3r, &h100, &cnt[100])) == .ok) installed += 1;
if (h101.attach(0x7786A0, probeDetour(TC2r, &h101, &cnt[101])) == .ok) installed += 1;
if (h102.attach(0x69BFF0, probeDetour(FC5r, &h102, &cnt[102])) == .ok) installed += 1;
if (h103.attach(0x7BCEF0, &sseTransposeMat4x4) == .ok) installed += 1;
if (h104.attach(0x7BB420, &sseMulMat3x4InPlace) == .ok) installed += 1;
if (h105.attach(0x7B2A50, probeDetour(TC3r, &h105, &cnt[105])) == .ok) installed += 1;
if (h106.attach(0x7B7A80, &ssePackParticleColor) == .ok) installed += 1;
if (h107.attach(0x7B7B10, &sseSetParticleAlpha) == .ok) installed += 1;
if (h108.attach(0x602630, &sseVec3Dot) == .ok) installed += 1;
if (h109.attach(0x686640, probeDetour(FC1v, &h109, &cnt[109])) == .ok) installed += 1;
if (h110.attach(0x686820, &sseTranslateBoundingVol) == .ok) installed += 1;
if (h111.attach(0x6868E0, &sseTransformBoundingVol) == .ok) installed += 1;
if (h112.attach(0x6720F0, &sseNormalizeVec3InPlace) == .ok) installed += 1;
if (h113.attach(0x5C8710, probeDetour(TC10r, &h113, &cnt[113])) == .ok) installed += 1;
// h114 (GetFPUControlWord 0x40CF81) — modifies FPU control word via FLDCW as side
// effect; generic probe callOriginal wrapper may emit FPU instructions that corrupt
// the control word state after the original returns. Needs naked asm like ftol.
// if (h114.attach(0x40CF81, probeDetour(SC2r, &h114, &cnt[114])) == .ok) installed += 1;
}
// ftol patched directly via installPatches(), no detour needed
// Periodic reporting hook
if (world_update_hook.attach(0x482EA0, &worldUpdateDetour) == .ok) {
log_state.print("silicon: world update reporter installed\n");
}
log_state.fmt("silicon: {d}/{d} probe hooks installed\n", .{ installed, @as(u32, NUM_PROBES) });
}
/// Dump probe hit counts and reset. Called periodically from worldUpdateDetour
/// and once from removeHooks on shutdown.
fn reportProbes() void {
log_state.print("silicon: probe hit counts:\n");
for (&probe_infos) |*info| {
const c = @atomicRmw(u32, &cnt[info.idx], .Xchg, 0, .monotonic);
if (c > 0) {
log_state.fmt(" {s}: {d}\n", .{ info.name, c });
}
}
}
pub fn removeHooks() void {
if (g_is_hook_owner) {
world_update_hook.detach();
reportProbes();
log_state.close();
mod_mutex.release(&g_mutex);
}
g_is_hook_owner = false;
}