GetOrCreateCharacterGlyph (0x5ca2d0) is the #2 CPU hotspot at 3.65%.
The game's glyph cache uses only 4 hash buckets for ~95 ASCII chars,
causing ~24-entry chain walks with pointer chasing on every lookup.
Text measurement (99.8% of calls) re-walks these chains per character,
thousands of times per frame during UI updates.
Shadow cache: 4096-entry direct-mapped array with Murmur2 hash,
keyed on (FontObject*, charCode, param2). Cache hit returns the
cached float width via FPU ST(0) inline asm, skipping the chain
walk entirely. Gated behind ab_use_custom for A/B benchmarking.
- Eliminate hardcoded module_names in addons.zig — now derived from
build.zig via all_module_names build option
- Add module_active.zig runtime registry: main.zig registers isActive
pointers during install(), addons.zig queries them without importing
each module directly
- Prune embedded file prefixes at startup: after all modules claim
mutexes, build a runtime active_prefixes table excluding modules we
don't own. findEmbeddedFile searches only active entries — no
per-lookup isActive check on the hot path
- Tag both addon and asset prefixes with module_name so pruning applies
to all embedded files for a module
- Rename src/markers/ → src/worldmarkers/, src/outline/api.zig →
src/outline/outline.zig to follow {name}/{name}.zig convention
Registers GetAddOnMemoryUsage, UpdateAddOnMemoryUsage, GetAddOnCPUUsage,
UpdateAddOnCPUUsage, ResetAddOnCPUUsage, and GetScriptCPUUsage as global
Lua functions. All stubs for now — implementation will hook lua_Alloc
and FrameScript dispatch for per-addon tracking.
@hasField only works on struct fields, not module declarations.
build_options is a module, so @hasField always returned false,
silently skipping all addon file embedding and registration.
Track frame-to-frame wall time via rdtsc delta at executeSceneRenderPass.
Stats dump now shows each function's cycles as % of total frame time,
plus rough ms estimate at 3GHz. Helps identify which functions dominate.
Build options now provide addon_name, addon_hidden, and file lists per
module. addons.zig derives everything from these — no hardcoded module
list. Added addon_hidden flag (WorldMarkers uses it to stay unlisted).
Changed dpslog default to enabled.
Resize hook now expands event array to 801 slots. Dynamic slot search
scans from slot 650 for first empty entry (compatible with SuperWoW).
Fixed ProcessEnvironmentalDamage parameter order — Ghidra mislabeled
damageSource as damage. Lua chat output now fires only on first subevent.
Hook 23 packet handlers / internal functions to fire a single
COMBAT_LOG_EVENT with WotLK-style subevent strings and structured
args (spellId, amount, school, etc.) for addon consumption.
Event registration writes directly into the internal FrameScript
event table at slot 650, with a resize_lua_event_array hook that
expands capacity to 700 when needed (compatible with SuperWoW).
Includes Lua tracker addon (WeirdUtils_DPSLog) with popup checklist
UI, per-subevent chat output, and /dpslog slash command.
When cursor hits an object (hitType=2), re-call WorldIntersectionTest
with flags=0 to get terrain position behind it. Replaces the GUID-based
unit position lookup which failed for GOs (no movement struct).
Extract duplicated WoW 1.12.1 addresses and game accessor functions
into shared modules (src/offsets.zig, src/wow.zig), replacing 5+
copies of getObjectByGUID, isInBattleground, isValidPtr, etc.
- src/offsets.zig: shared address constants (object manager, descriptor
fields, map/zone, core function addresses, D3D9/GX)
- src/wow.zig: shared accessor functions (pointer validation, object
manager traversal, field reads, unit helpers, battleground detection,
game function wrappers, raid target cache)
- Update 10 modules to import from shared instead of inline constants
- Remove outline/wow.zig (promoted to src/wow.zig)
- Trim outline/offsets.zig and markers/offsets.zig to module-specific only
Replace console.zig with logging.zig: per-module Logger with auto
[name] prefix, optional file output, and destination routing. Convert
all modules from manual [name] prefixes and global con.print to Logger
instances. Remove redundant "Module loaded" lines. Replace
OutputDebugStringA in outline/tracker with Logger. Add dpslog module
with structured combat log events (SPELL_DMG, PERIODIC, HEAL, MELEE).
Add clickthrough module and bigcursor D3D9 cursor scaling.
Reads current map type from ObjMgr+0xCC -> Map.dbc row+0x08 and blocks
marker placement when mapType == 3 (battleground). Clearing markers
still works in BGs. Shows "World Markers unavailable in battlegrounds."
via the existing deny message system.
Player hit → re-raycast without players, accept interactable NPCs
(npc_flags != 0: vendors, quest givers, flight masters, etc.) or GOs.
Unit hit → re-raycast GO-only, accept interactable GOs.
Fix DESC_NPC_FLAGS: was 0x8D*4 (0x234), should be 0x93*4 (0x24C) —
field index includes OBJECT_END (0x06) base, matching minimapicons.
Hooks WorldIntersectionTest (0x480DF0) to detect when the raycast hits a
unit/player, then re-calls the original with GO-only flags (0x04). If an
interactable GO is behind the unit/player on the same ray, replaces the
hit result so the game targets the GO instead.
Uses CallSpellCastHandler (0x5F8800) for interactability filtering —
any usable GO (mailboxes, soulwells, portals, etc.) triggers click-through.
Replace all local calling convention declarations (const fc/tc/sc)
with hook.cc.fastcall/thiscall/stdcall from zhook. Migrate remaining
17 hook.fastcall() inline asm call sites to hook.call() with typed
function pointers.
Replace hand-written inline asm blocks with hook.call() typed function
pointer dispatch across 10 files. Also migrates 4 D3D9 COM vtable
NULL-dispatch blocks using ?*anyopaque optional pointers.
Net removal: ~350 lines of inline asm replaced by single-line calls.
Reads zone ID from 0xB4E314 and suppresses NPC/GO tracking in capital
cities (SW, IF, Darn, Org, TB, UC, Alah'Thalas) when enabled. Castle
icon converted from WC3-style PNG via png2blp.
Replace faction template comparison with race-based same-faction check
since faction template is dynamically overwritten in cross-faction groups.
Uses bitmask lookup for Alliance/Horde races (including High Elf/Goblin).
Replace GO flag checks with CallSpellCastHandler (0x5f8800) — the same
virtual dispatch the game's cursor system uses to determine if a GO is
interactable. This correctly handles enemy-summoned mailboxes and
brainwashers that have no distinguishing descriptor flags.
Filter hostile units, wrong-faction summoned NPCs, and non-interactable
game objects from minimap tracking. Dynamic checks (UnitReaction, owner
faction, GO_FLAG_NO_INTERACT) run every frame since group membership can
change. GUID cache still handles static classification (NPC flags/subname).
Also fix missing ECX clobber in outline UnitReaction inline asm.
Performance:
- GUID->blip result cache (256-entry direct-mapped) skips classification
after first match; only position lookup remains per-frame
- Active NPC flag bitmask rejects most objects before the 16-entry loop
- Split tracking by object type (unit vs GO) for early exit
- Per-frame minimap info cache (center/radius/scale read once per cycle)
- Skip subname/entry reads when no filters are active
- Batch draw calls by texture (sort blips, one GxRsSet per unique texture)
- Split drawMinimapTexture into getGxTex + drawMinimapBlip for batching
Assets:
- Add Trade.blp icon (INV_Potion_85 derived) for Trade Goods vendors
- Update Brainwasher.blp with stark contrast version, downsized to 32x32
- Add png2blp.py converter (PNG to BLP2 DXT3 with mipmaps)
All inline asm call blocks must clobber ECX and EDX since callees
destroy them. Without clobbers, ReleaseSmall reuses register values
the callee already overwrote, causing NULL dereference crashes.
Remove unused indoor/outdoor WMO filtering and gray blip tinting.
Cache active tracking state in a bool refreshed on config change
instead of scanning all entries per-object per-frame. Move
SetObjectTypeBlip into WeirdUtils table to keep it out of the
global namespace. Add minimap tracking section to DLL_README.
All embedded addons now register through LoadAddonTOC (no more
separate hidden loading path). For addons marked hidden, walk the
addon linked list after registration and set the +0x29 exclusion
byte so DeserializeAddonData skips them when building the flat
display array. Removes the LoadAddonsRecursively hook since the
game's native LoadAddonRecursive handles all addon loading.
Move embedded file table, addon registration hooks, and loading logic
from main.zig into addons.zig. Each module's is_active callback gates
addon loading on mutex ownership so individual DLLs only load addons
for modules they control. Addon hooks are skipped entirely at comptime
when no addon-bearing modules are compiled in.
Also consolidates CheckFileExistence hook — customassets no longer hooks
it directly, instead exporting looseFilesLookup for main.zig's hook.