rename performance to weirdperformance, move file cache hook into module

Move fileFindDetour from main.zig into filecache.zig with its own
install/remove. Remove file_cache import and all references from
main.zig. Rename module to weirdperformance throughout (build flag,
module_name, source file, main.zig variable).
This commit is contained in:
MarcelineVQ
2026-03-24 17:48:22 -07:00
parent 979e5729bc
commit 4629ea71d6
5 changed files with 130 additions and 131 deletions
+8 -9
View File
@@ -109,19 +109,16 @@ No configuration needed, install and forget.
### Utility Minimap Trackings
Adds TBC/WotLK-style minimap tracking icons for NPC types, game objects, and quest givers.
Replaces the native tracking dropdown with a combined menu showing both spell tracking and NPC category tracking.
Can be disabled from the normal AddOn menu. Preferences saved per-character.
Adds TBC/WotLK-style minimap tracking icons for NPC types, game objects, and quest givers.
Replaces the native tracking dropdown with a combined menu showing both spell tracking and NPC category tracking.
Can be disabled from the normal AddOn menu. Preferences saved per-character.
- Click the minimap tracking icon to open the dropdown
- Check/uncheck NPC categories to toggle their minimap icons
- Spell tracking (Hunter tracking, Find Herbs, etc.) remains available alongside NPC tracking
- "Hide in Cities" toggle suppresses NPC icons in capital cities
NPC types: Auctioneer, Banker, Battle Master, Flight Master, Innkeeper, Repair, Stable Master, Class Trainer, Profession Trainer, and five vendor subtypes (Reagent, Poison, Trade Goods, Ammunition, General)
Game objects: Mailbox, Refreshment Table, Goblin Brainwashing Device
Quest tracking: nearby NPCs with available quests (yellow !) on the minimap
Vendor subtypes are identified by localized subname matching (EN, FR, DE, CN, ES, RU)
Tracks various npc types and useful objects like Oranged and Brainwasher and Mailbox.
**DLL:** `minimapicons.dll`
@@ -249,11 +246,13 @@ Engine-level optimizations that reduce CPU time on math, rendering helpers, file
- **SIMD Math** — replaces 20+ internal math functions with SSE/AVX equivalents covering skeletal animation, particle rendering, frustum culling, collision detection, text glyph caching, and float-to-integer conversion
- **Data Decompression** — swaps the game's 2004-era zlib with a modern library (2.2x faster). Loading screen times reduced by at least 13%
- **MPQ File Cache** — caches archive file lookups so repeat file opens skip the expensive archive chain walk. Cache hits cost ~1/30th of a full search, saving 50-160ms every 15 seconds during heavy gameplay
- **MPQ File Cache** — caches archive file lookups so repeat file opens skip the archive chain walk. Cache hits cost ~1/30th of a full search, saving 50-160ms every 15 seconds during heavy gameplay
- **Timer Calibration** — recalibrates the TSC frequency using the OS performance counter for accurate animation timing. Requests higher OS timer resolution (0.5ms) and disables Windows 11 power throttling. Ported from [VanillaFixes](https://github.com/hannesmann/vanillafixes)
Most noticeable in cities, raids, and during zone transitions.
**DLL:** `weirdperformance.dll`
---
## Why No Source Code?
@@ -281,7 +280,7 @@ WeirdUtils exports three functions for querying and disabling modules at runtime
Module names are case-insensitive and match the released dll names:
`customassets`, `framecrash`, `logsessions`, `transmogfix`, `minimapicons`, `healtextfix`, `bigcursor`, `worldmarkers`, `interact`, `outline`, `pngscreenshots`, `clickthrough`, `dpslog`, `filecache`, `performance`
`customassets`, `framecrash`, `logsessions`, `transmogfix`, `minimapicons`, `healtextfix`, `bigcursor`, `worldmarkers`, `interact`, `outline`, `pngscreenshots`, `clickthrough`, `dpslog`, `weirdperformance`
There is no re-enable API.
+1 -1
View File
@@ -33,7 +33,7 @@ const module_list = [_]ModuleDesc{
.{ .name = "addonperf", .desc = "Enable addon memory/CPU profiling API", .default = false },
.{ .name = "ssemaths", .desc = "Enable UnitXP x87 math polyfill replacements (SSE)", .default = false },
.{ .name = "silicon", .desc = "Enable SSE2 math replacements (ported from libSiliconPatch)", .default = false },
.{ .name = "performance", .desc = "Enable production SSE hooks (bone, particle, glyph cache)", .default = true },
.{ .name = "weirdperformance", .desc = "Enable production performance optimizations (SSE, inflate, filecache, timer)", .default = true },
};
pub fn build(b: *std.Build) void {
+3 -120
View File
@@ -20,10 +20,9 @@ const build_opts = struct {
const dpslog = @import("build_options").enable_dpslog;
const transform44 = @import("build_options").enable_transform44;
const addonperf = @import("build_options").enable_addonperf;
const filecache = @import("build_options").enable_filecache;
const ssemaths = @import("build_options").enable_ssemaths;
const silicon = @import("build_options").enable_silicon;
const performance = @import("build_options").enable_performance;
const weirdperformance = @import("build_options").enable_weirdperformance;
};
// Conditional module imports
@@ -43,9 +42,8 @@ const dpslog = if (build_opts.dpslog) @import("dpslog/dpslog.zig") else struct {
const transform44 = if (build_opts.transform44) @import("transform44/transform44.zig") else struct {};
const addonperf = if (build_opts.addonperf) @import("addonperf/addonperf.zig") else struct {};
const ssemaths = if (build_opts.ssemaths) @import("ssemaths/ssemaths.zig") else struct {};
const file_cache = if (build_opts.performance) @import("performance/filecache.zig") else struct {};
const silicon = if (build_opts.silicon) @import("silicon/silicon.zig") else struct {};
const performance = if (build_opts.performance) @import("performance/performance.zig") else struct {};
const weirdperformance = if (build_opts.weirdperformance) @import("performance/weirdperformance.zig") else struct {};
const module_active = @import("module_active.zig");
@@ -283,13 +281,6 @@ var process_async_hook: hook.Detour(ProcessAsyncFn) = .{};
const LoadModelFn = fn (u32, u32, u32) callconv(hook.cc.thiscall) u32;
var model_load_hook: hook.Detour(LoadModelFn) = .{};
// File_FindInArchive (0x6549a0) — Storm internal MPQ file lookup
// __fastcall(ECX=archive_or_group, EDX=filename, stack: flags, out_inner_archive,
// out_outer_archive, out_block_entry, out_disk_path) → int
// Returns: 0=not found, 1=found in MPQ, 2=found on disk, 3=deleted
const FileFindFn = fn (u32, u32, u32, u32, u32, u32, u32) callconv(hook.cc.fastcall) u32;
var file_find_hook: hook.Detour(FileFindFn) = .{};
// Windows API imports for async handling
extern "kernel32" fn EnterCriticalSection(lpCriticalSection: *anyopaque) callconv(WINAPI) void;
extern "kernel32" fn LeaveCriticalSection(lpCriticalSection: *anyopaque) callconv(WINAPI) void;
@@ -600,109 +591,6 @@ fn checkFileExistenceDetour(filename_ptr: u32, flags: u32, output_buffer_ptr: u3
// Intercepts the core MPQ file lookup to skip both chain walk and hash lookup
// on repeat opens. Caches {outer_archive, inner_archive, block_entry} per file.
//
// Called from two paths during each open:
// 1. FindFileInArchive wrapper (param_1=0): walks all archives, uses param_5 for output
// 2. File_FindInStorage (param_1=specific): single archive, uses param_4 + param_6
//
// We cache on path 2 (has all data), serve both paths from cache on subsequent opens.
fn fileFindDetour(
archive_or_group: u32, // ECX: 0 = search all, else specific archive/group
filename_ptr: u32, // EDX: filename string
flags: u32,
out_inner_archive: u32, // ptr to ptr: inner archive (File_FindInStorage uses this)
out_outer_archive: u32, // ptr to ptr: outer archive (FindFileInArchive wrapper uses this)
out_block_entry: u32, // ptr to ptr: block table entry data
out_disk_path: u32, // ptr to buf: disk path output
) callconv(hook.cc.fastcall) u32 {
if (!build_opts.performance or filename_ptr == 0)
return file_find_hook.callOriginal(.{ archive_or_group, filename_ptr, flags, out_inner_archive, out_outer_archive, out_block_entry, out_disk_path });
const tsc_start = file_cache.rdtsc();
const path: [*:0]const u8 = @ptrFromInt(filename_ptr);
const h = file_cache.hashPath(path);
cache_check: {
const cached = file_cache.archiveCacheLookup(h, path) orelse break :cache_check;
if (cached.is_negative and archive_or_group == 0) {
file_cache.recordNegativeHit();
if (out_outer_archive != 0) @as(*align(1) u32, @ptrFromInt(out_outer_archive)).* = 0;
if (out_inner_archive != 0) @as(*align(1) u32, @ptrFromInt(out_inner_archive)).* = 0;
if (out_block_entry != 0) @as(*align(1) u32, @ptrFromInt(out_block_entry)).* = 0;
hook.call(fn (u32) callconv(hook.cc.stdcall) void, 0x64e850, .{2});
file_cache.addHitCycles(file_cache.rdtsc() - tsc_start);
return 0;
}
if (!cached.is_negative) {
// Path 1: search-all
if (archive_or_group == 0 and out_outer_archive != 0) {
const valid_outer = if (cached.outer_archive != 0)
hook.call(fn (u32, u32) callconv(hook.cc.fastcall) u32, 0x650780, .{ cached.outer_archive, 0 })
else
0;
if (valid_outer == 0 and cached.outer_archive != 0) break :cache_check;
@as(*align(1) u32, @ptrFromInt(out_outer_archive)).* = valid_outer;
if (out_inner_archive != 0 and cached.inner_archive != 0) {
const valid_inner = hook.call(fn (u32, u32) callconv(hook.cc.fastcall) u32, 0x650780, .{ cached.inner_archive, 0 });
@as(*align(1) u32, @ptrFromInt(out_inner_archive)).* = valid_inner;
} else if (out_inner_archive != 0) {
@as(*align(1) u32, @ptrFromInt(out_inner_archive)).* = 0;
}
if (out_block_entry != 0) {
@as(*align(1) u32, @ptrFromInt(out_block_entry)).* = file_cache.computeBlockEntry(cached.inner_archive, cached.block_index);
}
file_cache.recordCacheHit();
file_cache.addHitCycles(file_cache.rdtsc() - tsc_start);
return 1;
}
// Path 2: specific archive
if (archive_or_group != 0 and (archive_or_group == cached.outer_archive or archive_or_group == cached.inner_archive)) {
if (out_outer_archive != 0 and cached.outer_archive != 0) {
const valid = hook.call(fn (u32, u32) callconv(hook.cc.fastcall) u32, 0x650780, .{ cached.outer_archive, 0 });
if (valid == 0) break :cache_check;
@as(*align(1) u32, @ptrFromInt(out_outer_archive)).* = valid;
} else if (out_outer_archive != 0) {
@as(*align(1) u32, @ptrFromInt(out_outer_archive)).* = 0;
}
if (out_inner_archive != 0 and cached.inner_archive != 0) {
const valid = hook.call(fn (u32, u32) callconv(hook.cc.fastcall) u32, 0x650780, .{ cached.inner_archive, 0 });
if (valid == 0) break :cache_check;
@as(*align(1) u32, @ptrFromInt(out_inner_archive)).* = valid;
} else if (out_inner_archive != 0) {
@as(*align(1) u32, @ptrFromInt(out_inner_archive)).* = 0;
}
if (out_block_entry != 0) {
@as(*align(1) u32, @ptrFromInt(out_block_entry)).* = file_cache.computeBlockEntry(cached.inner_archive, cached.block_index);
}
file_cache.recordCacheHit();
file_cache.addHitCycles(file_cache.rdtsc() - tsc_start);
return 1;
}
}
}
// Cache miss — call original and populate cache
file_cache.recordCacheMiss();
const ret = file_find_hook.callOriginal(.{ archive_or_group, filename_ptr, flags, out_inner_archive, out_outer_archive, out_block_entry, out_disk_path });
file_cache.addMissCycles(file_cache.rdtsc() - tsc_start);
if (archive_or_group != 0 and out_inner_archive != 0 and out_block_entry != 0) {
if (ret == 1) {
const inner = hook.readMem(u32, out_inner_archive);
const block = hook.readMem(u32, out_block_entry);
file_cache.archiveCacheInsert(h, path, archive_or_group, inner, block, false);
}
}
if (archive_or_group == 0 and ret == 0) {
file_cache.archiveCacheInsert(h, path, 0, 0, 0, true);
}
return ret;
}
// --- Install/remove in-memory file hooks ---
fn installFileHooks() void {
@@ -712,15 +600,10 @@ fn installFileHooks() void {
_ = cleanup_file_handle_hook.attach(0x648730, &cleanupFileHandleDetour);
_ = model_load_hook.attach(0x71d4e0, &loadModelAsyncDetour);
_ = cfe_hook.attach(0x654DD0, &checkFileExistenceDetour);
if (build_opts.performance) {
_ = file_find_hook.attach(0x6549a0, &fileFindDetour);
log.print("archive cache hook installed\n");
}
log.print("in-memory file hooks installed\n");
}
fn removeFileHooks() void {
file_find_hook.detach();
cfe_hook.detach();
model_load_hook.detach();
process_async_hook.detach();
@@ -856,7 +739,7 @@ const modules = [_]ModuleHooks{
if (build_opts.outline) .{ .name = outline.module_name, .remove = outline.cleanup, .is_active = outline.isActive } else .{},
if (build_opts.screenshot) .{ .name = screenshot.module_name, .remove = screenshot.removeHook, .is_active = screenshot.isActive } else .{},
if (build_opts.silicon) .{ .name = silicon.module_name, .install = silicon.installHooks, .remove = silicon.removeHooks, .is_active = silicon.isActive } else .{},
if (build_opts.performance) .{ .name = performance.module_name, .install = performance.installHooks, .remove = performance.removeHooks, .is_active = performance.isActive } else .{},
if (build_opts.weirdperformance) .{ .name = weirdperformance.module_name, .install = weirdperformance.installHooks, .remove = weirdperformance.removeHooks, .is_active = weirdperformance.isActive } else .{},
};
fn shutdownDetour() callconv(hook.cc.stdcall) void {
+113
View File
@@ -144,6 +144,119 @@ pub fn getSlotOccupant(h: u32) ?[]const u8 {
return null;
}
// =============================================================================
// File_FindInArchive hook (0x6549a0)
// =============================================================================
const FileFindFn = fn (u32, u32, u32, u32, u32, u32, u32) callconv(hook.cc.fastcall) u32;
var file_find_hook: hook.Detour(FileFindFn) = .{};
fn fileFindDetour(
archive_or_group: u32,
filename_ptr: u32,
flags: u32,
out_inner_archive: u32,
out_outer_archive: u32,
out_block_entry: u32,
out_disk_path: u32,
) callconv(hook.cc.fastcall) u32 {
if (filename_ptr == 0)
return file_find_hook.callOriginal(.{ archive_or_group, filename_ptr, flags, out_inner_archive, out_outer_archive, out_block_entry, out_disk_path });
const tsc_start = rdtsc();
const path: [*:0]const u8 = @ptrFromInt(filename_ptr);
const h = hashPath(path);
cache_check: {
const cached = archiveCacheLookup(h, path) orelse break :cache_check;
if (cached.is_negative and archive_or_group == 0) {
recordNegativeHit();
if (out_outer_archive != 0) @as(*align(1) u32, @ptrFromInt(out_outer_archive)).* = 0;
if (out_inner_archive != 0) @as(*align(1) u32, @ptrFromInt(out_inner_archive)).* = 0;
if (out_block_entry != 0) @as(*align(1) u32, @ptrFromInt(out_block_entry)).* = 0;
hook.call(fn (u32) callconv(hook.cc.stdcall) void, 0x64e850, .{2});
addHitCycles(rdtsc() - tsc_start);
return 0;
}
if (!cached.is_negative) {
if (archive_or_group == 0 and out_outer_archive != 0) {
const valid_outer = if (cached.outer_archive != 0)
hook.call(fn (u32, u32) callconv(hook.cc.fastcall) u32, 0x650780, .{ cached.outer_archive, 0 })
else
0;
if (valid_outer == 0 and cached.outer_archive != 0) break :cache_check;
@as(*align(1) u32, @ptrFromInt(out_outer_archive)).* = valid_outer;
if (out_inner_archive != 0 and cached.inner_archive != 0) {
const valid_inner = hook.call(fn (u32, u32) callconv(hook.cc.fastcall) u32, 0x650780, .{ cached.inner_archive, 0 });
@as(*align(1) u32, @ptrFromInt(out_inner_archive)).* = valid_inner;
} else if (out_inner_archive != 0) {
@as(*align(1) u32, @ptrFromInt(out_inner_archive)).* = 0;
}
if (out_block_entry != 0) {
@as(*align(1) u32, @ptrFromInt(out_block_entry)).* = computeBlockEntry(cached.inner_archive, cached.block_index);
}
recordCacheHit();
addHitCycles(rdtsc() - tsc_start);
return 1;
}
if (archive_or_group != 0 and (archive_or_group == cached.outer_archive or archive_or_group == cached.inner_archive)) {
if (out_outer_archive != 0 and cached.outer_archive != 0) {
const valid = hook.call(fn (u32, u32) callconv(hook.cc.fastcall) u32, 0x650780, .{ cached.outer_archive, 0 });
if (valid == 0) break :cache_check;
@as(*align(1) u32, @ptrFromInt(out_outer_archive)).* = valid;
} else if (out_outer_archive != 0) {
@as(*align(1) u32, @ptrFromInt(out_outer_archive)).* = 0;
}
if (out_inner_archive != 0 and cached.inner_archive != 0) {
const valid = hook.call(fn (u32, u32) callconv(hook.cc.fastcall) u32, 0x650780, .{ cached.inner_archive, 0 });
if (valid == 0) break :cache_check;
@as(*align(1) u32, @ptrFromInt(out_inner_archive)).* = valid;
} else if (out_inner_archive != 0) {
@as(*align(1) u32, @ptrFromInt(out_inner_archive)).* = 0;
}
if (out_block_entry != 0) {
@as(*align(1) u32, @ptrFromInt(out_block_entry)).* = computeBlockEntry(cached.inner_archive, cached.block_index);
}
recordCacheHit();
addHitCycles(rdtsc() - tsc_start);
return 1;
}
}
}
recordCacheMiss();
const ret = file_find_hook.callOriginal(.{ archive_or_group, filename_ptr, flags, out_inner_archive, out_outer_archive, out_block_entry, out_disk_path });
addMissCycles(rdtsc() - tsc_start);
if (archive_or_group != 0 and out_inner_archive != 0 and out_block_entry != 0) {
if (ret == 1) {
const inner = hook.readMem(u32, out_inner_archive);
const block = hook.readMem(u32, out_block_entry);
archiveCacheInsert(h, path, archive_or_group, inner, block, false);
}
}
if (archive_or_group == 0 and ret == 0) {
archiveCacheInsert(h, path, 0, 0, 0, true);
}
return ret;
}
pub fn install() bool {
return file_find_hook.attach(0x6549a0, &fileFindDetour) == .ok;
}
pub fn remove() void {
file_find_hook.detach();
}
// =============================================================================
// Stats
// =============================================================================
fn pct(part: u64, total: u64) u64 {
if (total == 0) return 0;
return part *| 1000 / total;
@@ -20,7 +20,7 @@ const inflate_hook = @import("inflate_hook.zig");
const timer_fix = @import("timer_fix.zig");
const filecache = @import("filecache.zig");
pub const module_name: [*:0]const u8 = "performance";
pub const module_name: [*:0]const u8 = "weirdperformance";
// Provide malloc/free for libdeflate's default allocator (linked without libc).
// Use game's Storm memory manager:
@@ -287,6 +287,9 @@ pub fn installHooks() void {
// Silicon SSE binary patches
const patched = installPatches();
// MPQ file cache
if (filecache.install()) installed += 1;
// libdeflate inflate replacement
if (inflate_hook.install(log)) installed += 1;
@@ -310,6 +313,7 @@ pub fn removeHooks() void {
if (g_is_hook_owner) {
inflate_hook.dumpStats();
filecache.dumpStats(&log);
filecache.remove();
inflate_hook.remove();
transform_hook.detach();
particle_hook.detach();