Files
WeirdUtils/docs/DATA_FLOW_MAP.md
T
MarcelineVQ 2cca8f4b53 Add assetfix, transmogfix, minimapicons modules; fix assetfix disk-vs-MPQ priority
Assetfix: hook CheckFileExistence to serve loose Data\ files. The original
flags|1 approach failed because game paths contain backslashes, causing
CheckFileExistence to skip BuildFilePath and check the raw path (no Data\
prefix). Fix: write the correct Data\-prefixed disk path to the output buffer
directly and return 1, bypassing the original function for hash map hits.
This preserves hook chaining (filename argument is never transformed).

Also adds transmogfix (transmog update coalescing), minimapicons (stub),
new build options for all three modules, mutex-based multi-DLL safety,
embed .skin data into .m2 models, and various module improvements.
2026-02-28 14:07:53 -08:00

14 KiB

Data Flow Map: Outline and Overlay Detection System

This document maps how the DLL detects and gathers information for rendering outlines on corpses, targets, and raid-marked units.

Overview

The system uses multiple data sources and hooks to identify which models should receive outline rendering:

  1. Object Manager Iteration - Scans all visible game objects each frame
  2. WoW Function Calls - Uses native game functions for GUID/object resolution
  3. Direct Memory Reads - Reads game structures at known offsets
  4. Model Render Hooks - Intercepts WoW's rendering pipeline to associate models with objects

High-Level Data Flow

flowchart TB
    subgraph FrameStart["Frame Start (RenderDeadOverlay)"]
        Reset["ModelOutline_ResetFrame()"]
        ClearTarget["ModelOutline_ClearTarget()"]
        ClearRaid["ModelOutline_ClearRaidMarked() (throttled 100ms)"]
        CacheRaid["wow_cache_raid_targets()"]
    end

    subgraph Detection["Object Detection Loop"]
        ObjFirst["wow_object_first()"]
        ObjNext["wow_object_next()"]
        ObjType["wow_object_type()"]

        subgraph PlayerCheck["Player Processing"]
            IsDead["wow_unit_is_dead()"]
            IsFriendly["wow_unit_is_friendly()"]
            GetGUID["wow_object_guid()"]
            GetModel["Read model ptr @ +0xD8, +0xDC"]
            GetRaidMark["wow_get_raid_target_index()"]
        end

        subgraph CorpseCheck["Corpse Processing"]
            IsSkeleton["wow_corpse_is_skeleton()"]
            OwnerGUID["wow_corpse_owner_guid()"]
            OwnerLookup["wow_get_object_by_guid()"]
            GroupCheck["wow_is_guid_in_group()"]
        end

        subgraph TargetCheck["Target Processing"]
            GetTarget["wow_get_target()"]
            TargetType["wow_object_type()"]
            IsEnemy["!wow_unit_is_friendly()"]
            IsEnemyPet["wow_unit_is_enemy_pet()"]
        end

        subgraph UnitCheck["NPC/Unit Processing"]
            UnitGUID["wow_object_guid()"]
            UnitRaidMark["wow_get_raid_target_index()"]
        end
    end

    subgraph Registration["Model Registration"]
        AddDead["ModelOutline_AddDeadPlayer(GUID)"]
        AddDeadModel["ModelOutline_AddDeadPlayerModel(ptr)"]
        AddRaidMark["ModelOutline_AddRaidMarkedModel(ptr, index)"]
        SetTarget["ModelOutline_SetTargetModel(ptr)"]
        SetOwner["ModelOutline_SetModelOwner(ptr, obj)"]
    end

    subgraph RenderHooks["Render Hook Pipeline"]
        ManageRender["CM2Model_ManageRenderListNode Hook"]
        DrawBatchProj["DrawBatchProj Hook"]
        DIPHook["DrawIndexedPrimitive Hook"]
    end

    subgraph Storage["Tracking Storage"]
        GUIDSet["g_deadPlayerGUIDs (unordered_set)"]
        ModelSet["g_deadPlayerModels (unordered_set)"]
        RaidMap["g_raidMarkedModels (unordered_map)"]
        OwnerMap["g_modelToOwner (unordered_map)"]
        TargetVar["g_targetModel, g_targetModelAlt"]
        FrameSet["g_frameDeadModels (per-frame)"]
    end

    FrameStart --> Detection
    Detection --> Registration
    Registration --> Storage
    Storage --> RenderHooks

Memory Address Sources

