Hook CheckFileExistence so game natively loads addon Bindings.xml
Move the CheckFileExistence (0x654DD0) hook from customassets into main.zig's core file hooks. Embedded files now pass the game's preloadFileWithFlags check, so LoadAddonRecursive handles Bindings.xml loading naturally — respecting addon enabled/disabled state. Only hidden addons still need explicit binding loading.
This commit is contained in:
@@ -5,8 +5,8 @@
|
||||
// 1. Patches patch-?.MPQ → patch-*.MPQ so multi-char patch names work
|
||||
// 2. NOPs two gates in File_FindInArchive so CheckFileExistence runs for
|
||||
// all files, not just Interface/AddOns
|
||||
// 3. Hooks CheckFileExistence with an O(1) hash set of pre-indexed loose
|
||||
// files so non-existent files skip GetFileAttributesA entirely
|
||||
// 3. Indexes loose Data/ files into an O(1) hash set; main.zig's
|
||||
// CheckFileExistence hook calls looseFilesLookup() to serve them
|
||||
//
|
||||
// =============================================================================
|
||||
|
||||
@@ -161,55 +161,32 @@ fn looseFilesCleanup() void {
|
||||
arena.deinit();
|
||||
}
|
||||
|
||||
fn looseFilesLookup(game_path_ptr: u32) ?[*]const u8 {
|
||||
if (game_path_ptr == 0) return null;
|
||||
/// Check if a game path matches a loose disk file. If found and output_buffer_ptr
|
||||
/// is non-zero, writes the disk path into it. Returns true on hit.
|
||||
/// Called from main.zig's CheckFileExistence hook.
|
||||
pub fn looseFilesLookup(game_path_ptr: u32, output_buffer_ptr: u32) bool {
|
||||
if (game_path_ptr == 0) return false;
|
||||
const raw: [*]const u8 = @ptrFromInt(game_path_ptr);
|
||||
const path = raw[0..cStrLen(raw)];
|
||||
|
||||
var norm_buf: [MAX_PATH]u8 = undefined;
|
||||
if (path.len > MAX_PATH) return null;
|
||||
if (path.len > MAX_PATH) return false;
|
||||
@memcpy(norm_buf[0..path.len], path);
|
||||
normalizeInPlace(norm_buf[0..path.len]);
|
||||
|
||||
const result = loose_files.get(norm_buf[0..path.len]);
|
||||
if (result) |disk_path| {
|
||||
return disk_path.ptr;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const disk_path = loose_files.get(norm_buf[0..path.len]) orelse return false;
|
||||
|
||||
// =============================================================================
|
||||
// Hook: CheckFileExistence (0x654DD0)
|
||||
// =============================================================================
|
||||
// __fastcall(ECX=filename, EDX=flags, stack=outputBuffer) → EAX
|
||||
con.fmt("[customassets] loose hit: \"{s}\"\n", .{path});
|
||||
|
||||
const fc: std.builtin.CallingConvention = .{ .x86_fastcall = .{} };
|
||||
const CheckFileExistenceFn = fn (u32, u32, u32) callconv(fc) u32;
|
||||
|
||||
var cfe_hook: hook.Detour(CheckFileExistenceFn) = .{};
|
||||
|
||||
fn checkFileExistenceDetour(filename_ptr: u32, flags: u32, output_buffer_ptr: u32) callconv(fc) u32 {
|
||||
if (filename_ptr != 0) {
|
||||
if (looseFilesLookup(filename_ptr)) |disk_path| {
|
||||
const raw: [*]const u8 = @ptrFromInt(filename_ptr);
|
||||
con.fmt("[customassets] loose hit: \"{s}\"\n", .{raw[0..cStrLen(raw)]});
|
||||
|
||||
if (output_buffer_ptr != 0) {
|
||||
const disk_len = cStrLen(disk_path);
|
||||
const out: [*]u8 = @ptrFromInt(output_buffer_ptr);
|
||||
if (disk_len < MAX_PATH) {
|
||||
@memcpy(out[0..disk_len], disk_path[0..disk_len]);
|
||||
out[disk_len] = 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
if (output_buffer_ptr != 0) {
|
||||
const disk_len = cStrLen(disk_path.ptr);
|
||||
const out: [*]u8 = @ptrFromInt(output_buffer_ptr);
|
||||
if (disk_len < MAX_PATH) {
|
||||
@memcpy(out[0..disk_len], disk_path.ptr[0..disk_len]);
|
||||
out[disk_len] = 0;
|
||||
}
|
||||
}
|
||||
return cfe_hook.callOriginal(.{ filename_ptr, flags, output_buffer_ptr });
|
||||
}
|
||||
|
||||
fn installHook() bool {
|
||||
return cfe_hook.attach(0x654DD0, &checkFileExistenceDetour) == .ok;
|
||||
return true;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -299,13 +276,11 @@ pub fn installHooks() void {
|
||||
applyGlobPatch();
|
||||
applyLooseFilePatches();
|
||||
looseFilesInit();
|
||||
if (!installHook()) return;
|
||||
installed = true;
|
||||
}
|
||||
|
||||
pub fn removeHooks() void {
|
||||
if (g_is_hook_owner and installed) {
|
||||
cfe_hook.detach();
|
||||
revertLooseFilePatches();
|
||||
revertGlobPatch();
|
||||
looseFilesCleanup();
|
||||
|
||||
+39
-13
@@ -751,6 +751,29 @@ fn loadModelAsyncDetour(model: u32, file_handle: u32, should_use_callback: u32)
|
||||
return model_load_hook.callOriginal(.{ model, file_handle, should_use_callback });
|
||||
}
|
||||
|
||||
// --- Hook 6: CheckFileExistence (0x654DD0) ---
|
||||
// __fastcall(ECX=filename, EDX=flags, stack=outputBuffer) → EAX (bool)
|
||||
// Tells the game whether a file exists in the VFS. Without this, embedded
|
||||
// files are invisible to preloadFileWithFlags, so LoadAddonRecursive skips
|
||||
// Bindings.xml (and SavedVariables) for our addons.
|
||||
|
||||
const CheckFileExistenceFn = fn (u32, u32, u32) callconv(fc) u32;
|
||||
var cfe_hook: hook.Detour(CheckFileExistenceFn) = .{};
|
||||
|
||||
fn checkFileExistenceDetour(filename_ptr: u32, flags: u32, output_buffer_ptr: u32) callconv(fc) u32 {
|
||||
if (filename_ptr != 0) {
|
||||
// Embedded DLL files
|
||||
const path: [*:0]const u8 = @ptrFromInt(filename_ptr);
|
||||
if (findEmbeddedFile(path) != null) return 1;
|
||||
|
||||
// Loose disk files (customassets module)
|
||||
if (build_opts.customassets and customassets.isActive()) {
|
||||
if (customassets.looseFilesLookup(filename_ptr, output_buffer_ptr)) return 1;
|
||||
}
|
||||
}
|
||||
return cfe_hook.callOriginal(.{ filename_ptr, flags, output_buffer_ptr });
|
||||
}
|
||||
|
||||
// --- Install/remove in-memory file hooks ---
|
||||
|
||||
fn installFileHooks() void {
|
||||
@@ -759,10 +782,12 @@ fn installFileHooks() void {
|
||||
_ = read_file_hook.attach(0x648460, &readFileDetour);
|
||||
_ = cleanup_file_handle_hook.attach(0x648730, &cleanupFileHandleDetour);
|
||||
_ = model_load_hook.attach(0x71d4e0, &loadModelAsyncDetour);
|
||||
_ = cfe_hook.attach(0x654DD0, &checkFileExistenceDetour);
|
||||
con.print("[file] in-memory file hooks installed\n");
|
||||
}
|
||||
|
||||
fn removeFileHooks() void {
|
||||
cfe_hook.detach();
|
||||
model_load_hook.detach();
|
||||
process_async_hook.detach();
|
||||
cleanup_file_handle_hook.detach();
|
||||
@@ -828,10 +853,10 @@ fn callLoadAddonTOC(addon_name: [*:0]const u8) void {
|
||||
|
||||
// =============================================================================
|
||||
// Hook: LoadAddonsRecursively (0x51F600)
|
||||
// With SetupAddonProcessing registering our addons, the game's own
|
||||
// LoadAddonRecursive now handles loading files, Bindings.xml, and saved
|
||||
// variables for embedded addons. This hook is kept for any future
|
||||
// post-load work but no longer manually loads addon files.
|
||||
// Non-hidden addons are fully handled by the game's LoadAddonRecursive (which
|
||||
// respects enabled/disabled state) — our CheckFileExistence hook 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) = .{};
|
||||
@@ -866,16 +891,17 @@ fn loadAddonsDetour(error_handler: u32) callconv(fc) void {
|
||||
error_handler,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Bindings.xml must be loaded explicitly for all addons -- the game's
|
||||
// preloadFileWithFlags doesn't go through our file hook.
|
||||
if (comptime hasFile(paths, "Bindings.xml")) {
|
||||
callLoadUIBindingsFromFile(
|
||||
"Interface\\AddOns\\" ++ addon_name ++ "\\Bindings.xml",
|
||||
&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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user