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.
This commit is contained in:
MarcelineVQ
2026-04-06 17:42:32 -07:00
parent 5f33944fda
commit 49f3f2452c
2 changed files with 48 additions and 44 deletions
+8 -44
View File
@@ -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;
+40
View File
@@ -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();