flowchart LR
    subgraph GlobalPointers["Global Pointers (wow_1121.h)"]
        ObjMgr["0x00B41414 - Object Manager Ptr"]
        WorldFrame["0x00B4B2BC - World Frame Ptr"]
        RaidArray["0x00B71368 - Raid Target Array (8 GUIDs)"]
        RaidTex["0x00CE878C - Raid Target Texture Ptr"]
    end

    subgraph Functions["Native WoW Functions"]
        WorldToScreen["0x00483EE0 - WorldToScreen"]
        DDCtoNDC["0x0041ADE0 - DDC_to_NDC"]
        UnitGUIDFunc["0x00515970 - UnitGUID('player','target')"]
        GetObjByGUID["0x00464870 - GetObjectByGUID"]
        UnitReaction["0x006061E0 - UnitReaction"]
        GetUnitName["0x00609210 - CGUnit::GetUnitName"]
        GetObjName["0x006264E0 - GetObjectName"]
    end

    subgraph HookTargets["Hook Target Addresses"]
        RenderDraw["0x0070B360 - CM2SceneRenderDraw"]
        ManageList["0x00710B90 - CM2Model_ManageRenderListNode"]
        DrawBatch["0x0070CF70 - DrawBatch"]
        DrawBatchP["0x0070CB30 - DrawBatchProj"]
        NameplateVB["0x006C724A - Nameplate VB Hook"]
    end

    subgraph ObjectOffsets["Object Memory Offsets"]
        ObjType2["Object+0x14 - Type"]
        ObjGUID["Object+0x30 - GUID (8 bytes)"]
        UnitDesc["Unit+0x110 - Descriptor Ptr"]
        UnitMove["Unit+0x118 - Movement Ptr"]
        ModelD8["Unit+0xD8 - Model Ptr (primary)"]
        ModelDC["Unit+0xDC - Model Ptr (secondary)"]
        Scale1["Unit+0x90 - Scale Factor 1"]
        Scale2["Unit+0xBC - Scale Factor 2"]
    end

    subgraph DescriptorOffsets["Descriptor Field Offsets"]
        HP["Descriptor+0x40 - Current HP"]
        MaxHP["Descriptor+0x48 - Max HP"]
        Flags["Descriptor+0x224 - Unit Flags"]
        Summoned["Descriptor+0x18 - UNIT_FIELD_SUMMONEDBY"]
        DuelArb["Descriptor+0x2D8 - PLAYER_DUEL_ARBITER"]
    end

    subgraph CorpseOffsets["Corpse Field Offsets"]
        CorpseDesc["Corpse+0x8 - Descriptor Ptr"]
        CorpseOwner["Desc+0x18 - Owner GUID"]
        CorpsePosX["Desc+0x24 - Position X"]
        CorpsePosY["Desc+0x28 - Position Y"]
        CorpsePosZ["Desc+0x2C - Position Z"]
        CorpseFlags["Desc+0x8C - Corpse Flags"]
    end

Detection Flow by Category

Dead Player Detection

flowchart TB
    Start["Object Iteration"] --> CheckType{"Object Type?"}

    CheckType -->|"Type 4 (Player)"| CheckDead{"wow_unit_is_dead()"}
    CheckType -->|"Type 7 (Corpse)"| ProcessCorpse
    CheckType -->|Other| NextObj

    CheckDead -->|Dead| CheckFriendly{"wow_unit_is_friendly()"}
    CheckDead -->|Alive| RemoveTracking["Remove from tracking<br/>(handles resurrection)"]

    CheckFriendly -->|Friendly| ReadPlayerData["Read GUID @ +0x30<br/>Read Model @ +0xD8, +0xDC"]
    CheckFriendly -->|Enemy| NextObj["Next Object"]

    ReadPlayerData --> RegisterDead["ModelOutline_AddDeadPlayer(GUID)<br/>ModelOutline_AddDeadPlayerModel(ptr)<br/>ModelOutline_SetModelOwner(ptr, obj)"]

    subgraph CorpseFlow["Corpse Processing"]
        ProcessCorpse --> CheckSkeleton{"wow_corpse_is_skeleton()?"}
        CheckSkeleton -->|Yes| NextObj
        CheckSkeleton -->|No| GetOwner["wow_corpse_owner_guid()"]

        GetOwner --> FindOwner{"wow_get_object_by_guid()"}
        FindOwner -->|Found| CheckOwnerFriendly{"wow_unit_is_friendly(owner)"}
        FindOwner -->|Not Found| CheckGroup{"wow_is_guid_in_group(guid)"}

        CheckOwnerFriendly -->|Friendly| ReadCorpseModel["Read Model @ +0xD8, +0xDC"]
        CheckOwnerFriendly -->|Enemy| NextObj

        CheckGroup -->|In Group| ReadCorpseModel
        CheckGroup -->|Not In Group| NextObj

        ReadCorpseModel --> RegisterCorpse["ModelOutline_AddDeadPlayer(ownerGUID)<br/>ModelOutline_AddDeadPlayerModel(ptr)"]
    end

    RegisterDead --> NextObj
    RegisterCorpse --> NextObj
    RemoveTracking --> NextObj

