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:
+8
-9
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user