From 43313f439d0d103be90f247a185409ca8f7522dc Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Mon, 2 Mar 2026 05:58:29 -0800 Subject: [PATCH] Add version-keyed patch sets to healtextfix Detect SuperWoW version at runtime by scanning the DLL for SUPERWOW_VERSION="..." and match against known PatchSet entries. This allows adding patches for future SuperWoW versions without losing support for older ones. Skips gracefully if version is unrecognized. --- src/healtextfix/healtextfix.zig | 55 ++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/src/healtextfix/healtextfix.zig b/src/healtextfix/healtextfix.zig index 7d1788d..bebf24b 100644 --- a/src/healtextfix/healtextfix.zig +++ b/src/healtextfix/healtextfix.zig @@ -53,7 +53,12 @@ const Patch = struct { mask: ?[]const u8 = null, }; -const patches = [_]Patch{ +const PatchSet = struct { + version: []const u8, + patches: []const Patch, +}; + +const v1_5_patches = [_]Patch{ // Patch 0: Skip duplicate heal text in the SuperWoW handler // The handler at RVA 0x3BF0 creates floating text then calls the original // (which also creates text). JMP from 0x3C06 to the call-through at 0x3C82. @@ -76,6 +81,10 @@ const patches = [_]Patch{ }, }; +const patch_sets = [_]PatchSet{ + .{ .version = "1.5", .patches = &v1_5_patches }, +}; + fn printHex(prefix: []const u8, bytes: []const u8) void { con.print(prefix); for (bytes) |b| { @@ -116,7 +125,21 @@ fn fileOffsetToVA(base: [*]const u8, file_offset: u32) ?[*]u8 { return null; } -var g_patched: bool = false; +var g_applied_set: ?*const PatchSet = null; + +/// Scan the DLL's mapped memory for SUPERWOW_VERSION="..." and extract the version string. +fn detectVersion(base: [*]const u8) ?[]const u8 { + const scan_len = 0x20000; + const needle = "SUPERWOW_VERSION=\""; + const mem = base[0..scan_len]; + + const pos = std.mem.indexOf(u8, mem, needle) orelse return null; + const ver_start = pos + needle.len; + // Find closing quote + const remaining = mem[ver_start..]; + const end = std.mem.indexOfScalar(u8, remaining, '"') orelse return null; + return remaining[0..end]; +} pub fn installHooks() void { con.print("[healtextfix] Module loaded (stub)\n"); @@ -134,8 +157,24 @@ pub fn lateInit() void { const base: [*]const u8 = @ptrCast(superwow_base.?); con.fmt("[healtextfix] SuperWoWhook.dll at 0x{x}\n", .{@intFromPtr(base)}); + // Detect SuperWoW version + const version = detectVersion(base) orelse { + con.print("[healtextfix] Could not detect SuperWoW version, skipping\n"); + return; + }; + con.fmt("[healtextfix] Detected SuperWoW version: {s}\n", .{version}); + + // Find matching patch set + const set: *const PatchSet = blk: { + for (&patch_sets) |*ps| { + if (std.mem.eql(u8, ps.version, version)) break :blk ps; + } + con.fmt("[healtextfix] No patches for version \"{s}\", skipping\n", .{version}); + return; + }; + var applied: u32 = 0; - for (patches, 0..) |patch, idx| { + for (set.patches, 0..) |patch, idx| { const va = fileOffsetToVA(base, patch.file_offset) orelse { con.fmt("[healtextfix] Patch {d}: failed to resolve file offset 0x{x}\n", .{ idx, patch.file_offset }); continue; @@ -179,19 +218,19 @@ pub fn lateInit() void { con.fmt("[healtextfix] Patch {d}: applied at VA 0x{x}\n", .{ idx, @intFromPtr(target) }); } - g_patched = applied > 0; - con.fmt("[healtextfix] {d}/{d} patches applied\n", .{ applied, patches.len }); + if (applied > 0) g_applied_set = set; + con.fmt("[healtextfix] {d}/{d} patches applied\n", .{ applied, set.patches.len }); } pub fn removeHooks() void { - if (!g_patched) return; + const set = g_applied_set orelse return; const superwow_base = GetModuleHandleA("SuperWoWhook.dll"); if (superwow_base == null) return; const base: [*]const u8 = @ptrCast(superwow_base.?); - for (patches, 0..) |patch, idx| { + for (set.patches, 0..) |patch, idx| { const va = fileOffsetToVA(base, patch.file_offset) orelse continue; const target: [*]u8 = va; @@ -211,6 +250,6 @@ pub fn removeHooks() void { } } - g_patched = false; + g_applied_set = null; con.print("[healtextfix] All patches restored\n"); }