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.
This commit is contained in:
MarcelineVQ
2026-03-09 10:28:55 -07:00
parent b688a3dee9
commit 9c96a94dc1
10 changed files with 85 additions and 437 deletions
+1 -6
View File
@@ -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});
}
// =============================================================================
+2 -11
View File
@@ -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 {
+11 -41
View File
@@ -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) = .{};
+2 -10
View File
@@ -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 {
+8 -35
View File
@@ -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) ---
+14 -93
View File
@@ -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).
+24 -125
View File
@@ -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);
+12 -68
View File
@@ -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 {
+7 -28
View File
@@ -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;
}
+4 -20
View File
@@ -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;