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.
This commit is contained in:
@@ -53,7 +53,12 @@ const Patch = struct {
|
|||||||
mask: ?[]const u8 = null,
|
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
|
// Patch 0: Skip duplicate heal text in the SuperWoW handler
|
||||||
// The handler at RVA 0x3BF0 creates floating text then calls the original
|
// 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.
|
// (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 {
|
fn printHex(prefix: []const u8, bytes: []const u8) void {
|
||||||
con.print(prefix);
|
con.print(prefix);
|
||||||
for (bytes) |b| {
|
for (bytes) |b| {
|
||||||
@@ -116,7 +125,21 @@ fn fileOffsetToVA(base: [*]const u8, file_offset: u32) ?[*]u8 {
|
|||||||
return null;
|
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 {
|
pub fn installHooks() void {
|
||||||
con.print("[healtextfix] Module loaded (stub)\n");
|
con.print("[healtextfix] Module loaded (stub)\n");
|
||||||
@@ -134,8 +157,24 @@ pub fn lateInit() void {
|
|||||||
const base: [*]const u8 = @ptrCast(superwow_base.?);
|
const base: [*]const u8 = @ptrCast(superwow_base.?);
|
||||||
con.fmt("[healtextfix] SuperWoWhook.dll at 0x{x}\n", .{@intFromPtr(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;
|
var applied: u32 = 0;
|
||||||
for (patches, 0..) |patch, idx| {
|
for (set.patches, 0..) |patch, idx| {
|
||||||
const va = fileOffsetToVA(base, patch.file_offset) orelse {
|
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 });
|
con.fmt("[healtextfix] Patch {d}: failed to resolve file offset 0x{x}\n", .{ idx, patch.file_offset });
|
||||||
continue;
|
continue;
|
||||||
@@ -179,19 +218,19 @@ pub fn lateInit() void {
|
|||||||
con.fmt("[healtextfix] Patch {d}: applied at VA 0x{x}\n", .{ idx, @intFromPtr(target) });
|
con.fmt("[healtextfix] Patch {d}: applied at VA 0x{x}\n", .{ idx, @intFromPtr(target) });
|
||||||
}
|
}
|
||||||
|
|
||||||
g_patched = applied > 0;
|
if (applied > 0) g_applied_set = set;
|
||||||
con.fmt("[healtextfix] {d}/{d} patches applied\n", .{ applied, patches.len });
|
con.fmt("[healtextfix] {d}/{d} patches applied\n", .{ applied, set.patches.len });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn removeHooks() void {
|
pub fn removeHooks() void {
|
||||||
if (!g_patched) return;
|
const set = g_applied_set orelse return;
|
||||||
|
|
||||||
const superwow_base = GetModuleHandleA("SuperWoWhook.dll");
|
const superwow_base = GetModuleHandleA("SuperWoWhook.dll");
|
||||||
if (superwow_base == null) return;
|
if (superwow_base == null) return;
|
||||||
|
|
||||||
const base: [*]const u8 = @ptrCast(superwow_base.?);
|
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 va = fileOffsetToVA(base, patch.file_offset) orelse continue;
|
||||||
const target: [*]u8 = va;
|
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");
|
con.print("[healtextfix] All patches restored\n");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user