Target Detection

flowchart TB
    Start["Frame Start"] --> GetTarget["wow_get_target()"]

    GetTarget --> CheckValid{"targetObj != 0 &&<br/>targetObj != localPlayer?"}
    CheckValid -->|No| ClearTarget["ModelOutline_ClearTarget()"]
    CheckValid -->|Yes| GetType["wow_object_type(target)"]

    GetType --> CheckUnit{"Type == Unit (3)?"}
    CheckUnit -->|No| ClearTarget
    CheckUnit -->|Yes| CheckHostile{"!wow_unit_is_friendly()?"}

    CheckHostile -->|Friendly| ClearTarget
    CheckHostile -->|Hostile| CheckPet{"!wow_unit_is_enemy_pet()?"}

    CheckPet -->|Enemy Pet| ClearTarget
    CheckPet -->|Not Pet| CheckAlive{"!wow_unit_is_dead()?"}

    CheckAlive -->|Dead| ClearTarget
    CheckAlive -->|Alive| ReadModel["Read Model @ +0xD8, +0xDC"]

    ReadModel --> RegisterTarget["ModelOutline_SetTargetModel(ptrD8)<br/>ModelOutline_SetTargetModelAlt(ptrDC)<br/>ModelOutline_SetModelOwner(ptr, obj)"]

Raid Mark Detection

flowchart TB
    Start["Frame Start"] --> CheckThrottle{"Time since last check<br/>>= 100ms?"}

    CheckThrottle -->|No| SkipScan["Use cached data"]
    CheckThrottle -->|Yes| CacheTargets["wow_cache_raid_targets()<br/>(copies 8 GUIDs from 0x00B71368)"]

    CacheTargets --> ClearOld["ModelOutline_ClearRaidMarked()<br/>ModelOutline_ClearModelOwners()"]

    ClearOld --> ObjectLoop["Object Iteration Loop"]

    ObjectLoop --> CheckType{"Object Type?"}

    CheckType -->|"Type 4 (Player)"| ProcessPlayer
    CheckType -->|"Type 3 (Unit/NPC)"| ProcessNPC
    CheckType -->|Other| NextObj["Next Object"]

    subgraph PlayerMarks["Player Raid Mark Check"]
        ProcessPlayer --> CheckPlayerAlive{"Alive?"}
        CheckPlayerAlive -->|No| NextObj
        CheckPlayerAlive -->|Yes| CheckNotSelf{"Not LocalPlayer?"}
        CheckNotSelf -->|Self| NextObj
        CheckNotSelf -->|Other| GetPlayerGUID["wow_object_guid()"]
        GetPlayerGUID --> CheckPlayerMark{"wow_get_raid_target_index(guid)"}
        CheckPlayerMark -->|No Mark| NextObj
        CheckPlayerMark -->|Has Mark 1-8| ReadPlayerModel["Read Model @ +0xD8, +0xDC"]
        ReadPlayerModel --> RegisterPlayerMark["ModelOutline_AddRaidMarkedModel(ptr, index)"]
    end

    subgraph NPCMarks["NPC Raid Mark Check"]
        ProcessNPC --> CheckNPCAlive{"Alive?"}
        CheckNPCAlive -->|No| NextObj
        CheckNPCAlive -->|Yes| CheckNotPet{"!wow_unit_is_enemy_pet()?"}
        CheckNotPet -->|Enemy Pet| NextObj
        CheckNotPet -->|OK| GetNPCGUID["wow_object_guid()"]
        GetNPCGUID --> CheckNPCMark{"wow_get_raid_target_index(guid)"}
        CheckNPCMark -->|No Mark| NextObj
        CheckNPCMark -->|Has Mark 1-8| ReadNPCModel["Read Model @ +0xD8, +0xDC"]
        ReadNPCModel --> RegisterNPCMark["ModelOutline_AddRaidMarkedModel(ptr, index)"]
    end

    RegisterPlayerMark --> NextObj
    RegisterNPCMark --> NextObj

Model-to-Object Linking

