Fix release-mode crashes: inline asm register clobbers and fastcall ABI
Root cause: hook.fastcall used "r" constraints + explicit MOV to set
ECX/EDX. LLVM can allocate "r" inputs to clobbered registers, causing
cross-assignment (ecx_in→EDX, edx_in→ECX) or function address stomping
when func lands in ECX/EDX. Debug works by luck (trivial regalloc);
Release optimizes aggressively and hits the conflicts.
Fix: explicit "{ecx}", "{edx}", "{eax}" register constraints in zhook
fastcall — compiler places values directly, no MOV needed, no ambiguity.
Also fix 9 inline asm blocks across main.zig, interact.zig,
screenshot.zig, markers.zig missing ECX/EDX clobbers after CALL
instructions. Without clobbers the optimizer assumes registers retain
input values after the call — stale reuse in release builds.
Other changes in this commit:
- Rename markers→worldmarkers (build flag, DLL, Lua table)
- Rename assetfix→looseassets
- lua.zig: add .never_tail to pushcclosure, pcall, openlib, pushnumber
- Move internal marker functions into WorldMarkers Lua table via openlib
- Remove unused GetCurrentAreaId function
- Fix cleanup_file_handle_hook.original() → .callOriginal()
This commit is contained in:
@@ -129,7 +129,7 @@ fn rightClickInteract(pointer: u32, autoloot: i32, fun_ptr: usize) void {
|
||||
: [_] "{ecx}" (pointer),
|
||||
[autoloot] "r" (autoloot),
|
||||
[func] "r" (fun_ptr),
|
||||
: .{ .eax = true, .edx = true, .memory = true, .cc = true });
|
||||
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true });
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// =============================================================================
|
||||
// assetfix - Loose file loading & permissive patch glob
|
||||
// looseassets - Loose file loading & permissive patch glob
|
||||
// =============================================================================
|
||||
//
|
||||
// 1. Patches patch-?.MPQ → patch-*.MPQ so multi-char patch names work
|
||||
@@ -192,7 +192,7 @@ fn checkFileExistenceDetour(filename_ptr: u32, flags: u32, output_buffer_ptr: u3
|
||||
if (filename_ptr != 0) {
|
||||
if (looseFilesLookup(filename_ptr)) |disk_path| {
|
||||
const raw: [*]const u8 = @ptrFromInt(filename_ptr);
|
||||
con.fmt("[assetfix] loose hit: \"{s}\"\n", .{raw[0..cStrLen(raw)]});
|
||||
con.fmt("[looseassets] loose hit: \"{s}\"\n", .{raw[0..cStrLen(raw)]});
|
||||
|
||||
if (output_buffer_ptr != 0) {
|
||||
const disk_len = cStrLen(disk_path);
|
||||
@@ -281,11 +281,11 @@ var g_mutex: ?*anyopaque = null;
|
||||
var g_is_hook_owner: bool = false;
|
||||
|
||||
pub fn installHooks() void {
|
||||
con.print("[assetfix] Module loaded\n");
|
||||
con.print("[looseassets] Module loaded\n");
|
||||
|
||||
// Multi-DLL safety: only one instance per process should hook
|
||||
var mutex_name_buf: [64]u8 = undefined;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\AssetfixHook_{d}", .{GetCurrentProcessId()}) catch return;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\LooseassetsHook_{d}", .{GetCurrentProcessId()}) catch return;
|
||||
mutex_name_buf[mutex_name.len] = 0;
|
||||
|
||||
g_mutex = CreateMutexA(null, 1, @ptrCast(mutex_name_buf[0..mutex_name.len :0]));
|
||||
@@ -295,7 +295,7 @@ pub fn installHooks() void {
|
||||
_ = CloseHandle(g_mutex.?);
|
||||
g_mutex = null;
|
||||
g_is_hook_owner = false;
|
||||
con.print("[assetfix] Another DLL owns hooks (mutex taken), skipping\n");
|
||||
con.print("[looseassets] Another DLL owns hooks (mutex taken), skipping\n");
|
||||
return;
|
||||
}
|
||||
g_is_hook_owner = true;
|
||||
+5
-4
@@ -66,8 +66,9 @@ pub fn pushnil(L: State) void {
|
||||
|
||||
pub fn pushnumber(L: State, n: f64) void {
|
||||
// __thiscall: ECX=L, f64 on stack [EBP+8]/[EBP+0xc], ret 8.
|
||||
// .never_tail: callee-cleanup pops stack args — tail-call would corrupt the stack.
|
||||
const f: *const fn (State, f64) callconv(.{ .x86_thiscall = .{} }) void = @ptrFromInt(0x6F3810);
|
||||
f(L, n);
|
||||
@call(.never_tail, f, .{ L, n });
|
||||
}
|
||||
|
||||
pub fn pushstring(L: State, s: [*:0]const u8) void {
|
||||
@@ -82,7 +83,7 @@ pub fn pushboolean(L: State, b: i32) void {
|
||||
|
||||
pub fn pushcclosure(L: State, func: usize, n: i32) void {
|
||||
const f: *const fn (State, usize, i32) callconv(fc) void = @ptrFromInt(0x6F3920);
|
||||
f(L, func, n);
|
||||
@call(.never_tail, f, .{ L, func, n });
|
||||
}
|
||||
|
||||
pub fn tonumber(L: State, index: i32) f64 {
|
||||
@@ -122,7 +123,7 @@ pub fn next(L: State, index: i32) i32 {
|
||||
|
||||
pub fn pcall(L: State, nargs: i32, nresults: i32, errfunc: i32) i32 {
|
||||
const f: *const fn (State, i32, i32, i32) callconv(fc) i32 = @ptrFromInt(0x6F41A0);
|
||||
return f(L, nargs, nresults, errfunc);
|
||||
return @call(.never_tail, f, .{ L, nargs, nresults, errfunc });
|
||||
}
|
||||
|
||||
pub fn luaError(L: State, msg: [*:0]const u8) void {
|
||||
@@ -146,7 +147,7 @@ pub const LuaReg = extern struct {
|
||||
|
||||
pub fn openlib(L: State, libname: ?[*:0]const u8, funcs: [*]const LuaReg, nup: i32) void {
|
||||
const f: *const fn (State, ?[*:0]const u8, [*]const LuaReg, i32) callconv(fc) void = @ptrFromInt(0x6F4DC0);
|
||||
f(L, libname, funcs, nup);
|
||||
@call(.never_tail, f, .{ L, libname, funcs, nup });
|
||||
}
|
||||
|
||||
pub fn checknumber(L: State, index: i32) f64 {
|
||||
|
||||
+32
-25
@@ -7,12 +7,12 @@ 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;
|
||||
const worldmarkers = @import("build_options").enable_worldmarkers;
|
||||
const framecrash = @import("build_options").enable_framecrash;
|
||||
const combatlog = @import("build_options").enable_combatlog;
|
||||
const minimapicons = @import("build_options").enable_minimapicons;
|
||||
const transmogfix = @import("build_options").enable_transmogfix;
|
||||
const assetfix = @import("build_options").enable_assetfix;
|
||||
const looseassets = @import("build_options").enable_looseassets;
|
||||
const healtextfix = @import("build_options").enable_healtextfix;
|
||||
};
|
||||
|
||||
@@ -20,12 +20,12 @@ const build_opts = struct {
|
||||
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 markers = if (build_opts.worldmarkers) @import("markers/markers.zig") else struct {};
|
||||
const framecrash = if (build_opts.framecrash) @import("framecrash/framecrash.zig") else struct {};
|
||||
const combatlog = if (build_opts.combatlog) @import("combatlog/combatlog.zig") else struct {};
|
||||
const minimapicons = if (build_opts.minimapicons) @import("minimapicons/minimapicons.zig") else struct {};
|
||||
const transmogfix = if (build_opts.transmogfix) @import("transmogfix/transmogfix.zig") else struct {};
|
||||
const assetfix = if (build_opts.assetfix) @import("assetfix/assetfix.zig") else struct {};
|
||||
const looseassets = if (build_opts.looseassets) @import("looseassets/looseassets.zig") else struct {};
|
||||
const healtextfix = if (build_opts.healtextfix) @import("healtextfix/healtextfix.zig") else struct {};
|
||||
|
||||
const WINAPI = std.builtin.CallingConvention.winapi;
|
||||
@@ -87,7 +87,7 @@ fn weirdUtilsVersion(L: lua.State) callconv(.c) u32 {
|
||||
:
|
||||
: [_] "{ecx}" (@intFromPtr(L)),
|
||||
[func] "r" (@as(u32, 0x6F3810)),
|
||||
: .{ .eax = true, .edx = true, .memory = true, .cc = true }
|
||||
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -108,15 +108,22 @@ fn registerLuaFunctions() void {
|
||||
if (build_opts.outline) {
|
||||
registerFunction("OutlineCommand", @intFromPtr(&outline.outlineCommand));
|
||||
}
|
||||
if (build_opts.markers and markers.isActive()) {
|
||||
if (build_opts.worldmarkers and markers.isActive()) {
|
||||
// User-facing functions stay global
|
||||
registerFunction("WorldMarker", @intFromPtr(&markers.luaWorldMarker));
|
||||
registerFunction("ClearWorldMarker", @intFromPtr(&markers.luaClearWorldMarker));
|
||||
registerFunction("SetMarkerDef", @intFromPtr(&markers.luaSetMarkerDef));
|
||||
registerFunction("SetMarkerDefSync", @intFromPtr(&markers.luaSetMarkerDefSync));
|
||||
registerFunction("ClearMarkerDef", @intFromPtr(&markers.luaClearMarkerDef));
|
||||
registerFunction("GetMarkerDef", @intFromPtr(&markers.luaGetMarkerDef));
|
||||
registerFunction("GetCurrentAreaId", @intFromPtr(&markers.luaGetCurrentAreaId));
|
||||
registerFunction("CanSetMarkers", @intFromPtr(&markers.luaCanSetMarkers));
|
||||
registerFunction("CanSetWorldMarkers", @intFromPtr(&markers.luaCanSetMarkers));
|
||||
|
||||
// Internal functions in WorldMarkers table (via luaL_openlib)
|
||||
const lib = [_]lua.LuaReg{
|
||||
.{ .name = "SetMarkerDef", .func = @intFromPtr(&markers.luaSetMarkerDef) },
|
||||
.{ .name = "SetMarkerDefSync", .func = @intFromPtr(&markers.luaSetMarkerDefSync) },
|
||||
.{ .name = "ClearMarkerDef", .func = @intFromPtr(&markers.luaClearMarkerDef) },
|
||||
.{ .name = "GetMarkerDef", .func = @intFromPtr(&markers.luaGetMarkerDef) },
|
||||
|
||||
.{ .name = null, .func = 0 }, // sentinel
|
||||
};
|
||||
lua.openlib(lua.getContext(), "WorldMarkers", &lib, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,14 +167,14 @@ 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{
|
||||
const markers_files = if (build_opts.worldmarkers) [_]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{};
|
||||
|
||||
// Marker model + skin + textures served under Spells\ prefix
|
||||
const markers_spells_assets = if (build_opts.markers) [_]FileEntry{
|
||||
const markers_spells_assets = if (build_opts.worldmarkers) [_]FileEntry{
|
||||
// Models (5 colors)
|
||||
.{ .name = "Raid_UI_FX_Yellow.m2", .data = @embedFile("markers/assets/Spells/Raid_UI_FX_Yellow.m2") },
|
||||
.{ .name = "Raid_UI_FX_Cyan.m2", .data = @embedFile("markers/assets/Spells/Raid_UI_FX_Cyan.m2") },
|
||||
@@ -188,18 +195,18 @@ const markers_spells_assets = if (build_opts.markers) [_]FileEntry{
|
||||
} else [_]FileEntry{};
|
||||
|
||||
// Shared effect textures served under World\Expansion01\Doodads\Zulaman\Doors\ prefix
|
||||
const markers_world_assets = if (build_opts.markers) [_]FileEntry{
|
||||
const markers_world_assets = if (build_opts.worldmarkers) [_]FileEntry{
|
||||
.{ .name = "T_VFX_FIRE03_A.BLP", .data = @embedFile("markers/assets/World/Expansion01/Doodads/Zulaman/Doors/T_VFX_FIRE03_A.BLP") },
|
||||
.{ .name = "T_VFX_BORDER6.BLP", .data = @embedFile("markers/assets/World/Expansion01/Doodads/Zulaman/Doors/T_VFX_BORDER6.BLP") },
|
||||
} else [_]FileEntry{};
|
||||
|
||||
// XYZ debug model (renamed to avoid collision with game's built-in xyz.m2)
|
||||
const markers_xyz_model = if (build_opts.markers) [_]FileEntry{
|
||||
const markers_xyz_model = if (build_opts.worldmarkers) [_]FileEntry{
|
||||
.{ .name = "WU_XYZ.m2", .data = @embedFile("markers/assets/Spells/WU_XYZ.m2") },
|
||||
} else [_]FileEntry{};
|
||||
|
||||
// XYZ texture served under World\ArtTest\Boxtest\ (matches M2 internal reference)
|
||||
const markers_xyz_texture = if (build_opts.markers) [_]FileEntry{
|
||||
const markers_xyz_texture = if (build_opts.worldmarkers) [_]FileEntry{
|
||||
.{ .name = "xyz.blp", .data = @embedFile("markers/assets/Spells/xyz.blp") },
|
||||
} else [_]FileEntry{};
|
||||
|
||||
@@ -340,7 +347,7 @@ fn callInitFileContext(ctx: [*]u8, file_type: u32) void {
|
||||
: [_] "{ecx}" (@intFromPtr(ctx)),
|
||||
[ftype] "r" (file_type),
|
||||
[func] "r" (@as(u32, 0x647290)),
|
||||
: .{ .eax = true, .edx = true, .memory = true, .cc = true }
|
||||
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -588,7 +595,7 @@ fn loadModelAsyncDetour(model: u32, file_handle: u32, should_use_callback: u32)
|
||||
// Call original CleanupFileHandleResources through the trampoline (bypasses our
|
||||
// detour). Must clean up file context before processLoadedModelData runs.
|
||||
con.fmt("[file] cleanup via trampoline fh=0x{x}\n", .{file_handle});
|
||||
cleanup_file_handle_hook.original()(file_handle);
|
||||
cleanup_file_handle_hook.callOriginal(.{file_handle});
|
||||
con.print("[file] cleanup done\n");
|
||||
|
||||
// Dump model fields before processLoadedModelData
|
||||
@@ -709,7 +716,7 @@ fn loadAddonsDetour(error_handler: u32) callconv(fc) void {
|
||||
error_handler,
|
||||
);
|
||||
}
|
||||
if (build_opts.markers and markers.isActive()) {
|
||||
if (build_opts.worldmarkers and markers.isActive()) {
|
||||
callLoadFileListWithIncludes(
|
||||
"Interface\\AddOns\\Markers\\Markers.toc",
|
||||
&md5ctx,
|
||||
@@ -732,7 +739,7 @@ fn callLoadFileListWithIncludes(toc_path: [*:0]const u8, md5ctx: *[88]u8, error_
|
||||
[_] "{edx}" (@intFromPtr(md5ctx)),
|
||||
[eh] "r" (error_handler),
|
||||
[func] "r" (@as(u32, 0x6EDB90)),
|
||||
: .{ .eax = true, .memory = true, .cc = true }
|
||||
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -749,7 +756,7 @@ fn callLoadUIBindingsFromFile(path: [*:0]const u8, md5ctx: *[88]u8, callback: u3
|
||||
[path] "r" (@intFromPtr(path)),
|
||||
[md5] "r" (@intFromPtr(md5ctx)),
|
||||
[cb] "r" (callback),
|
||||
: .{ .eax = true, .memory = true, .cc = true }
|
||||
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -796,13 +803,13 @@ const ModuleHooks = struct {
|
||||
/// Order matters: modules are installed top-to-bottom, removed bottom-to-top.
|
||||
/// Modules with remove_on_shutdown run their remove during shutdownDetour too.
|
||||
const modules = [_]ModuleHooks{
|
||||
if (build_opts.assetfix) .{ .install = assetfix.installHooks, .remove = assetfix.removeHooks } else .{},
|
||||
if (build_opts.looseassets) .{ .install = looseassets.installHooks, .remove = looseassets.removeHooks } else .{},
|
||||
if (build_opts.framecrash) .{ .install = framecrash.installHooks, .remove = framecrash.removeHooks } else .{},
|
||||
if (build_opts.combatlog) .{ .install = combatlog.installHooks, .remove = combatlog.removeHooks } else .{},
|
||||
if (build_opts.transmogfix) .{ .install = transmogfix.installHooks, .remove = transmogfix.removeHooks } else .{},
|
||||
if (build_opts.minimapicons) .{ .install = minimapicons.installHooks, .remove = minimapicons.removeHooks } else .{},
|
||||
if (build_opts.healtextfix) .{ .install = healtextfix.installHooks, .remove = healtextfix.removeHooks } else .{},
|
||||
if (build_opts.markers) .{ .install = markers.installHooks, .remove = markers.removeHooks } else .{},
|
||||
if (build_opts.worldmarkers) .{ .install = markers.installHooks, .remove = markers.removeHooks } else .{},
|
||||
if (build_opts.interact) .{ .install = interact.installHooks, .remove = interact.removeHooks } else .{},
|
||||
if (build_opts.outline) .{ .remove = outline.cleanup } else .{},
|
||||
if (build_opts.screenshot) .{ .remove = screenshot.removeHook } else .{},
|
||||
@@ -810,7 +817,7 @@ const modules = [_]ModuleHooks{
|
||||
|
||||
fn shutdownDetour() callconv(sc) void {
|
||||
// Clear marker definitions on logout/exit (not on map change).
|
||||
if (build_opts.markers) markers.onShutdown();
|
||||
if (build_opts.worldmarkers) markers.onShutdown();
|
||||
|
||||
// Clean up world objects BEFORE game shutdown — atexit handlers run before
|
||||
// DllMain so modules with remove_on_shutdown must destroy here.
|
||||
|
||||
@@ -408,9 +408,9 @@ A minimal V1 MPQ for ~20 uncompressed files requires:
|
||||
|
||||
The hash table uses a specific encryption algorithm (documented in wowdev.wiki and StormLib source). Could implement in Zig comptime or use a build-time tool.
|
||||
|
||||
### Assetfix Synergy
|
||||
### Loose Assets Synergy
|
||||
|
||||
The [assetfix project](/media/storage/projects/zig/assetfix/) already implements the glob pattern patch (`0x82edc2: '?' → '*'`) for multi-character patch names. If combined with assetfix, the temp file could use any name like `patch-weirdutils.MPQ`.
|
||||
The [looseassets project](/media/storage/projects/zig/looseassets/) already implements the glob pattern patch (`0x82edc2: '?' → '*'`) for multi-character patch names. If combined with looseassets, the temp file could use any name like `patch-weirdutils.MPQ`.
|
||||
|
||||
---
|
||||
|
||||
@@ -532,11 +532,11 @@ If we could insert entries into this hash table mapping our asset paths to disk
|
||||
|
||||
---
|
||||
|
||||
## Approach D: CheckFileExistence Hook (assetfix Pattern)
|
||||
## Approach D: CheckFileExistence Hook (looseassets Pattern)
|
||||
|
||||
### Concept
|
||||
|
||||
The [assetfix project](/media/storage/projects/zig/assetfix/) takes a different approach entirely:
|
||||
The [looseassets project](/media/storage/projects/zig/looseassets/) takes a different approach entirely:
|
||||
|
||||
1. **NOP two gates** in `File_FindInArchive` (0x654b5c, 0x654b6a) that restrict `CheckFileExistence` to only "Interface/AddOns" paths
|
||||
2. **Hook `CheckFileExistence`** (0x654DD0) to check a hash map of loose disk files
|
||||
@@ -544,7 +544,7 @@ The [assetfix project](/media/storage/projects/zig/assetfix/) takes a different
|
||||
|
||||
This works for **disk-based** loose files but not for in-memory embedded data. However, combined with writing temp files to disk, it provides a clean single-hook solution.
|
||||
|
||||
### Key Addresses (from assetfix)
|
||||
### Key Addresses (from looseassets)
|
||||
|
||||
| Target | Address | Patch |
|
||||
|--------|---------|-------|
|
||||
@@ -564,7 +564,7 @@ This works for **disk-based** loose files but not for in-memory embedded data. H
|
||||
| Fake contexts | ~20 | **0** | 0 | 1 | 0 | 0 |
|
||||
| Async handling | Manual | **Native** | Native | Uncertain | Native | Native |
|
||||
| Build complexity | Low | **Low** | Medium (MPQ builder) | Medium (MPQ builder) | High (struct RE) | Low |
|
||||
| RE work needed | Done | **Minimal** (verify path redirect) | Archive_OpenUnified conv | Same as A + async verify | Full struct layout | Already done (assetfix) |
|
||||
| RE work needed | Done | **Minimal** (verify path redirect) | Archive_OpenUnified conv | Same as A + async verify | Full struct layout | Already done (looseassets) |
|
||||
| Risk | Proven but fragile | **Low** (real OS handles) | Low (native I/O) | Medium (async path?) | High (struct mismatch) | Low |
|
||||
| Cleanup needed | None | **Auto** (DELETE_ON_CLOSE) | Delete temp file | None | Remove from array | Delete temp files |
|
||||
| Purely in-memory | Yes | Mostly (cache-backed) | No | Yes | Yes | No |
|
||||
@@ -595,9 +595,9 @@ This works for **disk-based** loose files but not for in-memory embedded data. H
|
||||
|
||||
If a single-file solution is preferred over ~20 temp files, building a real MPQ and registering it via `Archive_OpenUnified` eliminates all hooks entirely. The cost is implementing an MPQ V1 builder (hash table encryption, block table, header). Could be combined with Approach E: use temp files now, migrate to MPQ later.
|
||||
|
||||
### Worth Combining With: Assetfix Integration
|
||||
### Worth Combining With: Loose Assets Integration
|
||||
|
||||
The assetfix project's `CheckFileExistence` hook and glob pattern patch could complement either approach, especially for supporting user-provided loose asset files alongside our embedded ones.
|
||||
The looseassets project's `CheckFileExistence` hook and glob pattern patch could complement either approach, especially for supporting user-provided loose asset files alongside our embedded ones.
|
||||
|
||||
---
|
||||
|
||||
@@ -687,6 +687,6 @@ The assetfix project's `CheckFileExistence` hook and glob pattern patch could co
|
||||
| Archive search critsec | 0xc54008 | Critical section for archive ops |
|
||||
| Archive search state | 0xc53ff0 | Used by File_FindInArchive |
|
||||
| Patch glob "patch-?.MPQ" | 0x82edbc | Glob pattern for patch discovery |
|
||||
| Patch glob char | 0x82edc2 | The '?' byte (assetfix patches to '*') |
|
||||
| Patch glob char | 0x82edc2 | The '?' byte (looseassets patches to '*') |
|
||||
| Data path format | 0x82edc8 | `"Data\%s"` format string |
|
||||
| SArchive RTTI | 0x82e248 | `".PAVSArchive@@"` |
|
||||
|
||||
@@ -20,7 +20,7 @@ end
|
||||
-- Permission model: ALL permission checks are enforced DLL-side.
|
||||
-- WorldMarker/ClearWorldMarker: DLL checks local player is leader/assist.
|
||||
-- SetMarkerDef/ClearMarkerDef: DLL checks sender name against roster.
|
||||
-- CanSetMarkers(): DLL returns 1 if local player has permission.
|
||||
-- CanSetWorldMarkers(): DLL returns 1 if local player has permission.
|
||||
-- =============================================================================
|
||||
|
||||
local MSG_PREFIX = "WMark"
|
||||
@@ -63,7 +63,7 @@ local function broadcastAllDefs()
|
||||
if not ch then return end
|
||||
local count = 0
|
||||
for i = 1, NUM_MARKERS do
|
||||
local x, y, z, areaId = GetMarkerDef(i)
|
||||
local x, y, z, areaId = WorldMarkers.GetMarkerDef(i)
|
||||
if x then
|
||||
local msg = "SF:" .. i .. ":" .. x .. ":" .. y .. ":" .. z .. ":" .. areaId
|
||||
log("SEND [" .. ch .. "] " .. msg)
|
||||
@@ -110,7 +110,7 @@ function WorldMarker(index, ...)
|
||||
denyCount = 0
|
||||
local ch = getChannel()
|
||||
if ch then
|
||||
local x, y, z, areaId = GetMarkerDef(index)
|
||||
local x, y, z, areaId = WorldMarkers.GetMarkerDef(index)
|
||||
if x then
|
||||
broadcastPlace(index, x, y, z, areaId)
|
||||
end
|
||||
@@ -185,7 +185,7 @@ local function onAddonMessage(prefix, message, channel, sender)
|
||||
if cmd == "SR" then
|
||||
-- Normal sync request: only leader/assist responds
|
||||
log(" sync request from " .. tostring(sender))
|
||||
if CanSetMarkers() then
|
||||
if CanSetWorldMarkers() then
|
||||
broadcastAllDefs()
|
||||
end
|
||||
return
|
||||
@@ -205,7 +205,7 @@ local function onAddonMessage(prefix, message, channel, sender)
|
||||
end
|
||||
local idx, x, y, z, areaId = parseMarkerFields(parts)
|
||||
if idx then
|
||||
SetMarkerDefSync(idx, x, y, z, areaId, sender)
|
||||
WorldMarkers.SetMarkerDefSync(idx, x, y, z, areaId, sender)
|
||||
else
|
||||
log(" PARSE FAIL")
|
||||
end
|
||||
@@ -216,17 +216,17 @@ local function onAddonMessage(prefix, message, channel, sender)
|
||||
if cmd == "P" then
|
||||
local idx, x, y, z, areaId = parseMarkerFields(parts)
|
||||
if idx then
|
||||
SetMarkerDef(idx, x, y, z, areaId, sender)
|
||||
WorldMarkers.SetMarkerDef(idx, x, y, z, areaId, sender)
|
||||
else
|
||||
log(" PARSE FAIL")
|
||||
end
|
||||
elseif cmd == "C" then
|
||||
local idx = tonumber(parts[2])
|
||||
if idx then
|
||||
ClearMarkerDef(idx, sender)
|
||||
WorldMarkers.ClearMarkerDef(idx, sender)
|
||||
end
|
||||
elseif cmd == "CA" then
|
||||
ClearMarkerDef(sender)
|
||||
WorldMarkers.ClearMarkerDef(sender)
|
||||
else
|
||||
log(" unknown: " .. tostring(cmd))
|
||||
end
|
||||
@@ -244,7 +244,7 @@ local function broadcastSyncRequest()
|
||||
end
|
||||
-- Reset first-responder lock before requesting
|
||||
syncSender = nil
|
||||
local cmd = CanSetMarkers() and "LSR" or "SR"
|
||||
local cmd = CanSetWorldMarkers() and "LSR" or "SR"
|
||||
log("SEND [" .. ch .. "] " .. cmd)
|
||||
SendAddonMessage(MSG_PREFIX, cmd, ch)
|
||||
end
|
||||
@@ -302,7 +302,7 @@ rosterTimer:SetScript("OnUpdate", function()
|
||||
rosterTimer.pending = false
|
||||
rosterTimer.extensions = 0
|
||||
log("roster timer fired, broadcasting")
|
||||
if CanSetMarkers() then
|
||||
if CanSetWorldMarkers() then
|
||||
broadcastAllDefs()
|
||||
end
|
||||
end
|
||||
|
||||
+14
-16
@@ -5,15 +5,19 @@
|
||||
//! Markers persist across zone transitions via MarkerDef definitions.
|
||||
//! Entities are respawned automatically when the player approaches within 200y.
|
||||
//!
|
||||
//! Lua API:
|
||||
//! Lua API (globals):
|
||||
//! WorldMarker(index, x, y, z) — place marker at coordinates
|
||||
//! WorldMarker(index, "unit") — place marker at unit's position
|
||||
//! WorldMarker(index) — place marker at cursor terrain position
|
||||
//! ClearWorldMarker(index) — remove specific marker (1-5)
|
||||
//! ClearWorldMarker() — remove all markers
|
||||
//! SetMarkerDef(i, x, y, z, area) — store definition (no immediate spawn)
|
||||
//! ClearMarkerDef([index]) — clear definition (and entity)
|
||||
//! GetMarkerDef(index) — returns x, y, z, areaId or nil
|
||||
//! CanSetWorldMarkers() — returns 1 if leader/assist, nil otherwise
|
||||
//!
|
||||
//! Lua API (WorldMarkers table — internal, used by addon):
|
||||
//! WorldMarkers.SetMarkerDef(i, x, y, z, area, sender)
|
||||
//! WorldMarkers.SetMarkerDefSync(i, x, y, z, area, sender)
|
||||
//! WorldMarkers.ClearMarkerDef([index,] sender)
|
||||
//! WorldMarkers.GetMarkerDef(index) — returns x, y, z, areaId or nil
|
||||
|
||||
const std = @import("std");
|
||||
const hook = @import("zhook");
|
||||
@@ -151,7 +155,7 @@ fn getNameFromGUID(guid_lo: u32, guid_hi: u32) ?[*:0]const u8 {
|
||||
: [_] "{ecx}" (@as(u32, o.NAME_CACHE_OBJ)),
|
||||
[a] "r" (&stack_args),
|
||||
[func] "r" (@as(u32, o.FN_NAME_CACHE_LOOKUP)),
|
||||
: .{ .edx = true, .memory = true, .cc = true });
|
||||
: .{ .ecx = true, .edx = true, .memory = true, .cc = true });
|
||||
return if (result != 0) @ptrFromInt(result) else null;
|
||||
}
|
||||
|
||||
@@ -340,7 +344,7 @@ fn createEntityInstance(path: [*:0]const u8, pos: *[3]f32, facing: f32, flags: u
|
||||
[_] "{edx}" (@intFromPtr(pos)),
|
||||
[a] "r" (&stack_args),
|
||||
[func] "r" (o.FN_CREATE_ENTITY_INSTANCE),
|
||||
: .{ .memory = true, .cc = true });
|
||||
: .{ .ecx = true, .edx = true, .memory = true, .cc = true });
|
||||
|
||||
return if (result != 0) @ptrFromInt(result) else null;
|
||||
}
|
||||
@@ -351,7 +355,7 @@ fn cleanupEntity(obj: *anyopaque) void {
|
||||
:
|
||||
: [_] "{ecx}" (@intFromPtr(obj)),
|
||||
[func] "r" (o.FN_CLEANUP_ENTITY),
|
||||
: .{ .eax = true, .edx = true, .memory = true, .cc = true });
|
||||
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true });
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -389,7 +393,7 @@ fn playAnimation(entity: *anyopaque, anim_id: u32, queue: bool) void {
|
||||
: [_] "{ecx}" (model),
|
||||
[a] "r" (&stack_args),
|
||||
[func] "r" (o.FN_PLAY_BONE_ANIMATION),
|
||||
: .{ .eax = true, .edx = true, .memory = true, .cc = true });
|
||||
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true });
|
||||
}
|
||||
|
||||
/// Clean up despawning entities whose Decay animation has finished.
|
||||
@@ -800,7 +804,7 @@ pub fn luaGetMarkerDef(L: lua.State) callconv(.c) u32 {
|
||||
return 4;
|
||||
}
|
||||
|
||||
/// Lua: local ok = CanSetMarkers()
|
||||
/// Lua: local ok = CanSetWorldMarkers()
|
||||
/// Returns 1 if the local player has permission (leader/assist), nil otherwise.
|
||||
/// Used by the addon for broadcast/sync decisions.
|
||||
pub fn luaCanSetMarkers(L: lua.State) callconv(.c) u32 {
|
||||
@@ -811,13 +815,7 @@ pub fn luaCanSetMarkers(L: lua.State) callconv(.c) u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Lua: local areaId = GetCurrentAreaId()
|
||||
/// Returns the current zone area ID from the game global.
|
||||
pub fn luaGetCurrentAreaId(L: lua.State) callconv(.c) u32 {
|
||||
const area_id = hook.readMem(u32, o.ZONE_AREA_ID);
|
||||
lua.pushnumber(L, @floatCast(@as(f64, @floatFromInt(area_id))));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// World teardown hook
|
||||
|
||||
@@ -288,7 +288,7 @@ fn luaPushNumber(L_ptr: usize, n: f64) void {
|
||||
[lo] "r" (raw[0]),
|
||||
[hi] "r" (raw[1]),
|
||||
[func] "r" (@as(u32, 0x6F3810)),
|
||||
: .{ .eax = true, .edx = true, .memory = true, .cc = true }
|
||||
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user