From 49f3f2452c3fc0f14f90f6a0cd131434387822bc Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Mon, 6 Apr 2026 17:42:32 -0700 Subject: [PATCH] fix: move CheckFileExistence gate NOPs to core file hooks File_FindInArchive has two conditional jumps that skip calling CheckFileExistence. Without NOPing them, preloadFileWithFlags never reaches our hook and the game skips Bindings.xml loading for embedded addons. This was previously only done by the customassets module, so standalone variant DLLs (without customassets) failed to load addon keybindings. --- src/customassets/customassets.zig | 52 +++++-------------------------- src/main.zig | 40 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/customassets/customassets.zig b/src/customassets/customassets.zig index da88a9c..d5fb2eb 100644 --- a/src/customassets/customassets.zig +++ b/src/customassets/customassets.zig @@ -2,12 +2,13 @@ // customassets - Loose file loading & permissive patch glob // ============================================================================= // -// 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. Indexes loose Data/ files into an O(1) hash set; main.zig's +// 1. Patches patch-?.MPQ -> patch-*.MPQ so multi-char patch names work +// 2. Indexes loose Data/ files into an O(1) hash set; main.zig's // CheckFileExistence hook calls looseFilesLookup() to serve them // +// Note: File_FindInArchive gate NOPs (for CheckFileExistence) moved to +// main.zig core file hooks so all builds with embedded addons get them. +// // ============================================================================= const std = @import("std"); @@ -211,44 +212,6 @@ fn revertGlobPatch() void { } } -// ============================================================================= -// Patches 2 & 3: NOP loose file gates in File_FindInArchive -// ============================================================================= - -var old_jz: [2]u8 = undefined; -var jz_patched: bool = false; -var old_jnz: [2]u8 = undefined; -var jnz_patched: bool = false; - -fn applyLooseFilePatches() void { - const nops = [2]u8{ 0x90, 0x90 }; - - // Gate 1: JZ at 0x654b5c (74 25) - if (hook.readMem(u8, 0x654b5c) == 0x74 and hook.readMem(u8, 0x654b5d) == 0x25) { - old_jz = .{ 0x74, 0x25 }; - hook.writeProtected(0x654b5c, &nops); - jz_patched = true; - } - - // Gate 2: JNZ at 0x654b6a (75 17) - if (hook.readMem(u8, 0x654b6a) == 0x75 and hook.readMem(u8, 0x654b6b) == 0x17) { - old_jnz = .{ 0x75, 0x17 }; - hook.writeProtected(0x654b6a, &nops); - jnz_patched = true; - } -} - -fn revertLooseFilePatches() void { - if (jz_patched) { - hook.writeProtected(0x654b5c, &old_jz); - jz_patched = false; - } - if (jnz_patched) { - hook.writeProtected(0x654b6a, &old_jnz); - jnz_patched = false; - } -} - // ============================================================================= // Init / Cleanup // ============================================================================= @@ -276,14 +239,15 @@ pub fn installHooks() void { log = logging.Logger.open(module_name, .console); applyGlobPatch(); - applyLooseFilePatches(); + // Gate NOPs for CheckFileExistence are now in main.zig (core file hooks) + // so all builds with embedded addons get them. No need to apply here. looseFilesInit(); installed = true; } pub fn removeHooks() void { if (g_is_hook_owner and installed) { - revertLooseFilePatches(); + // Gate reverts are now in main.zig removeFileHooks() revertGlobPatch(); looseFilesCleanup(); installed = false; diff --git a/src/main.zig b/src/main.zig index 3055830..642c3ec 100644 --- a/src/main.zig +++ b/src/main.zig @@ -604,10 +604,50 @@ fn installFileHooks() void { _ = cleanup_file_handle_hook.attach(0x648730, &cleanupFileHandleDetour); _ = model_load_hook.attach(0x71d4e0, &loadModelAsyncDetour); _ = cfe_hook.attach(0x654DD0, &checkFileExistenceDetour); + // NOP two gates in File_FindInArchive that prevent CheckFileExistence + // from running. Without this, preloadFileWithFlags never reaches our + // hook and the game skips Bindings.xml for embedded addons. + // (customassets also does this for loose files; the patches are idempotent.) + nopFileExistenceGates(); log.print("in-memory file hooks installed\n"); } +// File_FindInArchive (0x6549a0) has two conditional jumps that skip the +// call to CheckFileExistence (0x654DD0). NOP them so our hook always runs. +var old_gate1: [2]u8 = undefined; +var gate1_patched: bool = false; +var old_gate2: [2]u8 = undefined; +var gate2_patched: bool = false; + +fn nopFileExistenceGates() void { + const nops = [2]u8{ 0x90, 0x90 }; + // Gate 1: JZ at 0x654b5c (74 25) -- skips CheckFileExistence + if (hook.readMem(u8, 0x654b5c) == 0x74 and hook.readMem(u8, 0x654b5d) == 0x25) { + old_gate1 = .{ 0x74, 0x25 }; + hook.writeProtected(0x654b5c, &nops); + gate1_patched = true; + } + // Gate 2: JNZ at 0x654b6a (75 17) -- skips CheckFileExistence + if (hook.readMem(u8, 0x654b6a) == 0x75 and hook.readMem(u8, 0x654b6b) == 0x17) { + old_gate2 = .{ 0x75, 0x17 }; + hook.writeProtected(0x654b6a, &nops); + gate2_patched = true; + } +} + +fn revertFileExistenceGates() void { + if (gate1_patched) { + hook.writeProtected(0x654b5c, &old_gate1); + gate1_patched = false; + } + if (gate2_patched) { + hook.writeProtected(0x654b6a, &old_gate2); + gate2_patched = false; + } +} + fn removeFileHooks() void { + revertFileExistenceGates(); cfe_hook.detach(); model_load_hook.detach(); process_async_hook.detach();