flowchart TB
    subgraph Sources["Model Pointer Sources"]
        UnitD8["Unit Object + 0xD8<br/>(Primary Model)"]
        UnitDC["Unit Object + 0xDC<br/>(Secondary Model)"]
        CorpseD8["Corpse Object + 0xD8"]
        CorpseDC["Corpse Object + 0xDC"]
    end

    subgraph ModelOwnerLookup["Model -> Owner Lookup (in hooks)"]
        Callback["Model + 0x3C0<br/>(SetRenderCallbacks owner)"]
        Direct["Model + 0x28<br/>(InitializeModelWithParameters owner)"]
    end

    subgraph Storage2["Storage"]
        OwnerMap2["g_modelToOwner<br/>unordered_map<model_ptr, object_ptr>"]
    end

    subgraph Usage["Usage in Hooks"]
        ManageHook["ManageRenderListNode Hook"]
        BatchHook["DrawBatchProj Hook"]
        GetCategory["GetModelCategory()"]
    end

    Sources -->|"SetModelOwner()"| Storage2
    ModelOwnerLookup -->|"Fallback lookup"| Usage
    Storage2 -->|"GetModelOwner()"| Usage

Hook Pipeline

flowchart TB
    subgraph WoWRender["WoW Render Pipeline"]
        AddModel["Model added to render list"]
        RenderScene["CM2SceneRenderDraw"]
        DrawBatchP["DrawBatchProj (batch type 0)"]
        DIP["IDirect3DDevice9::DrawIndexedPrimitive"]
    end

    subgraph OurHooks["Our Hooks"]
        ManageHook["ManageRenderListNode Hook<br/>@ 0x00710B90"]
        RenderHook["CM2SceneRenderDraw Hook<br/>@ 0x0070B360"]
        BatchProjHook["DrawBatchProj Hook<br/>@ 0x0070CB30"]
        DIPHook["D3D9 DIP Hook"]
    end

    subgraph Actions["Hook Actions"]
        CheckMatch["Check if model is tracked<br/>(dead/raid/target)"]
        AddToFrame["Add to g_frameDeadModels"]
        SetFlag["Set g_bRenderingCorpse = true<br/>Set g_currentRenderingModel"]
        ApplyStencil["Apply stencil outline effect"]
    end

    AddModel --> ManageHook
    ManageHook --> CheckMatch
    CheckMatch -->|Match| AddToFrame

    RenderScene --> RenderHook
    RenderHook --> BatchProjHook

    BatchProjHook --> SetFlag
    SetFlag --> DIP
    DIP --> DIPHook
    DIPHook --> ApplyStencil

Group Membership Detection

flowchart TB
    subgraph Cache["Group Cache (refreshed every 5s)"]
        Party["UnitGUID('party1'..'party4')"]
        Raid["UnitGUID('raid1'..'raid40')"]
        Storage3["g_groupGUIDs[40]<br/>g_groupMemberCount"]
    end

    subgraph Lookup["wow_is_guid_in_group(guid)"]
        CheckStale{"Cache > 5s old?"}
        Refresh["wow_refresh_group_cache()"]
        Search["Linear search g_groupGUIDs"]
    end

    CheckStale -->|Yes| Refresh
    Refresh --> Search
    CheckStale -->|No| Search

    Party --> Storage3
    Raid --> Storage3
    Storage3 --> Search

Summary: Memory Reads Per Frame

Operation Memory Source Frequency
Object Iteration Object Manager @ 0x00B41414 Every frame, O(n) objects
Object Type Object + 0x14 Per object
Object GUID Object + 0x30 Per player/unit/corpse
Unit Dead Check Descriptor + 0x40 (HP), + 0x224 (flags) Per unit
Unit Friendly Check UnitReaction function call Per relevant unit
Model Pointer Object + 0xD8, + 0xDC Per tracked object
Raid Targets 0x00B71368 (8 GUIDs) Every 100ms
Current Target UnitGUID("target") function Every frame
Group Members UnitGUID("party#", "raid#") Every 5 seconds
Corpse Owner Descriptor + 0x18 Per corpse
Corpse Skeleton Flag Descriptor + 0x8C Per corpse

Potential Consolidation Opportunities

  1. Batch GUID Resolution: Currently wow_get_raid_target_index() is called per-object. Could cache GUID->mark_index map once per 100ms.

  2. Unified Object Cache: Could build a single per-frame cache of {object_ptr, guid, type, model_ptrs, is_dead, is_friendly} to avoid repeated reads.

  3. Reduce Hook Count: Consider if ManageRenderListNode + DrawBatchProj could be consolidated into a single hook point.

  4. Group Cache Integration: Group membership check could be integrated with the raid target cache refresh cycle.

  5. Model Owner Association: Currently done both at detection time AND in hooks via fallback. Could be consolidated to detection-only.