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:
MarcelineVQ
2026-03-08 12:37:59 -07:00
parent 00e7e8b472
commit 9e4cbe4aaf
2 changed files with 56 additions and 55 deletions
+17 -42
View File
@@ -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();