diff --git a/build.zig b/build.zig index b62e447..fcde2f5 100644 --- a/build.zig +++ b/build.zig @@ -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, diff --git a/src/console.zig b/src/console.zig new file mode 100644 index 0000000..9f7902c --- /dev/null +++ b/src/console.zig @@ -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); + } +} diff --git a/src/framecrash/framecrash.zig b/src/framecrash/framecrash.zig new file mode 100644 index 0000000..dfa389b --- /dev/null +++ b/src/framecrash/framecrash.zig @@ -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". diff --git a/src/main.zig b/src/main.zig index f61bd39..840443c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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(); } // ============================================================================= diff --git a/src/markers/addon/Bindings.xml b/src/markers/addon/Bindings.xml new file mode 100644 index 0000000..7077d2c --- /dev/null +++ b/src/markers/addon/Bindings.xml @@ -0,0 +1,5 @@ + + + TestMarkerToggle() + + diff --git a/src/markers/addon/Markers.lua b/src/markers/addon/Markers.lua new file mode 100644 index 0000000..c3ba0e7 --- /dev/null +++ b/src/markers/addon/Markers.lua @@ -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 diff --git a/src/markers/addon/Markers.toc b/src/markers/addon/Markers.toc new file mode 100644 index 0000000..ada3133 --- /dev/null +++ b/src/markers/addon/Markers.toc @@ -0,0 +1,8 @@ +## Interface: 11200 +## Title: Markers +## Notes: Client-side raid markers and indicators +## Version: 1.0 +## Dependencies: WeirdUtils + +Markers.lua +Bindings.xml diff --git a/src/markers/assets/World/ArtTest/Boxtest/xyz.blp b/src/markers/assets/World/ArtTest/Boxtest/xyz.blp new file mode 100644 index 0000000..3b74bad Binary files /dev/null and b/src/markers/assets/World/ArtTest/Boxtest/xyz.blp differ diff --git a/src/markers/assets/World/ArtTest/Boxtest/xyz.m2 b/src/markers/assets/World/ArtTest/Boxtest/xyz.m2 new file mode 100644 index 0000000..8e0d3f3 Binary files /dev/null and b/src/markers/assets/World/ArtTest/Boxtest/xyz.m2 differ diff --git a/src/markers/markers.zig b/src/markers/markers.zig new file mode 100644 index 0000000..83b2b8f --- /dev/null +++ b/src/markers/markers.zig @@ -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(); +} diff --git a/src/markers/offsets.zig b/src/markers/offsets.zig new file mode 100644 index 0000000..604f2e2 --- /dev/null +++ b/src/markers/offsets.zig @@ -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; diff --git a/src/outline/README.md b/src/outline/README.md index 9b11cc6..15d8bd0 100644 --- a/src/outline/README.md +++ b/src/outline/README.md @@ -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