Hide embedded addons from login addon list via +0x29 flag

All embedded addons now register through LoadAddonTOC (no more
separate hidden loading path). For addons marked hidden, walk the
addon linked list after registration and set the +0x29 exclusion
byte so DeserializeAddonData skips them when building the flat
display array. Removes the LoadAddonsRecursively hook since the
game's native LoadAddonRecursive handles all addon loading.
This commit is contained in:
MarcelineVQ
2026-03-08 13:13:46 -07:00
parent 7ccab8f802
commit 95df207eb8
+24 -99
View File
@@ -309,17 +309,39 @@ fn setupAddonsDetour(mgr_ptr: u32) callconv(fc) void {
if (comptime mod.addon_name == null) continue;
if (!@field(build_options, "enable_" ++ mod.option)) continue;
if (mod.hidden) continue;
const active = if (mod.is_active) |f| f() else true;
if (active) {
const name: [*:0]const u8 = comptime (mod.addon_name.? ++ "\x00").ptr;
con.fmt("[addons] registering embedded addon: {s}\n", .{name});
callLoadAddonTOC(name);
if (mod.hidden) {
hideAddonFromList(name);
}
}
}
}
/// Find an addon struct in the linked list by name and set +0x29 to 1,
/// which excludes it from the flat display array built by DeserializeAddonData.
fn hideAddonFromList(name: [*:0]const u8) void {
// Walk the addon linked list (PTR_00be1b6c). Each node has:
// +0x14: name pointer
// next: *(PTR_00be1b64 + 4 + node)
const list_base = hook.readMem(u32, 0x00be1b64);
var node = hook.readMem(u32, 0x00be1b6c);
while (node != 0 and (node & 1) == 0) {
const node_name = hook.readMem([*:0]const u8, node + 0x14);
if (std.mem.orderZ(u8, node_name, name) == .eq) {
hook.writeMem(node + 0x29, &[_]u8{1});
con.fmt("[addons] hidden addon {s} excluded from list (+0x29=1)\n", .{name});
return;
}
node = hook.readMem(u32, list_base + 4 + node);
}
con.fmt("[addons] WARNING: could not find {s} in addon list\n", .{name});
}
fn callLoadAddonTOC(addon_name: [*:0]const u8) void {
asm volatile (
\\call *%[func]
@@ -329,101 +351,6 @@ fn callLoadAddonTOC(addon_name: [*:0]const u8) void {
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true });
}
// =============================================================================
// Hook: LoadAddonsRecursively (0x51F600)
// Non-hidden addons are fully handled by the game's LoadAddonRecursive (which
// respects enabled/disabled state) — the CheckFileExistence hook in main.zig
// makes their embedded files visible to preloadFileWithFlags. Hidden addons
// bypass LoadAddonRecursive, so we load their TOC and bindings explicitly here.
// =============================================================================
var load_addons_hook: hook.Detour(fn (u32) callconv(fc) void) = .{};
fn loadAddonsDetour(error_handler: u32) callconv(fc) void {
load_addons_hook.callOriginal(.{error_handler});
var md5ctx = std.mem.zeroes([88]u8);
inline for (embed_modules) |mod| {
if (comptime mod.addon_name != null and
@field(build_options, "enable_" ++ mod.option) and
mod.hidden)
{
const active = if (mod.is_active) |f| f() else true;
if (active) {
const addon_name = comptime mod.addon_name.?;
const paths = comptime @field(build_options, mod.addon_files_opt.?);
// Hidden addons are not registered via LoadAddonTOC, so the game
// doesn't know about them. Load their files directly to keep them
// invisible in the addon list.
const toc_name = comptime findTocName(paths);
if (toc_name) |tn| {
callLoadFileListWithIncludes(
"Interface\\AddOns\\" ++ addon_name ++ "\\" ++ tn,
&md5ctx,
error_handler,
);
}
// Hidden addons bypass LoadAddonRecursive, so load bindings
// explicitly. Non-hidden addons get theirs loaded by the game
// (which respects addon enabled/disabled state).
if (comptime hasFile(paths, "Bindings.xml")) {
callLoadUIBindingsFromFile(
"Interface\\AddOns\\" ++ addon_name ++ "\\Bindings.xml",
&md5ctx,
error_handler,
);
}
}
}
}
}
fn findTocName(comptime paths: []const []const u8) ?[]const u8 {
for (paths) |path| {
const name = comptimeBasename(path);
if (name.len >= 4 and eqlSlice(name[name.len - 4 ..], ".toc")) return name;
}
return null;
}
fn hasFile(comptime paths: []const []const u8, comptime target: []const u8) bool {
for (paths) |path| {
if (eqlSlice(comptimeBasename(path), target)) return true;
}
return false;
}
fn callLoadFileListWithIncludes(toc_path: [*:0]const u8, md5ctx: *[88]u8, error_handler: u32) void {
asm volatile (
\\push %[eh]
\\call *%[func]
:
: [_] "{ecx}" (@intFromPtr(toc_path)),
[_] "{edx}" (@intFromPtr(md5ctx)),
[eh] "r" (error_handler),
[func] "r" (@as(u32, 0x6EDB90)),
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true });
}
fn callLoadUIBindingsFromFile(path: [*:0]const u8, md5ctx: *[88]u8, callback: u32) void {
const binding_mgr = hook.readMem(u32, 0xB71290);
asm volatile (
\\push %[cb]
\\push %[md5]
\\push %[path]
\\call *%[func]
:
: [_] "{ecx}" (binding_mgr),
[func] "{edx}" (@as(u32, 0x4B6F70)),
[path] "r" (@intFromPtr(path)),
[md5] "r" (@intFromPtr(md5ctx)),
[cb] "r" (callback),
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true });
}
// =============================================================================
// Install / Remove
// =============================================================================
@@ -440,11 +367,9 @@ const has_addons = blk: {
pub fn install() void {
if (!has_addons) return;
_ = setup_addons_hook.attach(0x51C740, &setupAddonsDetour);
_ = load_addons_hook.attach(0x51F600, &loadAddonsDetour);
}
pub fn uninstall() void {
if (!has_addons) return;
load_addons_hook.detach();
setup_addons_hook.detach();
}