Add markers module, framecrash stub, debug console, build-flag wiring
Markers: client-side world object system using CreateGameObject. Places M2 models at arbitrary world positions via Lua commands (/mark test, /mark pos). Includes embedded addon, xyz.m2/blp assets served from DLL memory, position helpers from unit movement struct, and object lifecycle management (create, cleanup, reposition, alpha, animation). Framecrash: stub module with reference to crash at 0x007A2452. Console: debug output via AllocConsole/WriteConsoleA, compiles out entirely in non-Debug builds. Used by markers and file serve logging. Build: markers added to feature flag matrix and all-variants step. Main: conditional markers import, Lua function registration, embedded addon + asset file serving, console init/deinit lifecycle. Outline README: added misc planned features and debug mode notes.
This commit is contained in:
@@ -12,12 +12,14 @@ pub fn build(b: *std.Build) void {
|
||||
const enable_screenshot = b.option(bool, "screenshot", "Enable screenshot module") orelse true;
|
||||
const enable_interact = b.option(bool, "interact", "Enable interact module") orelse true;
|
||||
const enable_outline = b.option(bool, "outline", "Enable outline module") orelse true;
|
||||
const enable_markers = b.option(bool, "markers", "Enable markers module") orelse true;
|
||||
|
||||
// Create build options module
|
||||
const build_options = b.addOptions();
|
||||
build_options.addOption(bool, "enable_screenshot", enable_screenshot);
|
||||
build_options.addOption(bool, "enable_interact", enable_interact);
|
||||
build_options.addOption(bool, "enable_outline", enable_outline);
|
||||
build_options.addOption(bool, "enable_markers", enable_markers);
|
||||
const build_options_module = build_options.createModule();
|
||||
|
||||
const hook_mod = b.dependency("hook", .{
|
||||
@@ -45,16 +47,18 @@ pub fn build(b: *std.Build) void {
|
||||
const build_all_step = b.step("all-variants", "Build all DLL variants");
|
||||
|
||||
// Helper to create a single-module build
|
||||
inline for (&[_]struct { name: []const u8, screenshot: bool, interact: bool, outline: bool }{
|
||||
.{ .name = "full", .screenshot = true, .interact = true, .outline = true },
|
||||
.{ .name = "screenshot", .screenshot = true, .interact = false, .outline = false },
|
||||
.{ .name = "interact", .screenshot = false, .interact = true, .outline = false },
|
||||
.{ .name = "outline", .screenshot = false, .interact = false, .outline = true },
|
||||
inline for (&[_]struct { name: []const u8, screenshot: bool, interact: bool, outline: bool, markers: bool }{
|
||||
.{ .name = "full", .screenshot = true, .interact = true, .outline = true, .markers = true },
|
||||
.{ .name = "screenshot", .screenshot = true, .interact = false, .outline = false, .markers = false },
|
||||
.{ .name = "interact", .screenshot = false, .interact = true, .outline = false, .markers = false },
|
||||
.{ .name = "outline", .screenshot = false, .interact = false, .outline = true, .markers = false },
|
||||
.{ .name = "markers", .screenshot = false, .interact = false, .outline = false, .markers = true },
|
||||
}) |variant| {
|
||||
const opts = b.addOptions();
|
||||
opts.addOption(bool, "enable_screenshot", variant.screenshot);
|
||||
opts.addOption(bool, "enable_interact", variant.interact);
|
||||
opts.addOption(bool, "enable_outline", variant.outline);
|
||||
opts.addOption(bool, "enable_markers", variant.markers);
|
||||
|
||||
const variant_lib = b.addLibrary(.{
|
||||
.name = variant.name,
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//! Debug console — compiles out entirely in non-Debug builds.
|
||||
//!
|
||||
//! Usage from any module:
|
||||
//! const con = @import("../console.zig"); // or appropriate relative path
|
||||
//! con.print("[markers] loaded\n");
|
||||
//! con.fmt("[markers] pos = {d}, {d}, {d}\n", .{ x, y, z });
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const debug = builtin.mode == .Debug;
|
||||
|
||||
const WINAPI = std.builtin.CallingConvention.winapi;
|
||||
|
||||
const win = if (debug) struct {
|
||||
extern "kernel32" fn AllocConsole() callconv(WINAPI) i32;
|
||||
extern "kernel32" fn FreeConsole() callconv(WINAPI) i32;
|
||||
extern "kernel32" fn SetConsoleTitleA(title: [*:0]const u8) callconv(WINAPI) i32;
|
||||
extern "kernel32" fn GetStdHandle(nStdHandle: u32) callconv(WINAPI) ?*anyopaque;
|
||||
extern "kernel32" fn WriteConsoleA(
|
||||
hOut: *anyopaque,
|
||||
buf: [*]const u8,
|
||||
len: u32,
|
||||
written: ?*u32,
|
||||
reserved: ?*anyopaque,
|
||||
) callconv(WINAPI) i32;
|
||||
|
||||
const STD_OUTPUT_HANDLE: u32 = 0xFFFFFFF5;
|
||||
var handle: ?*anyopaque = null;
|
||||
} else void;
|
||||
|
||||
pub fn init() void {
|
||||
if (debug) {
|
||||
_ = win.AllocConsole();
|
||||
_ = win.SetConsoleTitleA("weirdutils");
|
||||
win.handle = win.GetStdHandle(win.STD_OUTPUT_HANDLE);
|
||||
print("[weirdutils] Console attached\n");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit() void {
|
||||
if (debug) {
|
||||
if (win.handle != null) {
|
||||
print("[weirdutils] Detaching\n");
|
||||
_ = win.FreeConsole();
|
||||
win.handle = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print(msg: []const u8) void {
|
||||
if (debug) {
|
||||
if (win.handle) |h| {
|
||||
_ = win.WriteConsoleA(h, msg.ptr, @intCast(msg.len), null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmt(comptime f: []const u8, args: anytype) void {
|
||||
if (debug) {
|
||||
var buf: [512]u8 = undefined;
|
||||
const msg = std.fmt.bufPrint(&buf, f, args) catch return;
|
||||
print(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Framecrash fix module.
|
||||
//!
|
||||
//! Reference crash log: /media/bigfaststore/games/twmoa_1172/Errors/2026-02-25 19.38.01 Crash.txt
|
||||
|
||||
//We are finally going to solve: The instruction at "0x007A2452" referenced memory at "0x16C0FFE8".
|
||||
@@ -1,17 +1,20 @@
|
||||
const std = @import("std");
|
||||
const hook = @import("hook");
|
||||
pub const con = @import("console.zig");
|
||||
|
||||
// Build options for conditional module compilation
|
||||
const build_opts = struct {
|
||||
const screenshot = @import("build_options").enable_screenshot;
|
||||
const interact = @import("build_options").enable_interact;
|
||||
const outline = @import("build_options").enable_outline;
|
||||
const markers = @import("build_options").enable_markers;
|
||||
};
|
||||
|
||||
// Conditional module imports
|
||||
const screenshot = if (build_opts.screenshot) @import("screenshot/screenshot.zig") else struct {};
|
||||
const interact = if (build_opts.interact) @import("interact/interact.zig") else struct {};
|
||||
const outline = if (build_opts.outline) @import("outline/api.zig") else struct {};
|
||||
const markers = if (build_opts.markers) @import("markers/markers.zig") else struct {};
|
||||
|
||||
const WINAPI = std.builtin.CallingConvention.winapi;
|
||||
const fc: std.builtin.CallingConvention = .{ .x86_fastcall = .{} };
|
||||
@@ -243,6 +246,12 @@ fn registerLuaFunctions() void {
|
||||
if (build_opts.outline) {
|
||||
registerFunction("OutlineCommand", @intFromPtr(&outline.outlineCommand));
|
||||
}
|
||||
if (build_opts.markers) {
|
||||
registerFunction("TestMarkerCreate", @intFromPtr(&markers.luaTestMarkerCreate));
|
||||
registerFunction("TestMarkerDestroy", @intFromPtr(&markers.luaTestMarkerDestroy));
|
||||
registerFunction("TestMarkerToggle", @intFromPtr(&markers.luaTestMarkerToggle));
|
||||
registerFunction("GetPlayerPosition", @intFromPtr(&markers.luaGetPlayerPosition));
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -285,12 +294,25 @@ const outline_files = if (build_opts.outline) [_]FileEntry{
|
||||
.{ .name = "Bindings.xml", .data = @embedFile("outline/addon/Bindings.xml") },
|
||||
} else [_]FileEntry{};
|
||||
|
||||
const markers_files = if (build_opts.markers) [_]FileEntry{
|
||||
.{ .name = "Markers.toc", .data = @embedFile("markers/addon/Markers.toc") },
|
||||
.{ .name = "Markers.lua", .data = @embedFile("markers/addon/Markers.lua") },
|
||||
.{ .name = "Bindings.xml", .data = @embedFile("markers/addon/Bindings.xml") },
|
||||
} else [_]FileEntry{};
|
||||
|
||||
const markers_assets = if (build_opts.markers) [_]FileEntry{
|
||||
.{ .name = "xyz.m2", .data = @embedFile("markers/assets/World/ArtTest/Boxtest/xyz.m2") },
|
||||
.{ .name = "xyz.blp", .data = @embedFile("markers/assets/World/ArtTest/Boxtest/xyz.blp") },
|
||||
} else [_]FileEntry{};
|
||||
|
||||
// All addon prefixes to check
|
||||
const addon_prefixes = [_]AddonPrefix{
|
||||
.{ .prefix = core_prefix, .files = &core_files },
|
||||
.{ .prefix = "Interface\\AddOns\\Screenshot\\", .files = &screenshot_files },
|
||||
.{ .prefix = "Interface\\AddOns\\Interact\\", .files = &interact_files },
|
||||
.{ .prefix = "Interface\\AddOns\\Outline\\", .files = &outline_files },
|
||||
.{ .prefix = "Interface\\AddOns\\Markers\\", .files = &markers_files },
|
||||
.{ .prefix = "World\\ArtTest\\Boxtest\\", .files = &markers_assets },
|
||||
};
|
||||
|
||||
fn findEmbeddedFile(path: [*:0]const u8) ?*const FileEntry {
|
||||
@@ -357,6 +379,7 @@ fn loadFileDetour(
|
||||
|
||||
buf_out.* = buf;
|
||||
if (size_out) |s| s.* = data_len;
|
||||
con.fmt("[file] served embedded: {s} ({d} bytes)\n", .{ std.mem.span(path), data_len });
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -435,6 +458,18 @@ fn loadAddonsDetour(error_handler: u32, _edx: u32) callconv(.c) void {
|
||||
error_handler,
|
||||
);
|
||||
}
|
||||
if (build_opts.markers) {
|
||||
callLoadFileListWithIncludes(
|
||||
"Interface\\AddOns\\Markers\\Markers.toc",
|
||||
&md5ctx,
|
||||
error_handler,
|
||||
);
|
||||
callLoadUIBindingsFromFile(
|
||||
"Interface\\AddOns\\Markers\\Bindings.xml",
|
||||
&md5ctx,
|
||||
error_handler,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn callOrigLoadAddons(error_handler: u32) void {
|
||||
@@ -511,6 +546,8 @@ fn shutdownDetour() callconv(sc) void {
|
||||
// =============================================================================
|
||||
|
||||
fn install() void {
|
||||
con.init();
|
||||
con.print("[weirdutils] Installing hooks\n");
|
||||
_ = protection_hook.install(0x42a320, 6, @intFromPtr(&luaProtectionDetour), &.{});
|
||||
_ = file_hook.install(0x648620, 6, @intFromPtr(&loadFileDetour), &.{});
|
||||
_ = lsf_hook.install(0x490250, 6, @intFromPtr(&loadScriptFunctionsDetour), &.{1});
|
||||
@@ -547,6 +584,7 @@ fn uninstall() void {
|
||||
lsf_hook.remove();
|
||||
file_hook.remove();
|
||||
protection_hook.remove();
|
||||
con.deinit();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<Bindings>
|
||||
<Binding name="Toggle Test Marker" header="MARKERS">
|
||||
TestMarkerToggle()
|
||||
</Binding>
|
||||
</Bindings>
|
||||
@@ -0,0 +1,46 @@
|
||||
-- Markers addon (embedded in DLL, loaded from memory)
|
||||
-- Part of WeirdUtils - only loaded when markers module is compiled
|
||||
|
||||
MARKERS_VERSION = 1
|
||||
|
||||
BINDING_HEADER_MARKERS = "Markers"
|
||||
|
||||
local frame = CreateFrame("Frame")
|
||||
frame:RegisterEvent("ADDON_LOADED")
|
||||
frame:RegisterEvent("PLAYER_LOGIN")
|
||||
|
||||
frame:SetScript("OnEvent", function()
|
||||
if event == "PLAYER_LOGIN" then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Markers|r v" .. MARKERS_VERSION .. " loaded")
|
||||
end
|
||||
end)
|
||||
|
||||
SLASH_MARKERS1 = "/markers"
|
||||
SLASH_MARKERS2 = "/mark"
|
||||
SlashCmdList["MARKERS"] = function(msg)
|
||||
msg = string.lower(msg or "")
|
||||
|
||||
if msg == "" or msg == "help" then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Markers|r commands:")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /mark test - Toggle test marker at player position")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /mark pos - Show player position")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /mark on - Create test marker")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /mark off - Destroy test marker")
|
||||
|
||||
elseif msg == "test" or msg == "toggle" then
|
||||
TestMarkerToggle()
|
||||
|
||||
elseif msg == "on" or msg == "create" then
|
||||
TestMarkerCreate()
|
||||
|
||||
elseif msg == "off" or msg == "destroy" then
|
||||
TestMarkerDestroy()
|
||||
|
||||
elseif msg == "pos" or msg == "position" then
|
||||
local x, y, z = GetPlayerPosition()
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("Position: %.2f, %.2f, %.2f", x, y, z))
|
||||
|
||||
else
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000Unknown command:|r " .. msg)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
## Interface: 11200
|
||||
## Title: Markers
|
||||
## Notes: Client-side raid markers and indicators
|
||||
## Version: 1.0
|
||||
## Dependencies: WeirdUtils
|
||||
|
||||
Markers.lua
|
||||
Bindings.xml
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,271 @@
|
||||
//! Client-side marker system using CreateGameObject_WithProperties
|
||||
//!
|
||||
//! Creates world objects at arbitrary positions for raid markers,
|
||||
//! player indicators, etc.
|
||||
|
||||
const hook = @import("hook");
|
||||
const o = @import("offsets.zig");
|
||||
const wow = @import("../outline/wow.zig");
|
||||
const con = @import("../console.zig");
|
||||
|
||||
// =============================================================================
|
||||
// Position helpers
|
||||
// =============================================================================
|
||||
|
||||
pub const Vec3 = struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
};
|
||||
|
||||
/// Get unit position from movement struct
|
||||
pub fn getUnitPosition(unit: u32) Vec3 {
|
||||
if (unit == 0) return .{ .x = 0, .y = 0, .z = 0 };
|
||||
|
||||
const movement = hook.readMem(u32, unit + o.UNIT_MOVEMENT_OFFSET);
|
||||
if (movement == 0 or movement < 0x10000) return .{ .x = 0, .y = 0, .z = 0 };
|
||||
|
||||
return .{
|
||||
.x = hook.readMem(f32, movement + o.MOVEMENT_POS_X),
|
||||
.y = hook.readMem(f32, movement + o.MOVEMENT_POS_Y),
|
||||
.z = hook.readMem(f32, movement + o.MOVEMENT_POS_Z),
|
||||
};
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// GameObject creation - function pointer wrappers
|
||||
// =============================================================================
|
||||
|
||||
/// CreateGameObject_WithProperties — __fastcall:
|
||||
/// ECX = model, EDX = callback1
|
||||
/// Stack (callee-clean, RET 0x14): callback2, x, y, z, flags
|
||||
pub fn createGameObject(
|
||||
model: ?*anyopaque,
|
||||
callback1: ?*anyopaque,
|
||||
callback2: ?*anyopaque,
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
flags: u32,
|
||||
) ?*anyopaque {
|
||||
// 5 stack params (callee cleans via RET 0x14)
|
||||
const stack_args = [5]u32{
|
||||
if (callback2) |c| @intCast(@intFromPtr(c)) else 0,
|
||||
@as(u32, @bitCast(x)),
|
||||
@as(u32, @bitCast(y)),
|
||||
@as(u32, @bitCast(z)),
|
||||
flags,
|
||||
};
|
||||
|
||||
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}" (if (model) |m| @intFromPtr(m) else @as(usize, 0)),
|
||||
[_] "{edx}" (if (callback1) |c| @intFromPtr(c) else @as(usize, 0)),
|
||||
[a] "r" (&stack_args),
|
||||
[func] "r" (o.FN_CREATE_GAMEOBJECT),
|
||||
: .{ .memory = true, .cc = true }
|
||||
);
|
||||
|
||||
return if (result != 0) @ptrFromInt(result) else null;
|
||||
}
|
||||
|
||||
/// CleanupWorldObject(obj) - __thiscall: ECX = obj, 0 stack params
|
||||
pub fn cleanupWorldObject(obj: *anyopaque) void {
|
||||
asm volatile ("call *%[func]"
|
||||
:
|
||||
: [_] "{ecx}" (@intFromPtr(obj)),
|
||||
[func] "r" (o.FN_CLEANUP_WORLD_OBJECT),
|
||||
: .{ .eax = true, .edx = true, .memory = true, .cc = true }
|
||||
);
|
||||
}
|
||||
|
||||
/// Update object position directly
|
||||
pub fn setObjectPosition(obj: *anyopaque, pos: Vec3) void {
|
||||
const ptr: u32 = @intCast(@intFromPtr(obj));
|
||||
@as(*f32, @ptrFromInt(ptr + o.OBJ_POS_X)).* = pos.x;
|
||||
@as(*f32, @ptrFromInt(ptr + o.OBJ_POS_Y)).* = pos.y;
|
||||
@as(*f32, @ptrFromInt(ptr + o.OBJ_POS_Z)).* = pos.z;
|
||||
}
|
||||
|
||||
/// Set object alpha (0-255)
|
||||
pub fn setObjectAlpha(obj: *anyopaque, alpha: u8) void {
|
||||
const ptr: u32 = @intCast(@intFromPtr(obj));
|
||||
// ARGB format - alpha in high byte
|
||||
const color: u32 = (@as(u32, alpha) << 24) | 0x00189680;
|
||||
@as(*u32, @ptrFromInt(ptr + o.OBJ_COLOR)).* = color;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Animation
|
||||
// =============================================================================
|
||||
|
||||
/// PlayAnimation(obj, animId) - __thiscall: ECX = obj, 1 stack param (callee-clean, RET 4)
|
||||
pub fn playAnimation(obj: *anyopaque, anim_id: u32) void {
|
||||
asm volatile (
|
||||
\\push %[anim]
|
||||
\\call *%[func]
|
||||
:
|
||||
: [_] "{ecx}" (@intFromPtr(obj)),
|
||||
[anim] "r" (anim_id),
|
||||
[func] "r" (o.FN_PLAY_ANIMATION),
|
||||
: .{ .eax = true, .edx = true, .memory = true, .cc = true }
|
||||
);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Marker management
|
||||
// =============================================================================
|
||||
|
||||
var test_marker: ?*anyopaque = null;
|
||||
|
||||
const MODEL_PATH: [*:0]const u8 = "World\\ArtTest\\Boxtest\\xyz.m2";
|
||||
|
||||
/// loadModelByName — __fastcall(ECX=path), returns model cache entry or null.
|
||||
fn loadModel(path: [*:0]const u8) ?*anyopaque {
|
||||
con.fmt("[markers] loadModelByName(\"{s}\")\n", .{@as([*:0]const u8, path)});
|
||||
const result: u32 = asm volatile ("call *%[func]"
|
||||
: [ret] "={eax}" (-> u32),
|
||||
: [_] "{ecx}" (@intFromPtr(path)),
|
||||
[func] "r" (o.FN_LOAD_MODEL_BY_NAME),
|
||||
: .{ .edx = true, .memory = true, .cc = true }
|
||||
);
|
||||
if (result != 0) {
|
||||
con.fmt("[markers] -> model at 0x{X:0>8}\n", .{result});
|
||||
} else {
|
||||
con.print("[markers] -> FAILED (returned null)\n");
|
||||
}
|
||||
return if (result != 0) @ptrFromInt(result) else null;
|
||||
}
|
||||
|
||||
/// Create a test marker at player position
|
||||
pub fn createTestMarker() ?*anyopaque {
|
||||
const player = wow.getLocalPlayer();
|
||||
if (player == 0) {
|
||||
con.print("[markers] createTestMarker: no local player\n");
|
||||
return null;
|
||||
}
|
||||
|
||||
const pos = getUnitPosition(player);
|
||||
con.fmt("[markers] player pos = {d:.1}, {d:.1}, {d:.1}\n", .{ pos.x, pos.y, pos.z });
|
||||
if (pos.x == 0 and pos.y == 0 and pos.z == 0) return null;
|
||||
|
||||
const model = loadModel(MODEL_PATH);
|
||||
con.fmt("[markers] createGameObject(model={?}, pos=({d:.1},{d:.1},{d:.1}))\n", .{
|
||||
@as(?usize, if (model) |m| @intFromPtr(m) else null),
|
||||
pos.x, pos.y, pos.z + 1.0,
|
||||
});
|
||||
const marker = createGameObject(model, null, null, pos.x, pos.y, pos.z + 1.0, 0);
|
||||
|
||||
if (marker) |m| {
|
||||
con.fmt("[markers] -> object at 0x{X:0>8}\n", .{@intFromPtr(m)});
|
||||
test_marker = m;
|
||||
} else {
|
||||
con.print("[markers] -> createGameObject FAILED\n");
|
||||
}
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
/// Destroy test marker
|
||||
pub fn destroyTestMarker() void {
|
||||
if (test_marker) |m| {
|
||||
cleanupWorldObject(m);
|
||||
test_marker = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Update test marker position to follow player
|
||||
pub fn updateTestMarker() void {
|
||||
if (test_marker == null) return;
|
||||
|
||||
const player = wow.getLocalPlayer();
|
||||
if (player == 0) return;
|
||||
|
||||
const pos = getUnitPosition(player);
|
||||
if (pos.x == 0 and pos.y == 0 and pos.z == 0) return;
|
||||
|
||||
setObjectPosition(test_marker.?, .{ .x = pos.x, .y = pos.y, .z = pos.z + 1.0 });
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Lua helpers
|
||||
// =============================================================================
|
||||
|
||||
/// lua_pushnumber at 0x6F3810: __fastcall(L_ECX, double_on_stack). Callee cleans (RET 8).
|
||||
fn luaPushNumber(L: u32, n: f64) void {
|
||||
const raw: [2]u32 = @bitCast(n);
|
||||
asm volatile (
|
||||
\\push %[hi]
|
||||
\\push %[lo]
|
||||
\\call *%[func]
|
||||
:
|
||||
: [_] "{ecx}" (L),
|
||||
[lo] "r" (raw[0]),
|
||||
[hi] "r" (raw[1]),
|
||||
[func] "r" (@as(u32, 0x6F3810)),
|
||||
: .{ .eax = true, .edx = true, .memory = true, .cc = true }
|
||||
);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Lua API
|
||||
// =============================================================================
|
||||
|
||||
/// Lua: TestMarkerCreate() - create marker at player position
|
||||
pub fn luaTestMarkerCreate(L: u32) callconv(.c) u32 {
|
||||
if (createTestMarker()) |_| {
|
||||
hook.fastcall(void, 0x6F3890, L, @intFromPtr(@as([*:0]const u8, "Marker created")));
|
||||
} else {
|
||||
hook.fastcall(void, 0x6F3890, L, @intFromPtr(@as([*:0]const u8, "Failed to create marker")));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// Lua: TestMarkerDestroy() - destroy test marker
|
||||
pub fn luaTestMarkerDestroy(L: u32) callconv(.c) u32 {
|
||||
destroyTestMarker();
|
||||
hook.fastcall(void, 0x6F3890, L, @intFromPtr(@as([*:0]const u8, "Marker destroyed")));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// Lua: TestMarkerToggle() - toggle marker on/off
|
||||
pub fn luaTestMarkerToggle(L: u32) callconv(.c) u32 {
|
||||
if (test_marker != null) {
|
||||
destroyTestMarker();
|
||||
hook.fastcall(void, 0x6F3890, L, @intFromPtr(@as([*:0]const u8, "Marker off")));
|
||||
} else {
|
||||
_ = createTestMarker();
|
||||
hook.fastcall(void, 0x6F3890, L, @intFromPtr(@as([*:0]const u8, "Marker on")));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// Lua: local x, y, z = GetPlayerPosition()
|
||||
pub fn luaGetPlayerPosition(L: u32) callconv(.c) u32 {
|
||||
const player = wow.getLocalPlayer();
|
||||
if (player == 0) return 0;
|
||||
|
||||
const pos = getUnitPosition(player);
|
||||
luaPushNumber(L, @floatCast(pos.x));
|
||||
luaPushNumber(L, @floatCast(pos.y));
|
||||
luaPushNumber(L, @floatCast(pos.z));
|
||||
return 3;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Install hooks
|
||||
// =============================================================================
|
||||
|
||||
pub fn installHooks() void {
|
||||
// Nothing to hook yet - markers are created via Lua commands
|
||||
}
|
||||
|
||||
pub fn removeHooks() void {
|
||||
destroyTestMarker();
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
//! Offsets for marker/game object system
|
||||
|
||||
// =============================================================================
|
||||
// Unit position
|
||||
// =============================================================================
|
||||
|
||||
/// Unit + this → movement struct pointer
|
||||
pub const UNIT_MOVEMENT_OFFSET: usize = 0x118;
|
||||
|
||||
/// Movement struct + this → X coordinate (float)
|
||||
pub const MOVEMENT_POS_X: usize = 0x10;
|
||||
|
||||
/// Movement struct + this → Y coordinate (float)
|
||||
pub const MOVEMENT_POS_Y: usize = 0x14;
|
||||
|
||||
/// Movement struct + this → Z coordinate (float)
|
||||
pub const MOVEMENT_POS_Z: usize = 0x18;
|
||||
|
||||
// =============================================================================
|
||||
// GameObject creation functions
|
||||
// =============================================================================
|
||||
|
||||
/// CreateGameObject_WithProperties(model_ECX, callback1_EDX, callback2, x, y, z, flags)
|
||||
/// __fastcall — ECX=model, EDX=callback1, 5 stack params. Callee cleans (RET 0x14).
|
||||
/// Ghidra: 55 8B EC 83 EC 14 ... 89 4D FC (saves ECX) ... 89 55 F8 (saves EDX) ... C2 14 00.
|
||||
pub const FN_CREATE_GAMEOBJECT: usize = 0x00670db0;
|
||||
|
||||
/// AllocateAndInitializeWorldObject(initializeFlag)
|
||||
/// __fastcall returns void**
|
||||
pub const FN_ALLOCATE_WORLD_OBJECT: usize = 0x006a0930;
|
||||
|
||||
/// CleanupWorldObject(object)
|
||||
/// __thiscall
|
||||
pub const FN_CLEANUP_WORLD_OBJECT: usize = 0x0069d730;
|
||||
|
||||
/// DestroyWorldObjectAndRelease(object)
|
||||
/// __fastcall
|
||||
pub const FN_DESTROY_WORLD_OBJECT: usize = 0x006a0a70;
|
||||
|
||||
// =============================================================================
|
||||
// Model loading
|
||||
// =============================================================================
|
||||
|
||||
/// loadModelByName(path)
|
||||
/// __fastcall returns model cache entry
|
||||
pub const FN_LOAD_MODEL_BY_NAME: usize = 0x006d4640;
|
||||
|
||||
/// createModelAttachment(resourceManager, path, flags)
|
||||
/// __thiscall returns render context
|
||||
pub const FN_CREATE_MODEL_ATTACHMENT: usize = 0x00707350;
|
||||
|
||||
// =============================================================================
|
||||
// Animation control
|
||||
// =============================================================================
|
||||
|
||||
/// PlayAnimation(object, animId)
|
||||
/// __thiscall
|
||||
pub const FN_PLAY_ANIMATION: usize = 0x0076cf50;
|
||||
|
||||
/// CM2Model__PlayBoneAnimation(model, boneIndex, animId, seqIndex, animData, speed, blend, queue)
|
||||
/// __thiscall
|
||||
pub const FN_PLAY_BONE_ANIMATION: usize = 0x007121a0;
|
||||
|
||||
/// HasAnimation(model, animId)
|
||||
/// __thiscall returns bool
|
||||
pub const FN_HAS_ANIMATION: usize = 0x00711960;
|
||||
|
||||
// =============================================================================
|
||||
// Animation IDs (from string table)
|
||||
// =============================================================================
|
||||
|
||||
pub const ANIM_SPAWN: u32 = 0; // TODO: find actual ID
|
||||
pub const ANIM_DESPAWN: u32 = 0; // TODO: find actual ID
|
||||
pub const ANIM_BIRTH: u32 = 0; // TODO: find actual ID
|
||||
|
||||
// =============================================================================
|
||||
// Object structure offsets (from CreateGameObject_WithProperties analysis)
|
||||
// =============================================================================
|
||||
|
||||
/// WorldObject + this → model pointer
|
||||
pub const OBJ_MODEL: usize = 0x88;
|
||||
|
||||
/// WorldObject + this → position X (float*)
|
||||
pub const OBJ_POS_X: usize = 0xC0;
|
||||
|
||||
/// WorldObject + this → position Y (float*)
|
||||
pub const OBJ_POS_Y: usize = 0xC4;
|
||||
|
||||
/// WorldObject + this → position Z (float*)
|
||||
pub const OBJ_POS_Z: usize = 0xC8;
|
||||
|
||||
/// WorldObject + this → color ARGB (alpha in high byte)
|
||||
pub const OBJ_COLOR: usize = 0x24;
|
||||
|
||||
/// WorldObject + this → flags
|
||||
pub const OBJ_FLAGS: usize = 0x90;
|
||||
@@ -219,6 +219,11 @@ OutlineCommand("off") -- disable outlines
|
||||
(mount is parent, rider is attached, gear is attached to rider?). All child models
|
||||
(rider body, gear) should be included in the silhouette.
|
||||
|
||||
- ** MISC **: target outlines on players should be diff color than on mobs. Also when a mob and you are in melee range your outline should merge with his probably, so show you who you'te attacking.
|
||||
Also we should shade targeted units not just outline them. And each outline and shade be a setting toggle
|
||||
|
||||
- **debug**: we should add to our debug mode coloring that colors each object type a different solid color, so that they issues arise it's easy to determine what object type is causing issues
|
||||
|
||||
## Future Architecture Notes
|
||||
|
||||
- **Hook "should render" function for model tracking**: the perfboost system uses a
|
||||
|
||||
Reference in New Issue
Block a user