From 4629ea71d6344b490ee66750e04c12dcee3045fa Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Tue, 24 Mar 2026 17:48:22 -0700 Subject: [PATCH] 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). --- DLL_README.md | 17 ++- build.zig | 2 +- src/main.zig | 123 +----------------- src/performance/filecache.zig | 113 ++++++++++++++++ .../{performance.zig => weirdperformance.zig} | 6 +- 5 files changed, 130 insertions(+), 131 deletions(-) rename src/performance/{performance.zig => weirdperformance.zig} (98%) diff --git a/DLL_README.md b/DLL_README.md index 000e48e..08efa1c 100644 --- a/DLL_README.md +++ b/DLL_README.md @@ -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. diff --git a/build.zig b/build.zig index 78b4a41..14c94fd 100644 --- a/build.zig +++ b/build.zig @@ -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 { diff --git a/src/main.zig b/src/main.zig index 1faefdb..e9a0f30 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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 { diff --git a/src/performance/filecache.zig b/src/performance/filecache.zig index ef39b88..1ca9950 100644 --- a/src/performance/filecache.zig +++ b/src/performance/filecache.zig @@ -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; diff --git a/src/performance/performance.zig b/src/performance/weirdperformance.zig similarity index 98% rename from src/performance/performance.zig rename to src/performance/weirdperformance.zig index e206f2d..f9116d1 100644 --- a/src/performance/performance.zig +++ b/src/performance/weirdperformance.zig @@ -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();