From 9c96a94dc162bc95da7b9b0b38045b47f00a6379 Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Mon, 9 Mar 2026 10:28:55 -0700 Subject: [PATCH] Migrate inline asm call sites to hook.call() Replace hand-written inline asm blocks with hook.call() typed function pointer dispatch across 10 files. Also migrates 4 D3D9 COM vtable NULL-dispatch blocks using ?*anyopaque optional pointers. Net removal: ~350 lines of inline asm replaced by single-line calls. --- src/addons.zig | 7 +- src/bigcursor/bigcursor.zig | 13 +-- src/interact/interact.zig | 52 +++-------- src/lua.zig | 12 +-- src/main.zig | 43 ++------- src/markers/markers.zig | 107 +++------------------ src/minimapicons/minimapicons.zig | 149 +++++------------------------- src/outline/d3d9_hook.zig | 80 +++------------- src/outline/wow.zig | 35 ++----- src/transmogfix/transmogfix.zig | 24 +---- 10 files changed, 85 insertions(+), 437 deletions(-) diff --git a/src/addons.zig b/src/addons.zig index 5a41c7d..808f1b5 100644 --- a/src/addons.zig +++ b/src/addons.zig @@ -343,12 +343,7 @@ fn hideAddonFromList(name: [*:0]const u8) void { } fn callLoadAddonTOC(addon_name: [*:0]const u8) void { - asm volatile ( - \\call *%[func] - : - : [_] "{ecx}" (@intFromPtr(addon_name)), - [func] "{eax}" (@as(u32, 0x0051c9b0)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn ([*:0]const u8) callconv(fc) void, 0x0051c9b0, .{addon_name}); } // ============================================================================= diff --git a/src/bigcursor/bigcursor.zig b/src/bigcursor/bigcursor.zig index b0905ea..17f7b3f 100644 --- a/src/bigcursor/bigcursor.zig +++ b/src/bigcursor/bigcursor.zig @@ -15,6 +15,7 @@ const mod_mutex = @import("../mutex.zig"); const WINAPI = std.builtin.CallingConvention.winapi; const sc: std.builtin.CallingConvention = .{ .x86_stdcall = .{} }; const fc: std.builtin.CallingConvention = .{ .x86_fastcall = .{} }; +const tc: std.builtin.CallingConvention = .{ .x86_thiscall = .{} }; pub const module_name: [*:0]const u8 = "bigcursor"; @@ -556,17 +557,7 @@ fn hkShowCursor(device: *anyopaque, bShow: i32) callconv(sc) i32 { // ============================================================================= fn luaPushNumber(L_ptr: usize, n: f64) void { - const raw: [2]u32 = @bitCast(n); - asm volatile ( - \\push %[hi] - \\push %[lo] - \\call *%[func] - : - : [_] "{ecx}" (L_ptr), - [lo] "r" (raw[0]), - [hi] "r" (raw[1]), - [func] "r" (@as(u32, 0x6F3810)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn (usize, f64) callconv(tc) void, 0x6F3810, .{ L_ptr, n }); } pub fn luaSetCursorScale(L: *anyopaque) callconv(.c) u32 { diff --git a/src/interact/interact.zig b/src/interact/interact.zig index 02dbd28..787cd42 100644 --- a/src/interact/interact.zig +++ b/src/interact/interact.zig @@ -51,23 +51,21 @@ const C3Vector = struct { z: f32, }; +// ============================================================================= +// Calling Conventions +// ============================================================================= + +const sc: std.builtin.CallingConvention = .{ .x86_stdcall = .{} }; +const tc: std.builtin.CallingConvention = .{ .x86_thiscall = .{} }; + // ============================================================================= // Game API // ============================================================================= fn getObjectPointer(guid: u64) u32 { - // __stdcall(u32 guidLow, u32 guidHigh): both on stack, callee cleans (ret 8) const lo: u32 = @truncate(guid); const hi: u32 = @truncate(guid >> 32); - return asm volatile ( - \\push %[hi] - \\push %[lo] - \\call *%[func] - : [ret] "={eax}" (-> u32), - : [lo] "r" (lo), - [hi] "r" (hi), - [func] "r" (@as(u32, Offsets.FUN_GET_OBJECT_POINTER)), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true }); + return hook.call(fn (u32, u32) callconv(sc) u32, Offsets.FUN_GET_OBJECT_POINTER, .{ lo, hi }); } fn isInWorld() bool { @@ -106,30 +104,13 @@ fn isUnitSkinnable(unit: u32) bool { } fn setTarget(guid: u64) void { - // __stdcall(uint64_t guid): push hi, push lo, callee cleans 8 const lo: u32 = @truncate(guid); const hi: u32 = @truncate(guid >> 32); - asm volatile ( - \\push %[hi] - \\push %[lo] - \\call *%[func] - : - : [lo] "r" (lo), - [hi] "r" (hi), - [func] "r" (@as(u32, Offsets.FUN_SET_TARGET)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn (u32, u32) callconv(sc) void, Offsets.FUN_SET_TARGET, .{ lo, hi }); } fn rightClickInteract(pointer: u32, autoloot: i32, fun_ptr: usize) void { - // __thiscall: ECX=this (pointer), push autoloot, callee cleans 4 - asm volatile ( - \\push %[autoloot] - \\call *%[func] - : - : [_] "{ecx}" (pointer), - [autoloot] "r" (autoloot), - [func] "r" (fun_ptr), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn (u32, i32) callconv(tc) void, fun_ptr, .{ pointer, autoloot }); } // ============================================================================= @@ -165,17 +146,7 @@ fn luaToNumber(L: *anyopaque, idx: i32) f64 { } fn luaPrintError(L: *anyopaque, msg: [*:0]const u8) void { - // __cdecl(lua_State*, const char*): both on stack, caller cleans - asm volatile ( - \\push %[msg] - \\push %[L] - \\call *%[func] - \\add $8, %%esp - : - : [L] "r" (@intFromPtr(L)), - [msg] "r" (@intFromPtr(msg)), - [func] "r" (@as(u32, Offsets.LUA_ERROR)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn (*anyopaque, [*:0]const u8) callconv(.c) void, Offsets.LUA_ERROR, .{ L, msg }); } // ============================================================================= @@ -393,7 +364,6 @@ pub fn lootAllCorpses(_: *anyopaque) callconv(.c) u32 { // Per-frame hook for processing the loot queue. // ============================================================================= -const tc: std.builtin.CallingConvention = .{ .x86_thiscall = .{} }; const SceneEndFn = fn (u32) callconv(tc) void; var scene_end_hook: hook.Detour(SceneEndFn) = .{}; diff --git a/src/lua.zig b/src/lua.zig index 1de8f5d..8633f67 100644 --- a/src/lua.zig +++ b/src/lua.zig @@ -127,16 +127,8 @@ pub fn pcall(L: State, nargs: i32, nresults: i32, errfunc: i32) i32 { } pub fn luaError(L: State, msg: [*:0]const u8) void { - asm volatile ( - \\push %[msg] - \\push %[L] - \\call *%[func] - \\add $8, %%esp - : - : [L] "r" (@intFromPtr(L)), - [msg] "r" (@intFromPtr(msg)), - [func] "r" (@as(u32, 0x6F4940)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + const hook = @import("zhook"); + hook.call(fn (*anyopaque, [*:0]const u8) callconv(.c) void, 0x6F4940, .{ L, msg }); } pub const LuaReg = extern struct { diff --git a/src/main.zig b/src/main.zig index ac43fe7..a59de36 100644 --- a/src/main.zig +++ b/src/main.zig @@ -59,17 +59,9 @@ fn registerFunction(name: [*:0]const u8, func_addr: usize) void { } fn allocateGameBuffer(size: u32) ?[*]u8 { - return asm volatile ( - \\push $0 - \\push $0 - \\push %[src] - \\push %[size] - \\call *%[func] - : [ret] "={eax}" (-> ?[*]u8), - : [size] "r" (size), - [src] "r" (@intFromPtr(@as([*:0]const u8, "weirdutils"))), - [func] "r" (@as(u32, 0x6462E0)), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true }); + return hook.call(fn (u32, u32, u32, u32) callconv(sc) ?[*]u8, 0x6462E0, .{ + size, @intFromPtr(@as([*:0]const u8, "weirdutils")), 0, 0, + }); } // ============================================================================= @@ -207,38 +199,19 @@ fn isFakeFileContext(ctx_addr: u32) bool { /// Call initializeFileContext (0x647290) - __thiscall(ECX=ctx, type) fn callInitFileContext(ctx: [*]u8, file_type: u32) void { - asm volatile ( - \\push %[ftype] - \\call *%[func] - : - : [_] "{ecx}" (@intFromPtr(ctx)), - [ftype] "r" (file_type), - [func] "r" (@as(u32, 0x647290)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn (u32, u32) callconv(tc) void, 0x647290, .{ @intFromPtr(ctx), file_type }); } /// Call cleanupFileContext (0x6472d0) - __thiscall(ECX=ctx) fn callCleanupFileContext(ctx: [*]u8) void { - asm volatile ( - \\call *%[func] - : - : [_] "{ecx}" (@intFromPtr(ctx)), - [func] "r" (@as(u32, 0x6472d0)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn (u32) callconv(tc) void, 0x6472d0, .{@intFromPtr(ctx)}); } /// Free a buffer via FreeMemory/SMemFree (0x646430) - __stdcall(ptr, src, flags) fn freeGameBuffer(ptr: [*]u8) void { - asm volatile ( - \\push $0xffffffff - \\push %[src] - \\push %[ptr] - \\call *%[func] - : - : [ptr] "r" (@intFromPtr(ptr)), - [src] "r" (@intFromPtr(@as([*:0]const u8, "weirdutils"))), - [func] "r" (@as(u32, 0x646430)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn (u32, u32, u32) callconv(sc) void, 0x646430, .{ + @intFromPtr(ptr), @intFromPtr(@as([*:0]const u8, "weirdutils")), 0xffffffff, + }); } // --- Hook 1: openFileWithOptions (0x6477c0) --- diff --git a/src/markers/markers.zig b/src/markers/markers.zig index f008b31..fe5e797 100644 --- a/src/markers/markers.zig +++ b/src/markers/markers.zig @@ -116,22 +116,16 @@ var despawning: [MAX_DESPAWNING]?DespawningEntity = .{null} ** MAX_DESPAWNING; const fc = std.builtin.CallingConvention{ .x86_fastcall = .{} }; const sc = std.builtin.CallingConvention{ .x86_stdcall = .{} }; +const tc = std.builtin.CallingConvention{ .x86_thiscall = .{} }; // ============================================================================= // Permission check - leader or raid officer required // ============================================================================= /// Get local player GUID via GetPlayerGUID (0x468550). -/// __fastcall(), no params, returns EAX(low):EDX(high). +/// __fastcall(), no params, returns u64 via EDX:EAX. fn getPlayerGUID() u64 { - var lo: u32 = undefined; - var hi: u32 = undefined; - asm volatile ("call *%[func]" - : [_] "={eax}" (lo), - [_] "={edx}" (hi), - : [func] "r" (o.FN_GET_PLAYER_GUID), - : .{ .ecx = true, .memory = true, .cc = true }); - return (@as(u64, hi) << 32) | lo; + return hook.call(fn () callconv(fc) u64, o.FN_GET_PLAYER_GUID, .{}); } /// Look up a player name from the name cache by GUID. @@ -139,27 +133,9 @@ fn getPlayerGUID() u64 { fn getNameFromGUID(guid_lo: u32, guid_hi: u32) ?[*:0]const u8 { if (guid_lo == 0 and guid_hi == 0) return null; var name_buf: [2]u32 = .{ 0, 0 }; - const stack_args = [6]u32{ - guid_lo, - guid_hi, - @intFromPtr(&name_buf), - 0, - 0, - 0, - }; - const result: u32 = asm volatile ( - \\ push 20(%[a]) - \\ push 16(%[a]) - \\ push 12(%[a]) - \\ push 8(%[a]) - \\ push 4(%[a]) - \\ push (%[a]) - \\ call *%[func] - : [ret] "={eax}" (-> u32), - : [_] "{ecx}" (@as(u32, o.NAME_CACHE_OBJ)), - [a] "r" (&stack_args), - [func] "r" (@as(u32, o.FN_NAME_CACHE_LOOKUP)), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true }); + const result = hook.call(fn (u32, u32, u32, u32, u32, u32, u32) callconv(tc) u32, o.FN_NAME_CACHE_LOOKUP, .{ + o.NAME_CACHE_OBJ, guid_lo, guid_hi, @intFromPtr(&name_buf), 0, 0, 0, + }); return if (result != 0) @ptrFromInt(result) else null; } @@ -310,39 +286,15 @@ fn getCursorTerrainPosition() ?Vec3 { /// CreateEntityInstance_WithAttachment - __fastcall, RET 0x14. fn createEntityInstance(path: [*:0]const u8, pos: *[3]f32, facing: f32, flags: u32, update_now: u32) ?*anyopaque { - const facing_bits: u32 = @bitCast(facing); - const stack_args = [5]u32{ - facing_bits, - flags, - update_now, - 0, - 0, - }; - - const result: u32 = asm volatile ( - \\ push 16(%[a]) - \\ push 12(%[a]) - \\ push 8(%[a]) - \\ push 4(%[a]) - \\ push (%[a]) - \\ call *%[func] - : [ret] "={eax}" (-> u32), - : [_] "{ecx}" (@intFromPtr(path)), - [_] "{edx}" (@intFromPtr(pos)), - [a] "r" (&stack_args), - [func] "r" (o.FN_CREATE_ENTITY_INSTANCE), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true }); - + const result = hook.call(fn ([*:0]const u8, *[3]f32, f32, u32, u32, u32, u32) callconv(fc) u32, o.FN_CREATE_ENTITY_INSTANCE, .{ + path, pos, facing, flags, update_now, 0, 0, + }); return if (result != 0) @ptrFromInt(result) else null; } /// CleanupEntity_ProcessAttachments - __fastcall(ECX=entity), no stack params. fn cleanupEntity(obj: *anyopaque) void { - asm volatile ("call *%[func]" - : - : [_] "{ecx}" (@intFromPtr(obj)), - [func] "r" (o.FN_CLEANUP_ENTITY), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn (*anyopaque) callconv(fc) void, o.FN_CLEANUP_ENTITY, .{obj}); } // ============================================================================= @@ -356,31 +308,9 @@ fn playAnimation(entity: *anyopaque, anim_id: u32, queue: bool) void { const model = hook.readMem(u32, entity_addr + 0x88); if (model == 0 or model < 0x10000) return; - const speed_bits: u32 = @bitCast(@as(f32, 1.0)); - const stack_args = [7]u32{ - 0xFFFFFFFF, // boneIndex: all bones - anim_id, - @bitCast(@as(i32, -1)), // seqIndex: random - 0, // animData: NULL - speed_bits, // speed: 1.0 - 1, // blendMode: smooth blend - @intFromBool(queue), - }; - - asm volatile ( - \\ push 24(%[a]) - \\ push 20(%[a]) - \\ push 16(%[a]) - \\ push 12(%[a]) - \\ push 8(%[a]) - \\ push 4(%[a]) - \\ push (%[a]) - \\ call *%[func] - : - : [_] "{ecx}" (model), - [a] "r" (&stack_args), - [func] "r" (o.FN_PLAY_BONE_ANIMATION), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn (u32, u32, u32, i32, u32, u32, u32, u32) callconv(tc) void, o.FN_PLAY_BONE_ANIMATION, .{ + model, 0xFFFFFFFF, anim_id, -1, 0, @as(u32, @bitCast(@as(f32, 1.0))), 1, @intFromBool(queue), + }); } /// Clean up despawning entities whose Decay animation has finished. @@ -475,16 +405,7 @@ fn spawnEntity(index: usize, pos: Vec3) bool { /// SetUnitPositionAndOrientation - __fastcall(ECX=positionData, EDX=pos), 1 stack param. fn setUnitPositionAndOrientation(entity: *anyopaque, pos: *[3]f32, facing: f32) void { - const facing_bits: u32 = @bitCast(facing); - asm volatile ( - \\ push %[facing] - \\ call *%[func] - : - : [_] "{ecx}" (@intFromPtr(entity)), - [_] "{edx}" (@intFromPtr(pos)), - [facing] "r" (facing_bits), - [func] "r" (@as(u32, 0x698e20)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn (*anyopaque, *[3]f32, f32) callconv(fc) void, 0x698e20, .{ entity, pos, facing }); } /// Remove only the live entity for a marker slot (def untouched). diff --git a/src/minimapicons/minimapicons.zig b/src/minimapicons/minimapicons.zig index 7ecfd99..00d2f4e 100644 --- a/src/minimapicons/minimapicons.zig +++ b/src/minimapicons/minimapicons.zig @@ -21,6 +21,7 @@ const mod_mutex = @import("../mutex.zig"); const fc: std.builtin.CallingConvention = .{ .x86_fastcall = .{} }; const tc: std.builtin.CallingConvention = .{ .x86_thiscall = .{} }; +const sc: std.builtin.CallingConvention = .{ .x86_stdcall = .{} }; pub const module_name: [*:0]const u8 = "minimapicons"; @@ -364,15 +365,7 @@ fn isValidPtr(addr: u32) bool { fn getObjectByGUID(guid_lo: u32, guid_hi: u32) u32 { if (guid_lo == 0 and guid_hi == 0) return 0; - return asm volatile ( - \\push %[hi] - \\push %[lo] - \\call *%[func] - : [ret] "={eax}" (-> u32), - : [lo] "r" (guid_lo), - [hi] "r" (guid_hi), - [func] "r" (@as(u32, ADDR.GetObjectByGUID)), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true }); + return hook.call(fn (u32, u32) callconv(sc) u32, ADDR.GetObjectByGUID, .{ guid_lo, guid_hi }); } fn getObjectType(obj: u32) u32 { @@ -468,14 +461,7 @@ fn getSummonedByGUID(obj: u32) u64 { } fn getActivePlayerGUID() u64 { - var lo: u32 = undefined; - var hi: u32 = undefined; - asm volatile ("call *%[func]" - : [_] "={eax}" (lo), - [_] "={edx}" (hi), - : [func] "r" (@as(u32, ADDR.ClntObjMgrGetActivePlayer)), - : .{ .ecx = true, .memory = true, .cc = true }); - return (@as(u64, hi) << 32) | lo; + return hook.call(fn () callconv(fc) u64, ADDR.ClntObjMgrGetActivePlayer, .{}); } fn getActivePlayerObject() u32 { @@ -487,14 +473,7 @@ fn getActivePlayerObject() u32 { /// UnitReaction: __thiscall(localPlayer_ECX, unit_stack) -> int (>=4 = friendly). fn unitReaction(local_player: u32, unit: u32) i32 { if (local_player == 0 or unit == 0) return 0; - return asm volatile ( - \\push %[unit] - \\call *%[func] - : [ret] "={eax}" (-> i32), - : [_] "{ecx}" (local_player), - [unit] "r" (unit), - [func] "r" (@as(u32, ADDR.UnitReaction)), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true }); + return hook.call(fn (u32, u32) callconv(tc) i32, ADDR.UnitReaction, .{ local_player, unit }); } /// Check if a unit passes faction/friendliness filters. @@ -535,11 +514,7 @@ fn isUnitAllowed(obj: u32, local_player: u32) bool { /// CallSpellCastHandler: __fastcall(obj_ECX) -> char (bool via virtual dispatch). /// This is what IsValidInteractionTarget uses for type 0x21 (GO). fn isGoInteractable(obj: u32) bool { - const result: u8 = @truncate(asm volatile ("call *%[func]" - : [ret] "={eax}" (-> u32), - : [_] "{ecx}" (obj), - [func] "r" (@as(u32, ADDR.CallSpellCastHandler)), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true })); + const result: u8 = @truncate(hook.call(fn (u32) callconv(fc) u32, ADDR.CallSpellCastHandler, .{obj})); if (result == 0) { con.fmt("[minimapicons] GO 0x{x} rejected: not interactable (entry={d})\n", .{ obj, getObjectEntry(obj) }); } @@ -574,14 +549,7 @@ fn getObjectPosition(obj: u32) ?C3Vector { if (!isValidPtr(get_pos_fn)) return null; var pos: C3Vector = undefined; // __thiscall(obj_ECX, &pos_stack) → C3Vector* - asm volatile ( - \\push %[out] - \\call *%[func] - : - : [_] "{ecx}" (obj), - [out] "r" (@intFromPtr(&pos)), - [func] "r" (get_pos_fn), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + _ = hook.call(fn (u32, *C3Vector) callconv(tc) u32, get_pos_fn, .{ obj, &pos }); return pos; } @@ -600,32 +568,10 @@ fn worldPosToMinimapCoords( ) void { // WorldPosToMinimapFrameCoords: __fastcall(out_ECX, edx, C3Vector cur, float radius, // float worldX, float worldY, float layoutScale, float unkScale) - // 8 stack params (32 bytes), callee cleanup - const args = [8]u32{ - @bitCast(cur.x), - @bitCast(cur.y), - @bitCast(cur.z), - @bitCast(radius), - @bitCast(world_x), - @bitCast(world_y), - @bitCast(layout_scale), - @bitCast(unk_scale), - }; - _ = asm volatile ( - \\pushl 28(%[args]) - \\pushl 24(%[args]) - \\pushl 20(%[args]) - \\pushl 16(%[args]) - \\pushl 12(%[args]) - \\pushl 8(%[args]) - \\pushl 4(%[args]) - \\pushl (%[args]) - \\call *%[func] - : [ret] "={eax}" (-> u32), - : [_] "{ecx}" (@intFromPtr(out)), - [args] "r" (&args), - [func] "r" (@as(u32, ADDR.WorldPosToMinimapCoords)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + // EDX unused — use thiscall (ECX=this, rest on stack) + _ = hook.call(fn (*C2Vector, f32, f32, f32, f32, f32, f32, f32, f32) callconv(tc) u32, ADDR.WorldPosToMinimapCoords, .{ + out, cur.x, cur.y, cur.z, radius, world_x, world_y, layout_scale, unk_scale, + }); } fn getFrameUnkScale(info: u32) f32 { @@ -637,12 +583,7 @@ fn getFrameUnkScale(info: u32) f32 { const fn_addr = hook.readMem(u32, vtable + 7 * 4); if (!isValidPtr(fn_addr)) return 1.0; // __thiscall(fsp_ECX) → f32 on FPU ST(0) - return asm volatile ( - \\call *%[func] - : [ret] "={st}" (-> f32), - : [_] "{ecx}" (fsp), - [func] "r" (fn_addr), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + return hook.call(fn (u32) callconv(tc) f32, fn_addr, .{fsp}); } // ============================================================================= @@ -680,17 +621,9 @@ fn loadTexture(path: [*:0]const u8) u32 { defer status.deinit(); // TextureCreate: __fastcall(filename_ECX, status_EDX, texFlags, unk1, unk2) - const texture: u32 = asm volatile ( - \\pushl $1 - \\pushl $0 - \\push %[flags] - \\call *%[func] - : [ret] "={eax}" (-> u32), - : [_] "{ecx}" (@intFromPtr(path)), - [_] "{edx}" (@intFromPtr(&status)), - [flags] "r" (g_default_tex_flags), - [func] "r" (@as(u32, ADDR.TextureCreate)), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true }); + const texture = hook.call(fn ([*:0]const u8, *CStatus, u32, u32, u32) callconv(fc) u32, ADDR.TextureCreate, .{ + path, &status, g_default_tex_flags, 0, 1, + }); if (!status.ok() or texture == 0) { con.print("[minimapicons] Failed to load texture\n"); @@ -709,20 +642,9 @@ fn initTexFlags() u32 { var flags: u32 = 0; // CGxTexFlags constructor: __thiscall(this, filter=0, wrapU=0, wrapV=0, // forceMipTracking=0, generateMipMaps=0, renderTarget=0, maxAnisotropy=0, unknownFlag=1) - asm volatile ( - \\pushl $1 - \\pushl $0 - \\pushl $0 - \\pushl $0 - \\pushl $0 - \\pushl $0 - \\pushl $0 - \\pushl $0 - \\call *%[func] - : - : [_] "{ecx}" (@intFromPtr(&flags)), - [func] "r" (@as(u32, ADDR.CGxTexFlagsInit)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + hook.call(fn (*u32, u32, u32, u32, u32, u32, u32, u32, u32) callconv(tc) void, ADDR.CGxTexFlagsInit, .{ + &flags, 0, 0, 0, 0, 0, 0, 0, 1, + }); return flags; } @@ -736,15 +658,9 @@ fn getGxTex(texture: u32) u32 { status.init(); defer status.deinit(); - const gx_tex: u32 = asm volatile ( - \\push %[status] - \\call *%[func] - : [ret] "={eax}" (-> u32), - : [_] "{ecx}" (texture), - [_] "{edx}" (@as(u32, 1)), - [status] "r" (@intFromPtr(&status)), - [func] "r" (@as(u32, ADDR.TextureGetGxTex)), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true }); + const gx_tex = hook.call(fn (u32, u32, *CStatus) callconv(fc) u32, ADDR.TextureGetGxTex, .{ + texture, 1, &status, + }); if (!status.ok()) return 0; return gx_tex; @@ -767,7 +683,9 @@ fn drawMinimapBlip(pos: C2Vector, scale: f32) void { // GxPrimLockVertexPtrs(count=4, vertices, vertStride=12, normal, 0, color, 0, // null, 0, texCoords, 8, null, 0) - const lock_args = [11]u32{ + hook.call(fn (u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32) callconv(fc) void, ADDR.GxPrimLockVertexPtrs, .{ + 4, // count (ECX) + @intFromPtr(&vertices), // vertices (EDX) 12, // vertStride @as(u32, ADDR.BlipNormal), // normal 0, // normalStride @@ -779,26 +697,7 @@ fn drawMinimapBlip(pos: C2Vector, scale: f32) void { 8, // texStride 0, // texCoords2 (null) 0, // tex2Stride - }; - asm volatile ( - \\pushl 40(%[args]) - \\pushl 36(%[args]) - \\pushl 32(%[args]) - \\pushl 28(%[args]) - \\pushl 24(%[args]) - \\pushl 20(%[args]) - \\pushl 16(%[args]) - \\pushl 12(%[args]) - \\pushl 8(%[args]) - \\pushl 4(%[args]) - \\pushl (%[args]) - \\call *%[func] - : - : [_] "{ecx}" (@as(u32, 4)), // count - [_] "{edx}" (@intFromPtr(&vertices)), // vertices - [args] "r" (&lock_args), - [func] "r" (@as(u32, ADDR.GxPrimLockVertexPtrs)), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); + }); // GxPrimDrawElements(TriangleStrip=4, count=4, indices) const drawElements: *const fn (u32, u32, u32) callconv(fc) void = @ptrFromInt(ADDR.GxPrimDrawElements); diff --git a/src/outline/d3d9_hook.zig b/src/outline/d3d9_hook.zig index 56bd82a..aa3fbcd 100644 --- a/src/outline/d3d9_hook.zig +++ b/src/outline/d3d9_hook.zig @@ -184,21 +184,10 @@ fn deviceGetPtr(dev: *anyopaque, idx: usize) ?*anyopaque { return ptr; } -/// Set a COM pointer, handling the null case by passing 0 via raw write. +/// Set a COM pointer, handling null via optional pointer (ABI-equivalent to passing 0). fn deviceSetPtrOrNull(dev: *anyopaque, idx: usize, ptr: ?*anyopaque) void { - if (ptr) |p| { - deviceSetPtr(dev, idx, p); - } else { - const func_addr = vt(dev)[idx]; - asm volatile ( - \\push $0 - \\push %[self] - \\call *%[func] - : - : [self] "r" (@intFromPtr(dev)), - [func] "r" (func_addr), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); - } + const f: *const fn (*anyopaque, ?*anyopaque) callconv(sc) i32 = @ptrFromInt(vt(dev)[idx]); + _ = @call(.never_tail, f, .{ dev, ptr }); } fn deviceSetPSConstF(dev: *anyopaque, start: u32, data: *const [4]f32) void { @@ -233,25 +222,10 @@ fn deviceGetRenderTarget(dev: *anyopaque, idx: u32) ?*anyopaque { return surf; } -/// Set texture on a sampler stage. Handles NULL via inline asm. fn deviceSetTexture(dev: *anyopaque, stage: u32, tex: ?*anyopaque) void { - if (tex) |t| { - const f: *const fn (*anyopaque, u32, *anyopaque) callconv(sc) i32 = - @ptrFromInt(vt(dev)[types.VT.SetTexture]); - _ = f(dev, stage, t); - } else { - const func_addr = vt(dev)[types.VT.SetTexture]; - asm volatile ( - \\push $0 - \\push %[stage] - \\push %[self] - \\call *%[func] - : - : [self] "r" (@intFromPtr(dev)), - [stage] "r" (stage), - [func] "r" (func_addr), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); - } + const f: *const fn (*anyopaque, u32, ?*anyopaque) callconv(sc) i32 = + @ptrFromInt(vt(dev)[types.VT.SetTexture]); + _ = @call(.never_tail, f, .{ dev, stage, tex }); } fn deviceSetFVF(dev: *anyopaque, fvf: u32) void { @@ -281,27 +255,9 @@ fn deviceGetStreamSource(dev: *anyopaque, stream: u32, vb_out: *?*anyopaque, off } fn deviceSetStreamSource(dev: *anyopaque, stream: u32, vb: ?*anyopaque, offset: u32, stride: u32) void { - if (vb) |v| { - const f: *const fn (*anyopaque, u32, *anyopaque, u32, u32) callconv(sc) i32 = - @ptrFromInt(vt(dev)[types.VT.SetStreamSource]); - _ = f(dev, stream, v, offset, stride); - } else { - // Pass NULL VB via inline asm (Zig's *anyopaque is non-null) - const args = [_]u32{ stream, 0, offset, stride }; - const func_addr = vt(dev)[types.VT.SetStreamSource]; - asm volatile ( - \\push 12(%[a]) - \\push 8(%[a]) - \\push 4(%[a]) - \\push (%[a]) - \\push %[self] - \\call *%[func] - : - : [self] "r" (@intFromPtr(dev)), - [a] "r" (&args), - [func] "r" (func_addr), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); - } + const f: *const fn (*anyopaque, u32, ?*anyopaque, u32, u32) callconv(sc) i32 = + @ptrFromInt(vt(dev)[types.VT.SetStreamSource]); + _ = @call(.never_tail, f, .{ dev, stream, vb, offset, stride }); } fn deviceGetTexture(dev: *anyopaque, stage: u32) ?*anyopaque { @@ -321,21 +277,9 @@ fn deviceGetIndices(dev: *anyopaque) ?*anyopaque { } fn deviceSetIndices(dev: *anyopaque, ib: ?*anyopaque) void { - if (ib) |i| { - const f: *const fn (*anyopaque, *anyopaque) callconv(sc) i32 = - @ptrFromInt(vt(dev)[types.VT.SetIndices]); - _ = f(dev, i); - } else { - const func_addr = vt(dev)[types.VT.SetIndices]; - asm volatile ( - \\push $0 - \\push %[self] - \\call *%[func] - : - : [self] "r" (@intFromPtr(dev)), - [func] "r" (func_addr), - : .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }); - } + const f: *const fn (*anyopaque, ?*anyopaque) callconv(sc) i32 = + @ptrFromInt(vt(dev)[types.VT.SetIndices]); + _ = @call(.never_tail, f, .{ dev, ib }); } fn deviceGetVSConstF(dev: *anyopaque, start: u32, data: [*][4]f32, count: u32) void { diff --git a/src/outline/wow.zig b/src/outline/wow.zig index a1ee35b..67d4473 100644 --- a/src/outline/wow.zig +++ b/src/outline/wow.zig @@ -14,6 +14,9 @@ const types = @import("types.zig"); // ============================================================================= const WINAPI = std.builtin.CallingConvention.winapi; +const fc: std.builtin.CallingConvention = .{ .x86_fastcall = .{} }; +const sc: std.builtin.CallingConvention = .{ .x86_stdcall = .{} }; +const tc: std.builtin.CallingConvention = .{ .x86_thiscall = .{} }; extern "kernel32" fn IsBadReadPtr( lp: ?*const anyopaque, @@ -140,35 +143,18 @@ pub fn resolveModelOwner(model: u32) u32 { // ============================================================================= /// UnitGUID("player") / UnitGUID("target") → 64-bit GUID. -/// __fastcall(unitIdStr_ECX) → EAX:EDX. +/// __fastcall(unitIdStr_ECX) → EDX:EAX (u64). pub fn unitGUID(unit_id: [*:0]const u8) u64 { - var lo: u32 = undefined; - var hi: u32 = undefined; - asm volatile ("call *%[func]" - : [_] "={eax}" (lo), - [_] "={edx}" (hi), - : [_] "{ecx}" (@intFromPtr(unit_id)), - [func] "r" (@as(u32, o.FN_UNIT_GUID)), - : .{ .memory = true, .cc = true }); - return (@as(u64, hi) << 32) | lo; + return hook.call(fn ([*:0]const u8) callconv(fc) u64, o.FN_UNIT_GUID, .{unit_id}); } /// Resolve a GUID → object pointer via the object manager hash table. /// Ghidra-verified: __stdcall(guidLow, guidHigh) with RET 8. -/// NOT __fastcall - params are read from stack, not registers. pub fn getObjectByGUID(guid: u64) u32 { if (guid == 0) return 0; const lo: u32 = @truncate(guid); const hi: u32 = @truncate(guid >> 32); - return asm volatile ( - \\push %[hi] - \\push %[lo] - \\call *%[func] - : [ret] "={eax}" (-> u32), - : [lo] "r" (lo), - [hi] "r" (hi), - [func] "r" (@as(u32, o.FN_GET_OBJECT_BY_GUID)), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true }); + return hook.call(fn (u32, u32) callconv(sc) u32, o.FN_GET_OBJECT_BY_GUID, .{ lo, hi }); } /// Get the local player's object pointer. @@ -188,14 +174,7 @@ pub fn getTargetGUID() u64 { /// Reaction >= 4 means friendly. pub fn isUnitFriendly(unit: u32, local_player: u32) bool { if (unit == 0 or local_player == 0) return false; - const reaction: i32 = asm volatile ( - \\push %[unit] - \\call *%[func] - : [ret] "={eax}" (-> i32), - : [_] "{ecx}" (local_player), - [unit] "r" (unit), - [func] "r" (@as(u32, o.FN_UNIT_REACTION)), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true }); + const reaction = hook.call(fn (u32, u32) callconv(tc) i32, o.FN_UNIT_REACTION, .{ local_player, unit }); return reaction >= 4; } diff --git a/src/transmogfix/transmogfix.zig b/src/transmogfix/transmogfix.zig index 799f25e..f7027c9 100644 --- a/src/transmogfix/transmogfix.zig +++ b/src/transmogfix/transmogfix.zig @@ -61,31 +61,13 @@ const DISPLAY_INFO_TABLE_PTR: usize = 0x00c0de90; // ============================================================================= fn unitGUID(unit_id: [*:0]const u8) u64 { - // __fastcall(ECX=str) → u64 in EDX:EAX - var lo: u32 = undefined; - var hi: u32 = undefined; - asm volatile ( - \\call *%[func] - : [lo] "={eax}" (lo), - [hi] "={edx}" (hi), - : [_] "{ecx}" (unit_id), - [func] "r" (@as(u32, ADDR_UnitGUID)), - : .{ .memory = true, .cc = true }); - return @as(u64, hi) << 32 | lo; + return hook.call(fn ([*:0]const u8) callconv(fc) u64, ADDR_UnitGUID, .{unit_id}); } fn getObjectByGUID(guid: u64) u32 { const lo: u32 = @truncate(guid); const hi: u32 = @truncate(guid >> 32); - return asm volatile ( - \\push %[hi] - \\push %[lo] - \\call *%[func] - : [ret] "={eax}" (-> u32), - : [lo] "r" (lo), - [hi] "r" (hi), - [func] "r" (@as(u32, ADDR_GetObjectByGUID)), - : .{ .ecx = true, .edx = true, .memory = true, .cc = true }); + return hook.call(fn (u32, u32) callconv(sc) u32, ADDR_GetObjectByGUID, .{ lo, hi }); } fn updateInventoryAlertStates() void { @@ -172,6 +154,8 @@ pub fn isActive() bool { // ============================================================================= const tc: std.builtin.CallingConvention = .{ .x86_thiscall = .{} }; +const fc: std.builtin.CallingConvention = .{ .x86_fastcall = .{} }; +const sc: std.builtin.CallingConvention = .{ .x86_stdcall = .{} }; const SetBlockFn = fn (u32, u32, u32) callconv(tc) u32; const RefreshFn = fn (u32, u32, u32, u32) callconv(tc) void; const SceneEndFn = fn (u32) callconv(tc) void;