dpslog: CLEU rename, DPSMate absorb pipeline, WSBT adapter, module version API
- Rename COMBAT_LOG_EVENT -> COMBAT_LOG_EVENT_UNFILTERED across Zig/Lua - DPSMate CLEU adapter: absorb pipeline (SetUnregisterVariables, Absorb, ConfirmAbsorbApplication, UnregisterAbsorb), BuildFail type 2/3, "(Periodic)" suffix, removed dead handler - WSBT (Weird Scrolling Battle Text): CLEU adapter fork of MSBT with dispatch table, A/B combat profiling, selective event unregistration - Module version API: GetWeirdUtilsVersion(name?) Lua function + WeirdUtils global table, additive across independent DLLs. Build system passes per-module version strings via build_options. - Hook Glue_LoadScriptFunctions (0x46ABB0) for login screen availability - Rename lsf_hook -> register_commands_hook (real name: Player_LoadScriptFunctions) - lua.zig: add setglobal/getglobal via LUA_GLOBALSINDEX (-10001)
This commit is contained in:
+32
-1
@@ -165,7 +165,7 @@ No other configuration needed, install and forget.
|
||||
|
||||
### DPSLog (Combat Log Events)
|
||||
|
||||
Provides WotLK 3.3.5-style `COMBAT_LOG_EVENT` for the vanilla client. Fires a single unified event with structured arguments instead of vanilla's fragmented localized text events. Enables modern DPS meter addons without expensive string parsing.
|
||||
Provides WotLK 3.3.5-style `COMBAT_LOG_EVENT_UNFILTERED` for the vanilla client. Fires a single unified event with structured arguments instead of vanilla's fragmented localized text events. Enables modern DPS meter addons without expensive string parsing.
|
||||
|
||||
37 subevents covering all combat interactions: damage (spell, melee, periodic, environmental, damage shield, damage split), healing (direct, periodic, overheal tracking), misses (all types), auras (applied, removed, refreshed, broken, dose changes), casts (start, success, failed, interrupted), power (energize, drain, leech), dispels, extra attacks, deaths, and kills.
|
||||
|
||||
@@ -297,6 +297,37 @@ if (hMod) {
|
||||
}
|
||||
```
|
||||
|
||||
### Version Query API
|
||||
|
||||
WeirdUtils registers a Lua global table and query function for addon developers to detect which modules are loaded and their versions. Available from the login screen onward.
|
||||
|
||||
#### `GetWeirdUtilsVersion()`
|
||||
|
||||
Returns the `WeirdUtils` table containing all enabled modules and their version strings:
|
||||
|
||||
```lua
|
||||
local modules = GetWeirdUtilsVersion()
|
||||
for name, version in pairs(modules) do
|
||||
print(name .. " v" .. version) -- e.g. "dpslog v1.0"
|
||||
end
|
||||
```
|
||||
|
||||
#### `GetWeirdUtilsVersion("modulename")`
|
||||
|
||||
Returns the version string for a specific module, or `nil` if not loaded:
|
||||
|
||||
```lua
|
||||
if GetWeirdUtilsVersion("dpslog") then
|
||||
-- DPSLog is available, register for COMBAT_LOG_EVENT_UNFILTERED
|
||||
end
|
||||
|
||||
local ver = GetWeirdUtilsVersion("minimapicons") -- "1.0" or nil
|
||||
```
|
||||
|
||||
The `WeirdUtils` table is additive -- if multiple independent DLLs are loaded (e.g. `dpslog.dll` and `minimapicons.dll` separately), each adds its own modules to the shared table.
|
||||
|
||||
---
|
||||
|
||||
### Module Mutexes
|
||||
|
||||
Each module also holds a named mutex while active: `Local\WeirdUtils_<name>_<PID>` (e.g. `Local\WeirdUtils_framecrash_12345`). The exception is transmogfix, which uses `Local\TransmogCoalesceHook_<PID>` for legacy reasons.
|
||||
|
||||
@@ -3,6 +3,7 @@ const std = @import("std");
|
||||
const ModuleDesc = struct {
|
||||
name: []const u8,
|
||||
desc: []const u8,
|
||||
version: []const u8 = "1.0",
|
||||
default: bool = true,
|
||||
/// Source directory under src/ (defaults to name if null).
|
||||
src_dir: ?[]const u8 = null,
|
||||
@@ -255,7 +256,7 @@ fn addModuleOptions(b: *std.Build, opts: *std.Build.Step.Options) void {
|
||||
inline for (module_list) |mod| {
|
||||
opts.addOption(bool, "enable_" ++ mod.name, b.option(bool, mod.name, mod.desc) orelse mod.default);
|
||||
}
|
||||
// Pass full module name list so addons.zig doesn't need a hardcoded copy
|
||||
// Pass full module name/version lists so main.zig/addons.zig can use them at comptime
|
||||
const names: []const []const u8 = comptime blk: {
|
||||
var n: [module_list.len][]const u8 = undefined;
|
||||
for (module_list, 0..) |mod, i| n[i] = mod.name;
|
||||
@@ -263,6 +264,13 @@ fn addModuleOptions(b: *std.Build, opts: *std.Build.Step.Options) void {
|
||||
break :blk &final;
|
||||
};
|
||||
opts.addOption([]const []const u8, "all_module_names", names);
|
||||
const versions: []const []const u8 = comptime blk: {
|
||||
var v: [module_list.len][]const u8 = undefined;
|
||||
for (module_list, 0..) |mod, i| v[i] = mod.version;
|
||||
const final = v;
|
||||
break :blk &final;
|
||||
};
|
||||
opts.addOption([]const []const u8, "all_module_versions", versions);
|
||||
addFileListOptions(b, opts);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
local L = AceLibrary("AceLocale-2.2"):new("WSBT")
|
||||
|
||||
L:RegisterTranslations("deDE", function()
|
||||
return {
|
||||
["Debug mode has been enabled."] = "Debug mode has been enabled.",
|
||||
["Debug mode has been disabled."] = "Debug mode has been disabled.",
|
||||
["Event search mode has been enabled. Searching for: "] = "Event search mode has been enabled. Searching for: ",
|
||||
["Event search mode has been disabled."] = "Event search mode has been disabled.",
|
||||
["The mod is now disabled."] = "The mod is now disabled.",
|
||||
["The mod is now enabled."] = "The mod is now enabled.",
|
||||
["Hits"] = "Treffer",
|
||||
["Crit"] = "Krit",
|
||||
["Crits"] = "Krits",
|
||||
["Multiple"] = "Mehrere",
|
||||
[" (%d vulnerability)"] = " (%d vulnerability)",
|
||||
[" <\124cff00b37e\124h%d\124h\124r>"] = " <\124cff00b37e\124h%d\124h\124r>",
|
||||
["Profile Reset"] = "Profile Reset",
|
||||
["Drowning"] = "Drowning",
|
||||
["Falling"] = "Falling",
|
||||
["Fatigue"] = "Fatigue",
|
||||
["Fire"] = "Fire",
|
||||
["Lava"] = "Lava",
|
||||
["Slime"] = "Slime",
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,25 @@
|
||||
local L = AceLibrary("AceLocale-2.2"):new("WSBT")
|
||||
|
||||
L:RegisterTranslations("enUS", function()
|
||||
return {
|
||||
["Debug mode has been enabled."] = true,
|
||||
["Debug mode has been disabled."] = true,
|
||||
["Event search mode has been enabled. Searching for: "] = true,
|
||||
["Event search mode has been disabled."] = true,
|
||||
["The mod is now disabled."] = true,
|
||||
["The mod is now enabled."] = true,
|
||||
["Hits"] = true,
|
||||
["Crit"] = true,
|
||||
["Crits"] = true,
|
||||
["Multiple"] = true,
|
||||
[" (%d vulnerability)"] = true,
|
||||
[" <\124cff00b37e\124h%d\124h\124r>"] = true,
|
||||
["Profile Reset"] = true,
|
||||
["Drowning"] = true,
|
||||
["Falling"] = true,
|
||||
["Fatigue"] = true,
|
||||
["Fire"] = true,
|
||||
["Lava"] = true,
|
||||
["Slime"] = true,
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,25 @@
|
||||
local L = AceLibrary("AceLocale-2.2"):new("WSBT")
|
||||
|
||||
L:RegisterTranslations("frFR", function()
|
||||
return {
|
||||
["Debug mode has been enabled."] = "Debug mode has been enabled.",
|
||||
["Debug mode has been disabled."] = "Debug mode has been disabled.",
|
||||
["Event search mode has been enabled. Searching for: "] = "Event search mode has been enabled. Searching for: ",
|
||||
["Event search mode has been disabled."] = "Event search mode has been disabled.",
|
||||
["The mod is now disabled."] = "The mod is now disabled.",
|
||||
["The mod is now enabled."] = "The mod is now enabled.",
|
||||
["Hits"] = "Coups",
|
||||
["Crit"] = "Crit",
|
||||
["Crits"] = "Crits",
|
||||
["Multiple"] = "Multiple",
|
||||
[" (%d vulnerability)"] = " (%d vulnérabilité)",
|
||||
[" <\124cff00b37e\124h%d\124h\124r>"] = " <\124cff00b37e\124h%d\124h\124r>",
|
||||
["Profile Reset"] = "Profile Reset",
|
||||
["Drowning"] = "Noyade",
|
||||
["Falling"] = "Chute",
|
||||
["Fatigue"] = "Fatigue",
|
||||
["Fire"] = "Feu",
|
||||
["Lava"] = "Lave",
|
||||
["Slime"] = "Gelée",
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,22 @@
|
||||
## Interface: 11200
|
||||
## Version: 4.43-WSBT
|
||||
## Title: Weird Scrolling Battle Text
|
||||
## Notes: Scrolls battle information around the character model. CLEU-powered fork of MSBT.
|
||||
## OptionalDeps: SW_FixLogStrings
|
||||
## SavedVariables: MikSBT_Save
|
||||
|
||||
Libs\AceLibrary\AceLibrary.lua
|
||||
Libs\AceLocale-2.2\AceLocale-2.2.lua
|
||||
Libs\BabbleSpell-2.2\Babble-Spell-2.2.lua
|
||||
|
||||
MikScrollingBattleText-enUS.lua
|
||||
MikScrollingBattleText-frFR.lua
|
||||
MikScrollingBattleText-deDE.lua
|
||||
|
||||
localization.lua
|
||||
MikTableRecyclerObject.lua
|
||||
MikCombatEventHelper.lua
|
||||
MikScrollingBattleText.lua
|
||||
MikCombatEventHelper.xml
|
||||
MikScrollingBattleText.xml
|
||||
WSBT_CLEUAdapter.lua
|
||||
@@ -0,0 +1,578 @@
|
||||
-- WSBT CLEU Adapter
|
||||
-- Replaces MikCEH's CHAT_MSG_* string parsing with structured COMBAT_LOG_EVENT_UNFILTERED.
|
||||
-- Toggle: /wsbtcleu (switches between CLEU adapter and original string parser)
|
||||
-- Requires: DPSLog module (provides COMBAT_LOG_EVENT_UNFILTERED)
|
||||
|
||||
-- Wait for MikCEH to be initialized
|
||||
if not MikCEH or not MikCEH.SendEvent then return end
|
||||
|
||||
local CEH = MikCEH
|
||||
local SendEvent = MikCEH.SendEvent
|
||||
local GetDamageData = MikCEH.GetDamageEventData
|
||||
local GetHealData = MikCEH.GetHealEventData
|
||||
local GetNotifData = MikCEH.GetNotificationEventData
|
||||
|
||||
-- ============================================================================
|
||||
-- Constants (local refs for speed)
|
||||
-- ============================================================================
|
||||
|
||||
local INCOMING = CEH.DIRECTIONTYPE_PLAYER_INCOMING
|
||||
local OUTGOING = CEH.DIRECTIONTYPE_PLAYER_OUTGOING
|
||||
local PET_OUT = CEH.DIRECTIONTYPE_PET_OUTGOING
|
||||
local PET_IN = CEH.DIRECTIONTYPE_PET_INCOMING
|
||||
|
||||
local HIT = CEH.ACTIONTYPE_HIT
|
||||
local MISS = CEH.ACTIONTYPE_MISS
|
||||
local DODGE = CEH.ACTIONTYPE_DODGE
|
||||
local PARRY = CEH.ACTIONTYPE_PARRY
|
||||
local BLOCK = CEH.ACTIONTYPE_BLOCK
|
||||
local RESIST = CEH.ACTIONTYPE_RESIST
|
||||
local ABSORB = CEH.ACTIONTYPE_ABSORB
|
||||
local IMMUNE = CEH.ACTIONTYPE_IMMUNE
|
||||
local EVADE = CEH.ACTIONTYPE_EVADE
|
||||
local REFLECT = CEH.ACTIONTYPE_REFLECT
|
||||
|
||||
local HIT_NORMAL = CEH.HITTYPE_NORMAL
|
||||
local HIT_CRIT = CEH.HITTYPE_CRIT
|
||||
local HIT_DOT = CEH.HITTYPE_OVER_TIME
|
||||
|
||||
local HEAL_NORMAL = CEH.HEALTYPE_NORMAL
|
||||
local HEAL_CRIT = CEH.HEALTYPE_CRIT
|
||||
local HEAL_HOT = CEH.HEALTYPE_OVER_TIME
|
||||
|
||||
local DMG_PHYSICAL = CEH.DAMAGETYPE_PHYSICAL
|
||||
local DMG_UNKNOWN = CEH.DAMAGETYPE_UNKNOWN
|
||||
|
||||
local PARTIAL_ABSORB = CEH.PARTIALACTIONTYPE_ABSORB
|
||||
local PARTIAL_BLOCK = CEH.PARTIALACTIONTYPE_BLOCK
|
||||
local PARTIAL_RESIST = CEH.PARTIALACTIONTYPE_RESIST
|
||||
local PARTIAL_CRUSHING = CEH.PARTIALACTIONTYPE_CRUSHING
|
||||
local PARTIAL_GLANCING = CEH.PARTIALACTIONTYPE_GLANCING
|
||||
local PARTIAL_OVERHEAL = CEH.PARTIALACTIONTYPE_OVERHEAL
|
||||
|
||||
local NOTIF_DEBUFF = CEH.NOTIFICATIONTYPE_DEBUFF
|
||||
local NOTIF_BUFF = CEH.NOTIFICATIONTYPE_BUFF
|
||||
local NOTIF_BUFF_FADE = CEH.NOTIFICATIONTYPE_BUFF_FADE
|
||||
local NOTIF_POWER_GAIN = CEH.NOTIFICATIONTYPE_POWER_GAIN
|
||||
local NOTIF_POWER_LOSS = CEH.NOTIFICATIONTYPE_POWER_LOSS
|
||||
|
||||
-- Miss type string → MikCEH action type
|
||||
local missActionMap = {
|
||||
MISS = MISS,
|
||||
DODGE = DODGE,
|
||||
PARRY = PARRY,
|
||||
BLOCK = BLOCK,
|
||||
RESIST = RESIST,
|
||||
ABSORB = ABSORB,
|
||||
IMMUNE = IMMUNE,
|
||||
EVADE = EVADE,
|
||||
REFLECT = REFLECT,
|
||||
DEFLECT = MISS, -- no DEFLECT in MSBT, treat as miss
|
||||
}
|
||||
|
||||
-- WoW spell school bitmask → MSBT damage type
|
||||
-- 1=Physical, 2=Holy, 4=Fire, 8=Nature, 16=Frost, 32=Shadow, 64=Arcane
|
||||
local schoolMap = {
|
||||
[1] = 1, -- Physical
|
||||
[2] = 2, -- Holy
|
||||
[4] = 4, -- Fire
|
||||
[8] = 3, -- Nature
|
||||
[16] = 5, -- Frost
|
||||
[32] = 6, -- Shadow
|
||||
[64] = 7, -- Arcane
|
||||
}
|
||||
|
||||
local function schoolToDamageType(school)
|
||||
if not school or school == 0 then return DMG_PHYSICAL end
|
||||
return schoolMap[school] or DMG_UNKNOWN
|
||||
end
|
||||
|
||||
-- Power type ID → localized string
|
||||
local powerTypeNames = {
|
||||
[0] = MANA or "Mana",
|
||||
[1] = RAGE or "Rage",
|
||||
[2] = "Focus",
|
||||
[3] = ENERGY or "Energy",
|
||||
}
|
||||
|
||||
-- ============================================================================
|
||||
-- Player/pet identity
|
||||
-- ============================================================================
|
||||
|
||||
local playerName = UnitName("player")
|
||||
local petName = UnitName("pet")
|
||||
|
||||
local identityFrame = CreateFrame("Frame")
|
||||
identityFrame:RegisterEvent("UNIT_NAME_UPDATE")
|
||||
identityFrame:RegisterEvent("PLAYER_PET_CHANGED")
|
||||
identityFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
identityFrame:SetScript("OnEvent", function()
|
||||
playerName = UnitName("player")
|
||||
petName = UnitName("pet")
|
||||
end)
|
||||
|
||||
local function getDirection(srcName, dstName)
|
||||
-- Returns direction, counterpartName
|
||||
if srcName == playerName then
|
||||
return OUTGOING, dstName
|
||||
elseif dstName == playerName then
|
||||
return INCOMING, srcName
|
||||
elseif petName and srcName == petName then
|
||||
return PET_OUT, dstName
|
||||
elseif petName and dstName == petName then
|
||||
return PET_IN, srcName
|
||||
end
|
||||
return nil, nil -- not relevant to player/pet
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Toggle state
|
||||
-- ============================================================================
|
||||
|
||||
local cleuActive = false
|
||||
local cleuFrame = CreateFrame("Frame")
|
||||
|
||||
local function enableCLEU()
|
||||
-- Only unregister CHAT_MSG_* events; keep core events (PLAYER_REGEN_*, UNIT_HEALTH, etc.)
|
||||
MikCEH.UnregisterCombatParseEvents()
|
||||
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||
cleuActive = true
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00WSBT CLEU|r: |cff00ff00ON|r (structured events)")
|
||||
end
|
||||
|
||||
local function enableOriginal()
|
||||
cleuFrame:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||
MikCEH.RegisterCombatParseEvents()
|
||||
cleuActive = false
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00WSBT CLEU|r: |cffff4444OFF|r (string parser)")
|
||||
end
|
||||
|
||||
local function toggle()
|
||||
if cleuActive then enableOriginal() else enableCLEU() end
|
||||
end
|
||||
|
||||
SLASH_WSBTCLEU1 = "/wsbtcleu"
|
||||
SlashCmdList["WSBTCLEU"] = function(msg)
|
||||
if msg == "on" then
|
||||
if not cleuActive then enableCLEU() end
|
||||
elseif msg == "off" then
|
||||
if cleuActive then enableOriginal() end
|
||||
elseif msg == "status" then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00WSBT CLEU|r: " .. (cleuActive and "|cff00ff00ON|r" or "|cffff4444OFF|r"))
|
||||
else
|
||||
toggle()
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Partial action helpers
|
||||
-- ============================================================================
|
||||
|
||||
local function applyPartials(eventData, resisted, blocked, absorbed, glancing, crushing)
|
||||
-- Only one partial action per event in MSBT's model
|
||||
if crushing and crushing == 1 then
|
||||
eventData.PartialActionType = PARTIAL_CRUSHING
|
||||
elseif glancing and glancing == 1 then
|
||||
eventData.PartialActionType = PARTIAL_GLANCING
|
||||
elseif absorbed and absorbed > 0 then
|
||||
eventData.PartialActionType = PARTIAL_ABSORB
|
||||
eventData.PartialAmount = absorbed
|
||||
elseif blocked and blocked > 0 then
|
||||
eventData.PartialActionType = PARTIAL_BLOCK
|
||||
eventData.PartialAmount = blocked
|
||||
elseif resisted and resisted > 0 then
|
||||
eventData.PartialActionType = PARTIAL_RESIST
|
||||
eventData.PartialAmount = resisted
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- CLEU handler
|
||||
-- ============================================================================
|
||||
|
||||
local envActionMap = {
|
||||
DROWNING = CEH.ACTIONTYPE_DROWNING,
|
||||
FALLING = CEH.ACTIONTYPE_FALLING,
|
||||
FATIGUE = CEH.ACTIONTYPE_FATIGUE,
|
||||
FIRE = CEH.ACTIONTYPE_FIRE,
|
||||
LAVA = CEH.ACTIONTYPE_LAVA,
|
||||
SLIME = CEH.ACTIONTYPE_SLIME,
|
||||
EXHAUSTED = CEH.ACTIONTYPE_FATIGUE,
|
||||
}
|
||||
|
||||
local cleuHandler
|
||||
|
||||
cleuHandler = function()
|
||||
local sub = arg1
|
||||
if not sub then return end
|
||||
|
||||
local srcName = arg3
|
||||
local dstName = arg5
|
||||
|
||||
if not srcName or srcName == "" then srcName = "Unknown" end
|
||||
if not dstName or dstName == "" then dstName = "Unknown" end
|
||||
|
||||
-- ========================================================================
|
||||
-- SWING_DAMAGE
|
||||
-- args: amount(6), overkill(7), school(8), resisted(9), blocked(10),
|
||||
-- absorbed(11), critical(12), glancing(13), crushing(14)
|
||||
-- ========================================================================
|
||||
|
||||
if sub == "SWING_DAMAGE" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local amount = arg6 or 0
|
||||
local resisted = arg9 or 0
|
||||
local blocked = arg10 or 0
|
||||
local absorbed = arg11 or 0
|
||||
local critical = arg12 == 1
|
||||
local glancing = arg13 == 1 and 1 or 0
|
||||
local crushing = arg14 == 1 and 1 or 0
|
||||
local hitType = critical and HIT_CRIT or HIT_NORMAL
|
||||
|
||||
local data = GetDamageData(dir, HIT, hitType, DMG_PHYSICAL, amount, nil, name)
|
||||
applyPartials(data, resisted, blocked, absorbed, glancing, crushing)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SWING_MISSED
|
||||
-- args: missType(6), amountMissed(7)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SWING_MISSED" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local action = missActionMap[arg6] or MISS
|
||||
local data = GetDamageData(dir, action, nil, nil, nil, nil, name)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SPELL_DAMAGE / RANGE_DAMAGE / SPELL_PERIODIC_DAMAGE / DAMAGE_SHIELD / DAMAGE_SPLIT
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: amount(9), overkill(10), school(11), resisted(12), blocked(13),
|
||||
-- absorbed(14), critical(15), glancing(16), crushing(17)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_DAMAGE" or sub == "RANGE_DAMAGE" or sub == "DAMAGE_SHIELD"
|
||||
or sub == "DAMAGE_SPLIT" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local school = arg11
|
||||
local resisted = arg12 or 0
|
||||
local blocked = arg13 or 0
|
||||
local absorbed = arg14 or 0
|
||||
local critical = arg15 == 1
|
||||
local crushing = arg17 == 1 and 1 or 0
|
||||
local hitType = critical and HIT_CRIT or HIT_NORMAL
|
||||
local dmgType = schoolToDamageType(school)
|
||||
|
||||
local data = GetDamageData(dir, HIT, hitType, dmgType, amount, spellName, name)
|
||||
applyPartials(data, resisted, blocked, absorbed, 0, crushing)
|
||||
SendEvent(data)
|
||||
|
||||
elseif sub == "SPELL_PERIODIC_DAMAGE" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local school = arg11
|
||||
local resisted = arg12 or 0
|
||||
local absorbed = arg14 or 0
|
||||
local dmgType = schoolToDamageType(school)
|
||||
|
||||
local data = GetDamageData(dir, HIT, HIT_DOT, dmgType, amount, spellName, name)
|
||||
applyPartials(data, resisted, 0, absorbed, 0, 0)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SPELL_MISSED / RANGE_MISSED / SPELL_PERIODIC_MISSED / DAMAGE_SHIELD_MISSED
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: missType(9), amountMissed(10)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_MISSED" or sub == "RANGE_MISSED"
|
||||
or sub == "SPELL_PERIODIC_MISSED" or sub == "DAMAGE_SHIELD_MISSED" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local spellName = arg7
|
||||
local action = missActionMap[arg9] or MISS
|
||||
local data = GetDamageData(dir, action, nil, nil, nil, spellName, name)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- ENVIRONMENTAL_DAMAGE
|
||||
-- args: envType(6), amount(7), overkill(8), school(9), resisted(10),
|
||||
-- blocked(11), absorbed(12), critical(13), glancing(14), crushing(15)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "ENVIRONMENTAL_DAMAGE" then
|
||||
if dstName ~= playerName then return end
|
||||
local envType = arg6
|
||||
local amount = arg7 or 0
|
||||
local action = envActionMap[envType] or HIT
|
||||
local data = GetDamageData(INCOMING, action, HIT_NORMAL, DMG_PHYSICAL, amount, nil, envType or "Environment")
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SPELL_HEAL / SPELL_PERIODIC_HEAL
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: amount(9), overheal(10), absorbed(11), critical(12)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_HEAL" or sub == "SPELL_PERIODIC_HEAL" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local overheal = arg10 or 0
|
||||
local critical = arg12 == 1
|
||||
local isPeriodic = (sub == "SPELL_PERIODIC_HEAL")
|
||||
|
||||
local healType
|
||||
if isPeriodic then
|
||||
healType = HEAL_HOT
|
||||
elseif critical then
|
||||
healType = HEAL_CRIT
|
||||
else
|
||||
healType = HEAL_NORMAL
|
||||
end
|
||||
|
||||
-- For outgoing heals, name = target (dstName); for incoming, name = healer (srcName)
|
||||
local data = GetHealData(dir, healType, amount, spellName, name)
|
||||
|
||||
-- Overheal partial
|
||||
if overheal > 0 then
|
||||
data.PartialActionType = PARTIAL_OVERHEAL
|
||||
data.PartialAmount = overheal
|
||||
end
|
||||
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SPELL_ENERGIZE / SPELL_PERIODIC_ENERGIZE
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: amount(9), powerType(10)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_ENERGIZE" or sub == "SPELL_PERIODIC_ENERGIZE" then
|
||||
if dstName ~= playerName then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local powerType = arg10 or 0
|
||||
local powerName = powerTypeNames[powerType] or "Mana"
|
||||
local data = GetNotifData(NOTIF_POWER_GAIN, amount .. " " .. powerName, spellName)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SPELL_PERIODIC_DRAIN / SPELL_PERIODIC_LEECH
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: amount(9), powerType(10), extraAmount(11)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_PERIODIC_DRAIN" then
|
||||
if dstName ~= playerName then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local powerType = arg10 or 0
|
||||
local powerName = powerTypeNames[powerType] or "Mana"
|
||||
local data = GetNotifData(NOTIF_POWER_LOSS, amount .. " " .. powerName, spellName)
|
||||
SendEvent(data)
|
||||
|
||||
elseif sub == "SPELL_PERIODIC_LEECH" then
|
||||
-- Leech = damage to target + heal to source
|
||||
-- Show as damage taken if we're the target
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local dmgType = schoolToDamageType(arg8)
|
||||
local data = GetDamageData(dir, HIT, HIT_DOT, dmgType, amount, spellName, name)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- AURA events
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: auraType(9)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_AURA_APPLIED" then
|
||||
if dstName ~= playerName then return end
|
||||
local spellName = arg7
|
||||
local auraType = arg9
|
||||
if auraType == "DEBUFF" then
|
||||
local data = GetNotifData(NOTIF_DEBUFF, nil, spellName)
|
||||
SendEvent(data)
|
||||
else
|
||||
local data = GetNotifData(NOTIF_BUFF, nil, spellName)
|
||||
SendEvent(data)
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_AURA_REMOVED" then
|
||||
if dstName ~= playerName then return end
|
||||
local spellName = arg7
|
||||
local data = GetNotifData(NOTIF_BUFF_FADE, nil, spellName)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- PARTY_KILL — killing blow notification
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "PARTY_KILL" then
|
||||
if srcName ~= playerName then return end
|
||||
local notifType = CEH.NOTIFICATIONTYPE_NPC_KILLING_BLOW
|
||||
if CEH.recentlySelectedPlayers[dstName] then
|
||||
notifType = CEH.NOTIFICATIONTYPE_PC_KILLING_BLOW
|
||||
end
|
||||
local data = GetNotifData(notifType, nil, dstName)
|
||||
SendEvent(data)
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Performance profiling (A/B per-combat)
|
||||
-- ============================================================================
|
||||
|
||||
local profiling = false
|
||||
local profCLEU = { events = 0, totalMs = 0, gcStart = 0 }
|
||||
local profOrig = { events = 0, totalMs = 0, gcStart = 0 }
|
||||
local profCurrent = nil
|
||||
|
||||
-- Hook the original MikCEH.OnEvent to measure original mode
|
||||
-- Only count events that CLEU replaces (combat parse), not honor/XP/rep/health/etc.
|
||||
local origOnEvent = MikCEH.OnEvent
|
||||
local cleuReplacedLookup = CEH.cleuReplacedLookup
|
||||
local function measuredOrigOnEvent()
|
||||
if profiling and profCurrent == profOrig and cleuReplacedLookup[event] then
|
||||
local before = debugprofilestop()
|
||||
origOnEvent()
|
||||
local after = debugprofilestop()
|
||||
profOrig.totalMs = profOrig.totalMs + (after - before)
|
||||
profOrig.events = profOrig.events + 1
|
||||
else
|
||||
origOnEvent()
|
||||
end
|
||||
end
|
||||
-- Patch MikCEH.OnEvent so the XML OnEvent handler calls the measured version
|
||||
MikCEH.OnEvent = measuredOrigOnEvent
|
||||
|
||||
local cleuEventsSkipped = 0
|
||||
|
||||
local function measuredCLEUHandler()
|
||||
if profiling and profCurrent == profCLEU then
|
||||
-- Quick relevance check: is player or pet involved?
|
||||
local srcName = arg3 or ""
|
||||
local dstName = arg5 or ""
|
||||
local relevant = (srcName == playerName or dstName == playerName
|
||||
or (petName and (srcName == petName or dstName == petName)))
|
||||
|
||||
local before = debugprofilestop()
|
||||
cleuHandler()
|
||||
local after = debugprofilestop()
|
||||
|
||||
if relevant then
|
||||
profCLEU.totalMs = profCLEU.totalMs + (after - before)
|
||||
profCLEU.events = profCLEU.events + 1
|
||||
else
|
||||
cleuEventsSkipped = cleuEventsSkipped + 1
|
||||
end
|
||||
else
|
||||
cleuHandler()
|
||||
end
|
||||
end
|
||||
|
||||
cleuFrame:SetScript("OnEvent", measuredCLEUHandler)
|
||||
|
||||
local function profReset(tbl)
|
||||
tbl.events = 0
|
||||
tbl.totalMs = 0
|
||||
tbl.gcStart = gcinfo()
|
||||
end
|
||||
|
||||
local lastCLEUAvg = nil
|
||||
local lastOrigAvg = nil
|
||||
|
||||
local function profReport(label, tbl)
|
||||
local gcEnd = gcinfo()
|
||||
local gcDelta = gcEnd - tbl.gcStart
|
||||
local totalMs = tbl.totalMs / 1000
|
||||
local avgUs = tbl.events > 0 and (tbl.totalMs / tbl.events) or 0
|
||||
|
||||
if label == "CLEU" then
|
||||
lastCLEUAvg = avgUs
|
||||
else
|
||||
lastOrigAvg = avgUs
|
||||
end
|
||||
|
||||
local skipMsg = ""
|
||||
if label == "CLEU" then
|
||||
skipMsg = string.format(" (%d skipped)", cleuEventsSkipped)
|
||||
cleuEventsSkipped = 0
|
||||
end
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||
"|cff00ff00[WSBT %s]|r %d events%s, %.1fms total, %.1f us/event, %+.1f KB gc",
|
||||
label, tbl.events, skipMsg, totalMs, avgUs, gcDelta))
|
||||
|
||||
if lastCLEUAvg and lastOrigAvg and lastOrigAvg > 0 then
|
||||
local pct = ((lastCLEUAvg - lastOrigAvg) / lastOrigAvg) * 100
|
||||
local sign = pct < 0 and "" or "+"
|
||||
local color = pct < 0 and "|cff00ff00" or "|cffff4444"
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||
"%s[WSBT CLEU vs ORIGINAL]|r %.1f vs %.1f us/event = %s%.1f%%|r %s",
|
||||
color, lastCLEUAvg, lastOrigAvg, sign, pct,
|
||||
pct < 0 and "(CLEU faster)" or "(ORIGINAL faster)"))
|
||||
end
|
||||
end
|
||||
|
||||
local benchActive = true
|
||||
local benchFrame = CreateFrame("Frame")
|
||||
|
||||
local function benchCombatStart()
|
||||
if not benchActive then return end
|
||||
profCurrent = cleuActive and profCLEU or profOrig
|
||||
profReset(profCurrent)
|
||||
debugprofilestart()
|
||||
profiling = true
|
||||
local label = cleuActive and "CLEU" or "ORIGINAL"
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[WSBT Bench]|r combat started, measuring %s", label))
|
||||
end
|
||||
|
||||
local function benchCombatEnd()
|
||||
if not benchActive or not profiling then return end
|
||||
profiling = false
|
||||
local label = cleuActive and "CLEU" or "ORIGINAL"
|
||||
profReport(label, profCurrent)
|
||||
toggle()
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||
"|cff00ff00[WSBT Bench]|r next combat will use: %s", cleuActive and "CLEU" or "ORIGINAL"))
|
||||
end
|
||||
|
||||
benchFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||
benchFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
benchFrame:SetScript("OnEvent", function()
|
||||
if event == "PLAYER_REGEN_DISABLED" then
|
||||
benchCombatStart()
|
||||
elseif event == "PLAYER_REGEN_ENABLED" then
|
||||
benchCombatEnd()
|
||||
end
|
||||
end)
|
||||
|
||||
SLASH_WSBTBENCH1 = "/wsbtbench"
|
||||
SlashCmdList["WSBTBENCH"] = function()
|
||||
benchActive = not benchActive
|
||||
if benchActive then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||
"|cff00ff00[WSBT Bench]|r enabled. Current mode: %s. Enter combat to begin.",
|
||||
cleuActive and "CLEU" or "ORIGINAL"))
|
||||
else
|
||||
profiling = false
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[WSBT Bench]|r disabled.")
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Start in CLEU mode by default
|
||||
-- ============================================================================
|
||||
|
||||
enableCLEU()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
-- DPSMate CLEU Adapter
|
||||
-- Replaces the string-parsing CHAT_MSG_* event system with structured COMBAT_LOG_EVENT
|
||||
-- data from the DPSLog module. Registers for COMBAT_LOG_EVENT and calls DPSMate.DB
|
||||
-- Replaces the string-parsing CHAT_MSG_* event system with structured COMBAT_LOG_EVENT_UNFILTERED
|
||||
-- data from the DPSLog module. Registers for COMBAT_LOG_EVENT_UNFILTERED and calls DPSMate.DB
|
||||
-- functions directly with extracted values.
|
||||
--
|
||||
-- Toggle: /dpscleu (switches between CLEU adapter and original string parser)
|
||||
-- Requires: DPSLog module (provides COMBAT_LOG_EVENT + GetSpellInfo)
|
||||
-- Requires: DPSLog module (provides COMBAT_LOG_EVENT_UNFILTERED + GetSpellInfo)
|
||||
|
||||
if not DPSMate or not DPSMate.DB then return end
|
||||
|
||||
@@ -75,14 +75,14 @@ local function enableCLEU()
|
||||
Parser:UnregisterEvent(ev)
|
||||
end
|
||||
-- Enable CLEU
|
||||
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT")
|
||||
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||
cleuActive = true
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00DPSMate CLEU Adapter|r: |cff00ff00ON|r (structured events)")
|
||||
end
|
||||
|
||||
local function enableOriginal()
|
||||
-- Disable CLEU
|
||||
cleuFrame:UnregisterEvent("COMBAT_LOG_EVENT")
|
||||
cleuFrame:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||
-- Re-enable string parser
|
||||
for _, ev in ipairs(chatEvents) do
|
||||
Parser:RegisterEvent(ev)
|
||||
@@ -129,177 +129,9 @@ local function isGroupMember(name)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- CLEU event handler
|
||||
-- (dead handler removed — real handler is cleuHandler below, wrapped by profiling)
|
||||
-- ============================================================================
|
||||
|
||||
cleuFrame:SetScript("OnEvent", function()
|
||||
local sub = arg1
|
||||
if not sub then return end
|
||||
|
||||
local srcGUID = arg2
|
||||
local srcName = arg3
|
||||
local dstGUID = arg4
|
||||
local dstName = arg5
|
||||
|
||||
if not srcName or srcName == "" then srcName = "Unknown" end
|
||||
if not dstName or dstName == "" then dstName = "Unknown" end
|
||||
|
||||
-- ========================================================================
|
||||
-- DAMAGE events
|
||||
-- ========================================================================
|
||||
|
||||
if sub == "SWING_DAMAGE" then
|
||||
local amount = arg6 or 0
|
||||
local critical = arg12 == 1 and 1 or 0
|
||||
local glancing = arg13 == 1 and 1 or 0
|
||||
local crushing = arg14 == 1 and 1 or 0
|
||||
local hit = (critical == 0 and glancing == 0 and crushing == 0) and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, AAttack, hit, critical, 0, 0, 0, 0, amount, glancing, 0)
|
||||
DB:DamageTaken(dstName, AAttack, hit, critical, 0, 0, 0, 0, amount, srcName, crushing, 0)
|
||||
DB:EnemyDamage(1, DPSMateEDT, dstName, AAttack, hit, critical, 0, 0, 0, 0, amount, srcName, 0, crushing)
|
||||
DB:EnemyDamage(2, DPSMateEDD, srcName, AAttack, hit, critical, 0, 0, 0, 0, amount, dstName, 0, 0)
|
||||
DB:DeathHistory(dstName, srcName, AAttack, amount, hit, critical, "hit", crushing)
|
||||
|
||||
elseif sub == "SWING_MISSED" then
|
||||
local missType = arg6
|
||||
local miss = (missType == "MISS") and 1 or 0
|
||||
local parry = (missType == "PARRY") and 1 or 0
|
||||
local dodge = (missType == "DODGE") and 1 or 0
|
||||
local resist = (missType == "RESIST" or missType == "IMMUNE") and 1 or 0
|
||||
local block = (missType == "BLOCK") and 1 or 0
|
||||
local absorb = (missType == "ABSORB") and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, AAttack, 0, 0, miss + absorb, parry, dodge, resist, 0, 0, block)
|
||||
DB:DamageTaken(dstName, AAttack, 0, 0, miss + absorb, parry, dodge, resist, 0, srcName, 0, block)
|
||||
|
||||
elseif sub == "SPELL_DAMAGE" or sub == "RANGE_DAMAGE" or sub == "SPELL_PERIODIC_DAMAGE"
|
||||
or sub == "DAMAGE_SHIELD" or sub == "DAMAGE_SPLIT" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local amount = arg9 or 0
|
||||
local school = arg11
|
||||
local critical = arg15 == 1 and 1 or 0
|
||||
local glancing = arg16 == 1 and 1 or 0
|
||||
local crushing = arg17 == 1 and 1 or 0
|
||||
local hit = (critical == 0 and glancing == 0 and crushing == 0) and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, spellName, hit, critical, 0, 0, 0, 0, amount, glancing, 0)
|
||||
DB:DamageTaken(dstName, spellName, hit, critical, 0, 0, 0, 0, amount, srcName, crushing, 0)
|
||||
DB:EnemyDamage(1, DPSMateEDT, dstName, spellName, hit, critical, 0, 0, 0, 0, amount, srcName, 0, crushing)
|
||||
DB:EnemyDamage(2, DPSMateEDD, srcName, spellName, hit, critical, 0, 0, 0, 0, amount, dstName, 0, 0)
|
||||
DB:DeathHistory(dstName, srcName, spellName, amount, hit, critical, "hit", crushing)
|
||||
if school then DB:AddSpellSchool(spellName, school) end
|
||||
|
||||
elseif sub == "SPELL_MISSED" or sub == "RANGE_MISSED"
|
||||
or sub == "SPELL_PERIODIC_MISSED" or sub == "DAMAGE_SHIELD_MISSED" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local missType = arg9
|
||||
local miss = (missType == "MISS") and 1 or 0
|
||||
local parry = (missType == "PARRY") and 1 or 0
|
||||
local dodge = (missType == "DODGE") and 1 or 0
|
||||
local resist = (missType == "RESIST" or missType == "IMMUNE") and 1 or 0
|
||||
local block = (missType == "BLOCK") and 1 or 0
|
||||
local absorb = (missType == "ABSORB") and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, spellName, 0, 0, miss + absorb, parry, dodge, resist, 0, 0, block)
|
||||
DB:DamageTaken(dstName, spellName, 0, 0, miss + absorb, parry, dodge, resist, 0, srcName, 0, block)
|
||||
|
||||
elseif sub == "ENVIRONMENTAL_DAMAGE" then
|
||||
local envType = arg6
|
||||
local amount = arg7 or 0
|
||||
DB:DamageTaken(dstName, envType or "Environment", 1, 0, 0, 0, 0, 0, amount, envType or "Environment", 0, 0)
|
||||
DB:DeathHistory(dstName, envType or "Environment", envType or "Environment", amount, 1, 0, "hit", 0)
|
||||
|
||||
-- ========================================================================
|
||||
-- HEAL events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_HEAL" or sub == "SPELL_PERIODIC_HEAL" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local amount = arg9 or 0
|
||||
local overheal = arg10 or 0
|
||||
local critical = arg12 == 1 and 1 or 0
|
||||
local hit = critical == 0 and 1 or 0
|
||||
local effective = amount - overheal
|
||||
if effective < 0 then effective = 0 end
|
||||
|
||||
DB:Healing(1, DPSMateHealingTaken, srcName, spellName, hit, critical, effective)
|
||||
DB:Healing(2, DPSMateOverhealing, srcName, spellName, hit, critical, overheal)
|
||||
DB:HealingTaken(1, DPSMateHealingTaken, srcName, spellName, hit, critical, effective, dstName)
|
||||
DB:DeathHistory(dstName, srcName, spellName, effective, hit, critical, "heal", 0)
|
||||
|
||||
-- ========================================================================
|
||||
-- AURA events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_AURA_APPLIED" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local auraType = arg9
|
||||
if auraType == "DEBUFF" then
|
||||
DB:BuildBuffs(srcName, dstName, spellName, false)
|
||||
if Parser.CC[spellName] then
|
||||
DB:BuildActiveCC(dstName, spellName)
|
||||
end
|
||||
else
|
||||
DB:BuildBuffs(srcName, dstName, spellName, true)
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_AURA_REMOVED" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
DB:DestroyBuffs(dstName, spellName)
|
||||
local auraType = arg9
|
||||
if auraType == "DEBUFF" then
|
||||
DB:RemoveActiveCC(dstName, spellName)
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_AURA_BROKEN_SPELL" or sub == "SPELL_AURA_BROKEN" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
DB:RemoveActiveCC(dstName, spellName)
|
||||
if Parser.CC[spellName] then
|
||||
DB:CCBreaker(dstName, spellName, srcName)
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- CAST events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_CAST_SUCCESS" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
if Parser.Kicks and Parser.Kicks[spellName] then
|
||||
DB:RegisterPotentialKick(srcName, spellName, GT())
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- INTERRUPT / DISPEL events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_INTERRUPT" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local extraSpellName = arg10 or "Unknown"
|
||||
DB:Kick(srcName, dstName, spellName, extraSpellName)
|
||||
|
||||
elseif sub == "SPELL_DISPEL" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local extraSpellName = arg10 or "Unknown"
|
||||
if isGroupMember(srcName) then
|
||||
DB:Dispels(srcName, spellName, dstName, extraSpellName)
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- DEATH events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "UNIT_DIED" or sub == "UNIT_DESTROYED" then
|
||||
DB:UnregisterDeath(dstName)
|
||||
|
||||
elseif sub == "SPELL_SUMMON" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
if not Parser.petToOwnerMap then Parser.petToOwnerMap = {} end
|
||||
if not Parser.petToOwnerMap[dstName] then Parser.petToOwnerMap[dstName] = {} end
|
||||
Parser.petToOwnerMap[dstName][srcName] = true
|
||||
end
|
||||
end)
|
||||
|
||||
-- ============================================================================
|
||||
-- Performance profiling
|
||||
-- ============================================================================
|
||||
@@ -441,6 +273,10 @@ cleuFrame:SetScript("OnEvent", function()
|
||||
end)
|
||||
|
||||
-- The actual CLEU handler (extracted so we can call it directly or measured)
|
||||
local ShieldFlags = DB.ShieldFlags
|
||||
local FailDT = DPSMate.Parser.FailDT
|
||||
local FailDB = DPSMate.Parser.FailDB
|
||||
|
||||
cleuHandler = function()
|
||||
local sub = arg1
|
||||
if not sub then return end
|
||||
@@ -453,17 +289,30 @@ cleuHandler = function()
|
||||
if not srcName or srcName == "" then srcName = "Unknown" end
|
||||
if not dstName or dstName == "" then dstName = "Unknown" end
|
||||
|
||||
-- ========================================================================
|
||||
-- DAMAGE events
|
||||
-- ========================================================================
|
||||
-- SWING_DAMAGE args: amount(6), overkill(7), school(8), resisted(9),
|
||||
-- blocked(10), absorbed(11), critical(12), glancing(13), crushing(14)
|
||||
|
||||
if sub == "SWING_DAMAGE" then
|
||||
local amount = arg6 or 0
|
||||
local absorbed = arg11 or 0
|
||||
local critical = arg12 == 1 and 1 or 0
|
||||
local glancing = arg13 == 1 and 1 or 0
|
||||
local crushing = arg14 == 1 and 1 or 0
|
||||
local hit = (critical == 0 and glancing == 0 and crushing == 0) and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, AAttack, hit, critical, 0, 0, 0, 0, amount, glancing, 0)
|
||||
DB:DamageTaken(dstName, AAttack, hit, critical, 0, 0, 0, 0, amount, srcName, crushing, 0)
|
||||
DB:EnemyDamage(1, DPSMateEDT, dstName, AAttack, hit, critical, 0, 0, 0, 0, amount, srcName, 0, crushing)
|
||||
DB:EnemyDamage(2, DPSMateEDD, srcName, AAttack, hit, critical, 0, 0, 0, 0, amount, dstName, 0, 0)
|
||||
DB:DeathHistory(dstName, srcName, AAttack, amount, hit, critical, "hit", crushing)
|
||||
-- Partial absorb on a hit
|
||||
if absorbed > 0 then
|
||||
DB:SetUnregisterVariables(absorbed, AAttack, srcName)
|
||||
DB:Absorb(AAttack, dstName, srcName)
|
||||
end
|
||||
|
||||
elseif sub == "SWING_MISSED" then
|
||||
local missType = arg6
|
||||
@@ -473,24 +322,44 @@ cleuHandler = function()
|
||||
local resist = (missType == "RESIST" or missType == "IMMUNE") and 1 or 0
|
||||
local block = (missType == "BLOCK") and 1 or 0
|
||||
local absorb = (missType == "ABSORB") and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, AAttack, 0, 0, miss + absorb, parry, dodge, resist, 0, 0, block)
|
||||
DB:DamageTaken(dstName, AAttack, 0, 0, miss + absorb, parry, dodge, resist, 0, srcName, 0, block)
|
||||
-- Full absorb
|
||||
if absorb == 1 then
|
||||
DB:Absorb(AAttack, dstName, srcName)
|
||||
end
|
||||
|
||||
-- SPELL_DAMAGE args (after prefix): amount(9), overkill(10), school(11),
|
||||
-- resisted(12), blocked(13), absorbed(14), critical(15), glancing(16), crushing(17)
|
||||
|
||||
elseif sub == "SPELL_DAMAGE" or sub == "RANGE_DAMAGE" or sub == "SPELL_PERIODIC_DAMAGE"
|
||||
or sub == "DAMAGE_SHIELD" or sub == "DAMAGE_SPLIT" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local amount = arg9 or 0
|
||||
local school = arg11
|
||||
local absorbed = arg14 or 0
|
||||
local critical = arg15 == 1 and 1 or 0
|
||||
local glancing = arg16 == 1 and 1 or 0
|
||||
local crushing = arg17 == 1 and 1 or 0
|
||||
local hit = (critical == 0 and glancing == 0 and crushing == 0) and 1 or 0
|
||||
DB:DamageDone(srcName, spellName, hit, critical, 0, 0, 0, 0, amount, glancing, 0)
|
||||
DB:DamageTaken(dstName, spellName, hit, critical, 0, 0, 0, 0, amount, srcName, crushing, 0)
|
||||
DB:EnemyDamage(1, DPSMateEDT, dstName, spellName, hit, critical, 0, 0, 0, 0, amount, srcName, 0, crushing)
|
||||
DB:EnemyDamage(2, DPSMateEDD, srcName, spellName, hit, critical, 0, 0, 0, 0, amount, dstName, 0, 0)
|
||||
DB:DeathHistory(dstName, srcName, spellName, amount, hit, critical, "hit", crushing)
|
||||
if school then DB:AddSpellSchool(spellName, school) end
|
||||
local abilityName = (sub == "SPELL_PERIODIC_DAMAGE") and (spellName .. "(Periodic)") or spellName
|
||||
|
||||
DB:DamageDone(srcName, abilityName, hit, critical, 0, 0, 0, 0, amount, glancing, 0)
|
||||
DB:DamageTaken(dstName, abilityName, hit, critical, 0, 0, 0, 0, amount, srcName, crushing, 0)
|
||||
DB:EnemyDamage(1, DPSMateEDT, dstName, abilityName, hit, critical, 0, 0, 0, 0, amount, srcName, 0, crushing)
|
||||
DB:EnemyDamage(2, DPSMateEDD, srcName, abilityName, hit, critical, 0, 0, 0, 0, amount, dstName, 0, 0)
|
||||
DB:DeathHistory(dstName, srcName, abilityName, amount, hit, critical, "hit", crushing)
|
||||
if school then DB:AddSpellSchool(abilityName, school) end
|
||||
-- Partial absorb on a hit
|
||||
if absorbed > 0 then
|
||||
DB:SetUnregisterVariables(absorbed, abilityName, srcName)
|
||||
DB:Absorb(abilityName, dstName, srcName)
|
||||
end
|
||||
-- Avoidable damage taken (fire, void zones, etc.)
|
||||
if FailDT and FailDT[spellName] then
|
||||
DB:BuildFail(2, srcName, dstName, abilityName, amount)
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_MISSED" or sub == "RANGE_MISSED"
|
||||
or sub == "SPELL_PERIODIC_MISSED" or sub == "DAMAGE_SHIELD_MISSED" then
|
||||
@@ -502,8 +371,15 @@ cleuHandler = function()
|
||||
local resist = (missType == "RESIST" or missType == "IMMUNE") and 1 or 0
|
||||
local block = (missType == "BLOCK") and 1 or 0
|
||||
local absorb = (missType == "ABSORB") and 1 or 0
|
||||
DB:DamageDone(srcName, spellName, 0, 0, miss + absorb, parry, dodge, resist, 0, 0, block)
|
||||
DB:DamageTaken(dstName, spellName, 0, 0, miss + absorb, parry, dodge, resist, 0, srcName, 0, block)
|
||||
local isPeriodic = (sub == "SPELL_PERIODIC_MISSED")
|
||||
local abilityName = isPeriodic and (spellName .. "(Periodic)") or spellName
|
||||
|
||||
DB:DamageDone(srcName, abilityName, 0, 0, miss + absorb, parry, dodge, resist, 0, 0, block)
|
||||
DB:DamageTaken(dstName, abilityName, 0, 0, miss + absorb, parry, dodge, resist, 0, srcName, 0, block)
|
||||
-- Full absorb
|
||||
if absorb == 1 then
|
||||
DB:Absorb(abilityName, dstName, srcName)
|
||||
end
|
||||
|
||||
elseif sub == "ENVIRONMENTAL_DAMAGE" then
|
||||
local envType = arg6
|
||||
@@ -511,6 +387,10 @@ cleuHandler = function()
|
||||
DB:DamageTaken(dstName, envType or "Environment", 1, 0, 0, 0, 0, 0, amount, envType or "Environment", 0, 0)
|
||||
DB:DeathHistory(dstName, envType or "Environment", envType or "Environment", amount, 1, 0, "hit", 0)
|
||||
|
||||
-- ========================================================================
|
||||
-- HEAL events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_HEAL" or sub == "SPELL_PERIODIC_HEAL" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local amount = arg9 or 0
|
||||
@@ -519,11 +399,16 @@ cleuHandler = function()
|
||||
local hit = critical == 0 and 1 or 0
|
||||
local effective = amount - overheal
|
||||
if effective < 0 then effective = 0 end
|
||||
|
||||
DB:Healing(1, DPSMateHealingTaken, srcName, spellName, hit, critical, effective)
|
||||
DB:Healing(2, DPSMateOverhealing, srcName, spellName, hit, critical, overheal)
|
||||
DB:HealingTaken(1, DPSMateHealingTaken, srcName, spellName, hit, critical, effective, dstName)
|
||||
DB:DeathHistory(dstName, srcName, spellName, effective, hit, critical, "heal", 0)
|
||||
|
||||
-- ========================================================================
|
||||
-- AURA events (+ absorb shield lifecycle)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_AURA_APPLIED" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local auraType = arg9
|
||||
@@ -532,15 +417,29 @@ cleuHandler = function()
|
||||
if Parser.CC[spellName] then
|
||||
DB:BuildActiveCC(dstName, spellName)
|
||||
end
|
||||
-- Avoidable debuff application
|
||||
if FailDB and FailDB[spellName] then
|
||||
DB:BuildFail(3, "Environment", dstName, spellName, 0)
|
||||
end
|
||||
else
|
||||
DB:BuildBuffs(srcName, dstName, spellName, true)
|
||||
-- Absorb shield applied
|
||||
if ShieldFlags[spellName] then
|
||||
DB:ConfirmAbsorbApplication(spellName, dstName, GT())
|
||||
end
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_AURA_REMOVED" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
DB:DestroyBuffs(dstName, spellName)
|
||||
if arg9 == "DEBUFF" then
|
||||
local auraType = arg9
|
||||
if auraType == "DEBUFF" then
|
||||
DB:RemoveActiveCC(dstName, spellName)
|
||||
else
|
||||
-- Absorb shield removed
|
||||
if ShieldFlags[spellName] then
|
||||
DB:UnregisterAbsorb(spellName, dstName)
|
||||
end
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_AURA_BROKEN_SPELL" or sub == "SPELL_AURA_BROKEN" then
|
||||
@@ -550,11 +449,23 @@ cleuHandler = function()
|
||||
DB:CCBreaker(dstName, spellName, srcName)
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- CAST events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_CAST_SUCCESS" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
if Parser.Kicks and Parser.Kicks[spellName] then
|
||||
DB:RegisterPotentialKick(srcName, spellName, GT())
|
||||
end
|
||||
-- Shield cast → await confirmation via SPELL_AURA_APPLIED
|
||||
if ShieldFlags[spellName] then
|
||||
DB:AwaitingAbsorbConfirmation(srcName, spellName, dstName, GT())
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- INTERRUPT / DISPEL events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_INTERRUPT" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
@@ -568,6 +479,10 @@ cleuHandler = function()
|
||||
DB:Dispels(srcName, spellName, dstName, extraSpellName)
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- DEATH events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "UNIT_DIED" or sub == "UNIT_DESTROYED" then
|
||||
DB:UnregisterDeath(dstName)
|
||||
|
||||
|
||||
@@ -591,7 +591,7 @@ end)
|
||||
|
||||
-- Expand args table to handle up to 17 args (spell damage has the most: 5 base + 3 prefix + 9 suffix)
|
||||
local eventFrame = CreateFrame("Frame")
|
||||
eventFrame:RegisterEvent("COMBAT_LOG_EVENT")
|
||||
eventFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||
|
||||
eventFrame:SetScript("OnEvent", function()
|
||||
local subevent = arg1
|
||||
|
||||
+437
-210
@@ -1,6 +1,6 @@
|
||||
//! DPS log module — TBC/WotLK-style COMBAT_LOG_EVENT for vanilla 1.12.1.
|
||||
//! DPS log module — TBC/WotLK-style COMBAT_LOG_EVENT_UNFILTERED for vanilla 1.12.1.
|
||||
//!
|
||||
//! Fires a unified COMBAT_LOG_EVENT with WotLK-style subevent names and
|
||||
//! Fires a unified COMBAT_LOG_EVENT_UNFILTERED with WotLK-style subevent names and
|
||||
//! structured args. All subevents share: arg1=subevent, arg2=sourceGUID,
|
||||
//! arg3=destGUID. Remaining args are subevent-specific.
|
||||
//!
|
||||
@@ -87,12 +87,12 @@ pub fn isActive() bool {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COMBAT_LOG_EVENT — slot assigned dynamically in createEventsDetour
|
||||
// COMBAT_LOG_EVENT_UNFILTERED — slot assigned dynamically in createEventsDetour
|
||||
// =============================================================================
|
||||
|
||||
var g_event_combat_log: u32 = 0;
|
||||
const EVENT_TABLE_MAIN: u32 = 0xBE1198;
|
||||
const event_name: [*:0]const u8 = "COMBAT_LOG_EVENT";
|
||||
const event_name: [*:0]const u8 = "COMBAT_LOG_EVENT_UNFILTERED";
|
||||
|
||||
// =============================================================================
|
||||
// Subevent name strings (WotLK naming)
|
||||
@@ -119,6 +119,7 @@ const SUB_SPELL_PERIODIC_HEAL: [*:0]const u8 = "SPELL_PERIODIC_HEAL";
|
||||
|
||||
// Energize / Drain
|
||||
const SUB_SPELL_ENERGIZE: [*:0]const u8 = "SPELL_ENERGIZE";
|
||||
const SUB_SPELL_DRAIN: [*:0]const u8 = "SPELL_DRAIN";
|
||||
const SUB_SPELL_PERIODIC_ENERGIZE: [*:0]const u8 = "SPELL_PERIODIC_ENERGIZE";
|
||||
const SUB_SPELL_PERIODIC_DRAIN: [*:0]const u8 = "SPELL_PERIODIC_DRAIN";
|
||||
const SUB_SPELL_PERIODIC_LEECH: [*:0]const u8 = "SPELL_PERIODIC_LEECH";
|
||||
@@ -296,6 +297,94 @@ fn guidToString(guid: u64) [*:0]const u8 {
|
||||
return @ptrCast(buf[0..18 :0]);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Handler Table Swap — replace NetClient opcode dispatch entries
|
||||
// =============================================================================
|
||||
//
|
||||
// NetClient__ProcessMessage (0x537AA0) dispatches packets via:
|
||||
// handler = NetClient[opcode * 4 + 0x74]
|
||||
// call handler(ECX=context, EDX=opcode, stack: timestamp, CDataStore*)
|
||||
//
|
||||
// We swap pointers in the heap-allocated NetClient object (no code patching).
|
||||
// Original handler is saved and called after our processing.
|
||||
|
||||
const NETCLIENT_PTR: u32 = 0xC28128;
|
||||
const HANDLER_TABLE_BASE: u32 = 0x74;
|
||||
|
||||
const HandlerSwap = struct {
|
||||
opcode: u16,
|
||||
original: u32 = 0,
|
||||
active: bool = false,
|
||||
};
|
||||
|
||||
// Max swaps we'll ever need (one per opcode we intercept)
|
||||
const MAX_SWAPS = 16;
|
||||
var handler_swaps: [MAX_SWAPS]HandlerSwap = [_]HandlerSwap{.{ .opcode = 0 }} ** MAX_SWAPS;
|
||||
var swap_count: u32 = 0;
|
||||
|
||||
fn getNetClient() ?u32 {
|
||||
const nc = hook.readMem(u32, NETCLIENT_PTR);
|
||||
return if (nc != 0) nc else null;
|
||||
}
|
||||
|
||||
fn handlerSlotAddr(net_client: u32, opcode: u16) u32 {
|
||||
return net_client + @as(u32, opcode) * 4 + HANDLER_TABLE_BASE;
|
||||
}
|
||||
|
||||
fn swapHandler(opcode: u16, replacement: u32) bool {
|
||||
const nc = getNetClient() orelse {
|
||||
log.fmt("swapHandler: NetClient is NULL, cannot swap opcode 0x{X}\n", .{opcode});
|
||||
return false;
|
||||
};
|
||||
if (swap_count >= MAX_SWAPS) {
|
||||
log.fmt("swapHandler: swap table full\n", .{});
|
||||
return false;
|
||||
}
|
||||
const slot = handlerSlotAddr(nc, opcode);
|
||||
const original = hook.readMem(u32, slot);
|
||||
if (original == 0) {
|
||||
log.fmt("swapHandler: no handler registered for opcode 0x{X}\n", .{opcode});
|
||||
return false;
|
||||
}
|
||||
|
||||
handler_swaps[swap_count] = .{
|
||||
.opcode = opcode,
|
||||
.original = original,
|
||||
.active = true,
|
||||
};
|
||||
swap_count += 1;
|
||||
|
||||
// Write our replacement into the table (heap memory, writable)
|
||||
@as(*align(1) u32, @ptrFromInt(slot)).* = replacement;
|
||||
log.fmt("swapHandler: opcode 0x{X} swapped (original=0x{X})\n", .{ opcode, original });
|
||||
return true;
|
||||
}
|
||||
|
||||
fn getOriginalHandler(opcode: u16) ?u32 {
|
||||
for (handler_swaps[0..swap_count]) |entry| {
|
||||
if (entry.opcode == opcode and entry.active) return entry.original;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn callOriginalHandler(opcode: u16, unk: u32, opc: u32, unk2: u32, cds: u32) u32 {
|
||||
const original = getOriginalHandler(opcode) orelse return 0;
|
||||
return @call(.auto, @as(*const FastCallPacketHandlerFn, @ptrFromInt(original)), .{ unk, opc, unk2, cds });
|
||||
}
|
||||
|
||||
fn restoreAllHandlers() void {
|
||||
const nc = getNetClient() orelse return;
|
||||
for (handler_swaps[0..swap_count]) |*entry| {
|
||||
if (entry.active) {
|
||||
const slot = handlerSlotAddr(nc, entry.opcode);
|
||||
@as(*align(1) u32, @ptrFromInt(slot)).* = entry.original;
|
||||
entry.active = false;
|
||||
log.fmt("restoreHandler: opcode 0x{X} restored\n", .{entry.opcode});
|
||||
}
|
||||
}
|
||||
swap_count = 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SignalEventParam fire functions — one per format pattern
|
||||
// =============================================================================
|
||||
@@ -406,6 +495,21 @@ fn fireBase(sub: [*:0]const u8, src: [*:0]const u8, src_name: [*:0]const u8, dst
|
||||
@call(.auto, @as(*const F, @ptrFromInt(SIGNAL)), .{ g_event_combat_log, "%s%s%s%s%s", sub, src, src_name, dst, dst_name });
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: InitializeGameEngine (0x401570)
|
||||
// __thiscall(ECX=this), RET 0xC (3 stack params)
|
||||
// Registers all packet handlers. We install table swaps after it returns.
|
||||
// =============================================================================
|
||||
|
||||
const InitGameEngineFn = fn (u32, u32, u32, u32) callconv(hook.cc.thiscall) void;
|
||||
var init_engine_hook: hook.Detour(InitGameEngineFn) = .{};
|
||||
|
||||
fn initGameEngineDetour(this: u32, p1: u32, p2: u32, p3: u32) callconv(hook.cc.thiscall) void {
|
||||
init_engine_hook.callOriginal(.{ this, p1, p2, p3 });
|
||||
// All packet handlers are now registered — install our table swaps
|
||||
installHandlerSwaps();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: FrameScript_CreateEvents (0x703D90)
|
||||
// After the chain runs, write our event into the internal table.
|
||||
@@ -475,10 +579,76 @@ fn createEventsDetour(param1: u32, max_event_id: u32) callconv(hook.cc.fastcall)
|
||||
hook.writeMem(internal_array + slot * 16, std.mem.asBytes(&name_ptr));
|
||||
g_event_combat_log = slot;
|
||||
|
||||
log.fmt("createEventsDetour: COMBAT_LOG_EVENT at slot {d}, capacity={d}\n", .{ slot, capacity });
|
||||
log.fmt("createEventsDetour: COMBAT_LOG_EVENT_UNFILTERED at slot {d}, capacity={d}\n", .{ slot, capacity });
|
||||
}
|
||||
}
|
||||
|
||||
fn installHandlerSwaps() void {
|
||||
if (swap_count > 0) return; // already installed
|
||||
// Packet handler table swaps (no code patching, heap pointer writes only)
|
||||
if (!swapHandler(0x250, @intFromPtr(&spellNonMeleeDmgLogDetour)))
|
||||
log.print("FAILED to swap SPELLNONMELEEDAMAGELOG (0x250)\n")
|
||||
else
|
||||
log.print("Swapped SPELLNONMELEEDAMAGELOG (0x250)\n");
|
||||
|
||||
if (!swapHandler(0x24E, @intFromPtr(&periodicAuraLogDetour)))
|
||||
log.print("FAILED to swap PERIODICAURALOG (0x24E)\n")
|
||||
else
|
||||
log.print("Swapped PERIODICAURALOG (0x24E)\n");
|
||||
|
||||
if (!swapHandler(0x150, @intFromPtr(&healLogDetour)))
|
||||
log.print("FAILED to swap SPELLHEALLOG (0x150)\n")
|
||||
else
|
||||
log.print("Swapped SPELLHEALLOG (0x150)\n");
|
||||
|
||||
if (!swapHandler(0x14A, @intFromPtr(&meleeDispatcherDetour)))
|
||||
log.print("FAILED to swap ATTACKERSTATEUPDATE (0x14A)\n")
|
||||
else
|
||||
log.print("Swapped ATTACKERSTATEUPDATE (0x14A)\n");
|
||||
|
||||
if (!swapHandler(0x1F5, @intFromPtr(&partyKillLogDetour)))
|
||||
log.print("FAILED to swap PARTYKILLLOG (0x1F5)\n")
|
||||
else
|
||||
log.print("Swapped PARTYKILLLOG (0x1F5)\n");
|
||||
|
||||
if (!swapHandler(0x131, @intFromPtr(&spellStartDetour)))
|
||||
log.print("FAILED to swap SPELL_START (0x131)\n")
|
||||
else
|
||||
log.print("Swapped SPELL_START (0x131)\n");
|
||||
|
||||
if (!swapHandler(0x132, @intFromPtr(&spellStartDetour)))
|
||||
log.print("FAILED to swap SPELL_GO (0x132)\n")
|
||||
else
|
||||
log.print("Swapped SPELL_GO (0x132)\n");
|
||||
|
||||
if (!swapHandler(0x130, @intFromPtr(&castResultDetour)))
|
||||
log.print("FAILED to swap CAST_RESULT (0x130)\n")
|
||||
else
|
||||
log.print("Swapped CAST_RESULT (0x130)\n");
|
||||
|
||||
if (!swapHandler(0x24B, @intFromPtr(&spellMissedDetour)))
|
||||
log.print("FAILED to swap SPELLLOGMISS (0x24B)\n")
|
||||
else
|
||||
log.print("Swapped SPELLLOGMISS (0x24B)\n");
|
||||
|
||||
if (!swapHandler(0x24F, @intFromPtr(&damageShieldDetour)))
|
||||
log.print("FAILED to swap SPELLDAMAGESHIELD (0x24F)\n")
|
||||
else
|
||||
log.print("Swapped SPELLDAMAGESHIELD (0x24F)\n");
|
||||
|
||||
if (!swapHandler(0x24C, @intFromPtr(&spellLogExecuteDetour)))
|
||||
log.print("FAILED to swap SPELLLOGEXECUTE (0x24C)\n")
|
||||
else
|
||||
log.print("Swapped SPELLLOGEXECUTE (0x24C)\n");
|
||||
|
||||
if (!swapHandler(0x32F, @intFromPtr(&instaKillDetour)))
|
||||
log.print("FAILED to swap SPELLINSTAKILLLOG (0x32F)\n")
|
||||
else
|
||||
log.print("Swapped SPELLINSTAKILLLOG (0x32F)\n");
|
||||
|
||||
log.fmt("installHandlerSwaps: {d} handlers swapped\n", .{swap_count});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: SpellNonMeleeDmgLogHandler (0x5E85E0)
|
||||
// Packet: SMSG_SPELLNONMELEEDAMAGELOG
|
||||
@@ -487,8 +657,6 @@ fn createEventsDetour(param1: u32, max_event_id: u32) callconv(hook.cc.fastcall)
|
||||
|
||||
const FastCallPacketHandlerFn = fn (u32, u32, u32, u32) callconv(hook.cc.fastcall) u32;
|
||||
|
||||
var spell_dmg_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn spellNonMeleeDmgLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -548,7 +716,7 @@ fn spellNonMeleeDmgLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callcon
|
||||
}
|
||||
}
|
||||
|
||||
return spell_dmg_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
return callOriginalHandler(0x250, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -558,8 +726,6 @@ fn spellNonMeleeDmgLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callcon
|
||||
// SPELL_PERIODIC_DRAIN, SPELL_PERIODIC_LEECH
|
||||
// =============================================================================
|
||||
|
||||
var periodic_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn periodicAuraLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -650,45 +816,44 @@ fn periodicAuraLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(ho
|
||||
}
|
||||
|
||||
cdsSetRead(cds, saved_read);
|
||||
return periodic_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
return callOriginalHandler(0x24E, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: ProcessSpellPowerDrainMessage (0x62C770) — downstream of SMSG_SPELLHEALLOG
|
||||
// __fastcall(ECX=casterGuid_ptr, EDX=targetGuid_ptr, stack: spellId, healAmount, isCrit)
|
||||
// RET 0xC (3 stack params). Ghidra mislabel — actually heal display function.
|
||||
// Hook: SMSG_SPELLHEALLOG (opcode 0x150) — handler table swap
|
||||
// Packet: victimPackGUID, casterPackGUID, uint32 spellId, uint32 healAmount, uint8 isCrit
|
||||
// Fires: SPELL_HEAL
|
||||
// =============================================================================
|
||||
|
||||
const HealDisplayFn = fn (u32, u32, u32, u32, u32) callconv(hook.cc.fastcall) void;
|
||||
|
||||
var heal_hook: hook.Detour(HealDisplayFn) = .{};
|
||||
|
||||
fn healDisplayDetour(caster_ptr: u32, target_ptr: u32, spell_id: u32, heal_amount: u32, is_crit: u32) callconv(hook.cc.fastcall) void {
|
||||
fn healLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
if (caster_ptr != 0 and target_ptr != 0 and spell_id != 0) {
|
||||
const caster_lo = hook.readMem(u32, caster_ptr);
|
||||
const caster_hi = hook.readMem(u32, caster_ptr + 4);
|
||||
const target_lo = hook.readMem(u32, target_ptr);
|
||||
const target_hi = hook.readMem(u32, target_ptr + 4);
|
||||
const caster_guid: u64 = @as(u64, caster_hi) << 32 | @as(u64, caster_lo);
|
||||
const target_guid: u64 = @as(u64, target_hi) << 32 | @as(u64, target_lo);
|
||||
const saved_read = cdsGetRead(cds);
|
||||
|
||||
if (caster_guid != 0 and target_guid != 0 and !isPeriodicLeechSpell(spell_id)) {
|
||||
// Suppress heal for periodic leech spells — already covered by SPELL_PERIODIC_LEECH
|
||||
const src_str = guidToString(caster_guid);
|
||||
const dst_str = guidToString(target_guid);
|
||||
const src_name = wow.getNameByGUID(caster_guid);
|
||||
const dst_name = wow.getNameByGUID(target_guid);
|
||||
const critical: u32 = if (is_crit != 0) 1 else 0;
|
||||
const overheal = computeOverheal(target_guid, heal_amount);
|
||||
// _HEAL: spellId, amount, overheal, absorbed(0), critical
|
||||
fireSpellHeal(SUB_SPELL_HEAL, src_str, src_name, dst_str, dst_name, spell_id, getSpellName(spell_id), getSpellSchool(spell_id), heal_amount, overheal, 0, critical);
|
||||
}
|
||||
const target_guid = cdsGetPackedGuid(cds);
|
||||
const caster_guid = cdsGetPackedGuid(cds);
|
||||
const spell_id = cdsGet(u32, cds);
|
||||
const heal_amount = cdsGet(u32, cds);
|
||||
const is_crit = cdsGet(u8, cds);
|
||||
|
||||
cdsSetRead(cds, saved_read);
|
||||
|
||||
if (target_guid != null and caster_guid != null and spell_id != null and
|
||||
heal_amount != null and is_crit != null and
|
||||
caster_guid.? != 0 and target_guid.? != 0 and !isPeriodicLeechSpell(spell_id.?))
|
||||
{
|
||||
// Suppress heal for periodic leech spells — already covered by SPELL_PERIODIC_LEECH
|
||||
const src_str = guidToString(caster_guid.?);
|
||||
const dst_str = guidToString(target_guid.?);
|
||||
const src_name = wow.getNameByGUID(caster_guid.?);
|
||||
const dst_name = wow.getNameByGUID(target_guid.?);
|
||||
const critical: u32 = if (is_crit.? != 0) 1 else 0;
|
||||
const overheal = computeOverheal(target_guid.?, heal_amount.?);
|
||||
// _HEAL: spellId, amount, overheal, absorbed(0), critical
|
||||
fireSpellHeal(SUB_SPELL_HEAL, src_str, src_name, dst_str, dst_name, spell_id.?, getSpellName(spell_id.?), getSpellSchool(spell_id.?), heal_amount.?, overheal, 0, critical);
|
||||
}
|
||||
|
||||
heal_hook.callOriginal(.{ caster_ptr, target_ptr, spell_id, heal_amount, is_crit });
|
||||
return callOriginalHandler(0x150, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -700,8 +865,6 @@ fn healDisplayDetour(caster_ptr: u32, target_ptr: u32, spell_id: u32, heal_amoun
|
||||
|
||||
const OPCODE_ATTACKERSTATEUPDATE: u32 = 0x14A;
|
||||
|
||||
var melee_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn meleeDispatcherDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -709,7 +872,7 @@ fn meleeDispatcherDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(ho
|
||||
parseMeleePacket(cds);
|
||||
}
|
||||
|
||||
return melee_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
return callOriginalHandler(0x14A, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
fn parseMeleePacket(cds: u32) void {
|
||||
@@ -851,81 +1014,87 @@ fn getActivePlayerGuid() u64 {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: ProcessSpellCombatResult (0x62BAB0) — downstream of SMSG_SPELLLOGMISS
|
||||
// __fastcall(ECX=missType, EDX=spellId, stack: casterGuidLo, casterGuidHi,
|
||||
// targetGuidLo, targetGuidHi, isFromSpellLogMiss)
|
||||
// RET 0x14 (5 stack params)
|
||||
// Hook: SMSG_SPELLLOGMISS (opcode 0x24B) — handler table swap
|
||||
// Packet: uint32 spellId, casterGUID(8 raw), uint8 unk, uint32 targetCount,
|
||||
// [targetGUID(8 raw), uint8 missInfo] x count
|
||||
// Fires: SPELL_MISSED, RANGE_MISSED, SPELL_PERIODIC_MISSED, DAMAGE_SHIELD_MISSED
|
||||
// =============================================================================
|
||||
|
||||
const SpellMissedFn = fn (u32, u32, u32, u32, u32, u32, u32) callconv(hook.cc.fastcall) void;
|
||||
|
||||
var spell_missed_hook: hook.Detour(SpellMissedFn) = .{};
|
||||
|
||||
fn spellMissedDetour(miss_type: u32, spell_id: u32, caster_lo: u32, caster_hi: u32, target_lo: u32, target_hi: u32, is_spell_log_miss: u32) callconv(hook.cc.fastcall) void {
|
||||
fn spellMissedDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
const caster_guid: u64 = @as(u64, caster_hi) << 32 | @as(u64, caster_lo);
|
||||
const target_guid: u64 = @as(u64, target_hi) << 32 | @as(u64, target_lo);
|
||||
const saved_read = cdsGetRead(cds);
|
||||
|
||||
if (caster_guid != 0 and target_guid != 0 and spell_id != 0) {
|
||||
const src_str = guidToString(caster_guid);
|
||||
const dst_str = guidToString(target_guid);
|
||||
const src_name = wow.getNameByGUID(caster_guid);
|
||||
const dst_name = wow.getNameByGUID(target_guid);
|
||||
const miss_str = missInfoToString(@truncate(miss_type));
|
||||
const school = getSpellSchool(spell_id);
|
||||
// Classify miss subevent by spell type
|
||||
const sub = if (spell_id == 75 or spell_id == 5019)
|
||||
const spell_id = cdsGet(u32, cds);
|
||||
const caster_guid = cdsGet(u64, cds);
|
||||
_ = cdsGet(u8, cds); // unknown byte
|
||||
const target_count = cdsGet(u32, cds);
|
||||
|
||||
if (spell_id != null and caster_guid != null and target_count != null and
|
||||
caster_guid.? != 0 and spell_id.? != 0)
|
||||
{
|
||||
const src_str = guidToString(caster_guid.?);
|
||||
const src_name = wow.getNameByGUID(caster_guid.?);
|
||||
const school = getSpellSchool(spell_id.?);
|
||||
const sub = if (spell_id.? == 75 or spell_id.? == 5019)
|
||||
SUB_RANGE_MISSED
|
||||
else if (isDamageShieldSpell(spell_id))
|
||||
else if (isDamageShieldSpell(spell_id.?))
|
||||
SUB_DAMAGE_SHIELD_MISSED
|
||||
else if (isPeriodicSpell(spell_id))
|
||||
else if (isPeriodicSpell(spell_id.?))
|
||||
SUB_SPELL_PERIODIC_MISSED
|
||||
else
|
||||
SUB_SPELL_MISSED;
|
||||
log.fmt("{s}: [{d}]{s} miss={s} logmiss={d}\n", .{ std.mem.span(sub), spell_id, std.mem.span(getSpellName(spell_id)), std.mem.span(miss_str), is_spell_log_miss });
|
||||
fireSpellMissed(sub, src_str, src_name, dst_str, dst_name, spell_id, getSpellName(spell_id), school, miss_str, 0);
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < target_count.?) : (i += 1) {
|
||||
const target_guid = cdsGet(u64, cds) orelse break;
|
||||
const miss_info = cdsGet(u8, cds) orelse break;
|
||||
if (target_guid == 0) continue;
|
||||
|
||||
const dst_str = guidToString(target_guid);
|
||||
const dst_name = wow.getNameByGUID(target_guid);
|
||||
const miss_str = missInfoToString(miss_info);
|
||||
log.fmt("{s}: [{d}]{s} miss={s}\n", .{ std.mem.span(sub), spell_id.?, std.mem.span(getSpellName(spell_id.?)), std.mem.span(miss_str) });
|
||||
fireSpellMissed(sub, src_str, src_name, dst_str, dst_name, spell_id.?, getSpellName(spell_id.?), school, miss_str, 0);
|
||||
}
|
||||
}
|
||||
|
||||
spell_missed_hook.callOriginal(.{ miss_type, spell_id, caster_lo, caster_hi, target_lo, target_hi, is_spell_log_miss });
|
||||
cdsSetRead(cds, saved_read);
|
||||
return callOriginalHandler(0x24B, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: ProcessSpellDrainEffectMessage (0x62CA20) — downstream of SMSG_SPELLDAMAGESHIELD
|
||||
// __fastcall(ECX=victimGuid_ptr, EDX=casterGuid_ptr, stack: damage, school)
|
||||
// RET 0x8 (2 stack params)
|
||||
// Packet: victimGUID(8 raw), attackerGUID(8 raw), uint32 damage, uint32 school
|
||||
// Fires: DAMAGE_SHIELD
|
||||
// =============================================================================
|
||||
|
||||
const DamageShieldFn = fn (u32, u32, u32, u32) callconv(hook.cc.fastcall) void;
|
||||
|
||||
var damage_shield_hook: hook.Detour(DamageShieldFn) = .{};
|
||||
|
||||
fn damageShieldDetour(victim_ptr: u32, caster_ptr: u32, damage: u32, school: u32) callconv(hook.cc.fastcall) void {
|
||||
fn damageShieldDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
if (victim_ptr != 0 and caster_ptr != 0) {
|
||||
const victim_lo = hook.readMem(u32, victim_ptr);
|
||||
const victim_hi = hook.readMem(u32, victim_ptr + 4);
|
||||
const caster_lo = hook.readMem(u32, caster_ptr);
|
||||
const caster_hi = hook.readMem(u32, caster_ptr + 4);
|
||||
const victim_guid: u64 = @as(u64, victim_hi) << 32 | @as(u64, victim_lo);
|
||||
const caster_guid: u64 = @as(u64, caster_hi) << 32 | @as(u64, caster_lo);
|
||||
const saved_read = cdsGetRead(cds);
|
||||
|
||||
if (victim_guid != 0 and caster_guid != 0) {
|
||||
const src_str = guidToString(caster_guid);
|
||||
const dst_str = guidToString(victim_guid);
|
||||
const src_name = wow.getNameByGUID(caster_guid);
|
||||
const dst_name = wow.getNameByGUID(victim_guid);
|
||||
const overkill = computeOverkill(victim_guid, damage);
|
||||
log.fmt("DAMAGE_SHIELD: dmg={d} school={d}\n", .{ damage, school });
|
||||
// No spellId available in SMSG_SPELLDAMAGESHIELD — pass 0
|
||||
fireSpellDamage(SUB_DAMAGE_SHIELD, src_str, src_name, dst_str, dst_name, 0, getSpellName(0), school, damage, overkill, school, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
const victim_guid = cdsGet(u64, cds);
|
||||
const attacker_guid = cdsGet(u64, cds);
|
||||
const damage = cdsGet(u32, cds);
|
||||
const school = cdsGet(u32, cds);
|
||||
|
||||
cdsSetRead(cds, saved_read);
|
||||
|
||||
if (victim_guid != null and attacker_guid != null and damage != null and school != null and
|
||||
victim_guid.? != 0 and attacker_guid.? != 0)
|
||||
{
|
||||
const src_str = guidToString(attacker_guid.?);
|
||||
const dst_str = guidToString(victim_guid.?);
|
||||
const src_name = wow.getNameByGUID(attacker_guid.?);
|
||||
const dst_name = wow.getNameByGUID(victim_guid.?);
|
||||
const overkill = computeOverkill(victim_guid.?, damage.?);
|
||||
log.fmt("DAMAGE_SHIELD: dmg={d} school={d}\n", .{ damage.?, school.? });
|
||||
// No spellId in SMSG_SPELLDAMAGESHIELD packet — pass 0
|
||||
fireSpellDamage(SUB_DAMAGE_SHIELD, src_str, src_name, dst_str, dst_name, 0, getSpellName(0), school.?, damage.?, overkill, school.?, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
damage_shield_hook.callOriginal(.{ victim_ptr, caster_ptr, damage, school });
|
||||
return callOriginalHandler(0x24F, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -1001,35 +1170,177 @@ fn spellInterruptDetour(caster_ptr: u32, target_ptr: u32, interrupted_spell_id:
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: ProcessInstaKillSpellMessage (0x62CBE0) — downstream of SMSG_SPELLINSTAKILLLOG
|
||||
// __fastcall(ECX=casterGuid_ptr, EDX=spellId)
|
||||
// Plain RET (0 stack params)
|
||||
// Fires: SPELL_INSTAKILL
|
||||
// Hook: SMSG_SPELLLOGEXECUTE (opcode 0x24C) — handler table swap
|
||||
// Packet: casterPackGUID, uint32 spellId, uint32 effectCount,
|
||||
// per effect: uint32 effectType, uint32 logCount,
|
||||
// per log entry: varies by effectType (see SpellDefines.h)
|
||||
// Fires: SPELL_INTERRUPT, SPELL_ENERGIZE, SPELL_EXTRA_ATTACKS,
|
||||
// SPELL_SUMMON, SPELL_RESURRECT
|
||||
// Replaces 4 downstream hooks: 0x626A10, 0x62D9F0, 0x62CA00, 0x62ACE0
|
||||
// =============================================================================
|
||||
|
||||
const InstaKillFn = fn (u32, u32) callconv(hook.cc.fastcall) void;
|
||||
// Vanilla spell effect type constants
|
||||
const EFFECT_INSTAKILL: u32 = 1;
|
||||
const EFFECT_POWER_DRAIN: u32 = 8;
|
||||
const EFFECT_HEAL: u32 = 10;
|
||||
const EFFECT_ADD_EXTRA_ATTACKS: u32 = 19;
|
||||
const EFFECT_CREATE_ITEM: u32 = 24;
|
||||
const EFFECT_ENERGIZE: u32 = 30;
|
||||
const EFFECT_DISPEL: u32 = 38;
|
||||
const EFFECT_SUMMON_PET: u32 = 56;
|
||||
const EFFECT_HEAL_MAX_HEALTH: u32 = 67;
|
||||
const EFFECT_INTERRUPT_CAST: u32 = 68;
|
||||
const EFFECT_FEED_PET: u32 = 101;
|
||||
const EFFECT_DURABILITY_DAMAGE: u32 = 111;
|
||||
const EFFECT_RESURRECT_NEW: u32 = 113;
|
||||
|
||||
var instakill_hook: hook.Detour(InstaKillFn) = .{};
|
||||
|
||||
fn instaKillDetour(caster_ptr: u32, spell_id: u32) callconv(hook.cc.fastcall) void {
|
||||
fn spellLogExecuteDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
if (caster_ptr != 0 and spell_id != 0) {
|
||||
const caster_lo = hook.readMem(u32, caster_ptr);
|
||||
const caster_hi = hook.readMem(u32, caster_ptr + 4);
|
||||
const caster_guid: u64 = @as(u64, caster_hi) << 32 | @as(u64, caster_lo);
|
||||
const saved_read = cdsGetRead(cds);
|
||||
|
||||
if (caster_guid != 0) {
|
||||
const src_str = guidToString(caster_guid);
|
||||
const src_name = wow.getNameByGUID(caster_guid);
|
||||
const school = getSpellSchool(spell_id);
|
||||
log.fmt("SPELL_INSTAKILL: [{d}]{s} caster=0x{x}\n", .{ spell_id, std.mem.span(getSpellName(spell_id)), caster_guid });
|
||||
// No victim GUID in SMSG_SPELLINSTAKILLLOG — pass zero GUID
|
||||
fireSpell(SUB_SPELL_INSTAKILL, src_str, src_name, GUID_ZERO, "", spell_id, getSpellName(spell_id), school);
|
||||
const caster_guid = cdsGetPackedGuid(cds);
|
||||
const spell_id = cdsGet(u32, cds);
|
||||
const effect_count = cdsGet(u32, cds);
|
||||
|
||||
if (caster_guid != null and spell_id != null and effect_count != null and
|
||||
caster_guid.? != 0 and spell_id.? != 0)
|
||||
{
|
||||
const src_str = guidToString(caster_guid.?);
|
||||
const src_name = wow.getNameByGUID(caster_guid.?);
|
||||
const spell_school = getSpellSchool(spell_id.?);
|
||||
const spell_name = getSpellName(spell_id.?);
|
||||
|
||||
var eff_i: u32 = 0;
|
||||
while (eff_i < effect_count.?) : (eff_i += 1) {
|
||||
const effect_type = cdsGet(u32, cds) orelse break;
|
||||
const log_count = cdsGet(u32, cds) orelse break;
|
||||
|
||||
var log_i: u32 = 0;
|
||||
while (log_i < log_count) : (log_i += 1) {
|
||||
switch (effect_type) {
|
||||
EFFECT_POWER_DRAIN => {
|
||||
// targetGUID(8), amount(4), powerType(4), multiplier(float 4)
|
||||
const target = cdsGet(u64, cds) orelse break;
|
||||
const amount = cdsGet(u32, cds) orelse break;
|
||||
const power_type = cdsGet(u32, cds) orelse break;
|
||||
_ = cdsGet(u32, cds) orelse break; // multiplier float, skip
|
||||
if (target != 0) {
|
||||
const dst_str = guidToString(target);
|
||||
const dst_name = wow.getNameByGUID(target);
|
||||
log.fmt("SPELL_DRAIN: [{d}]{s} amt={d} power={d}\n", .{ spell_id.?, std.mem.span(spell_name), amount, power_type });
|
||||
fireSpellEnergize(SUB_SPELL_DRAIN, src_str, src_name, dst_str, dst_name, spell_id.?, spell_name, spell_school, amount, power_type);
|
||||
}
|
||||
},
|
||||
EFFECT_ENERGIZE => {
|
||||
// targetGUID(8), amount(4), powerType(4)
|
||||
const target = cdsGet(u64, cds) orelse break;
|
||||
const amount = cdsGet(u32, cds) orelse break;
|
||||
const power_type = cdsGet(u32, cds) orelse break;
|
||||
if (target != 0) {
|
||||
const dst_str = guidToString(target);
|
||||
const dst_name = wow.getNameByGUID(target);
|
||||
log.fmt("SPELL_ENERGIZE: [{d}]{s} amt={d} power={d}\n", .{ spell_id.?, std.mem.span(spell_name), amount, power_type });
|
||||
fireSpellEnergize(SUB_SPELL_ENERGIZE, src_str, src_name, dst_str, dst_name, spell_id.?, spell_name, spell_school, amount, power_type);
|
||||
}
|
||||
},
|
||||
EFFECT_ADD_EXTRA_ATTACKS => {
|
||||
// targetGUID(8), count(4)
|
||||
const target = cdsGet(u64, cds) orelse break;
|
||||
const count = cdsGet(u32, cds) orelse break;
|
||||
_ = target;
|
||||
log.fmt("SPELL_EXTRA_ATTACKS: [{d}]{s} count={d}\n", .{ spell_id.?, std.mem.span(spell_name), count });
|
||||
fireSpellExtraAttacks(src_str, src_name, GUID_ZERO, "", spell_id.?, spell_name, spell_school, count);
|
||||
},
|
||||
EFFECT_INTERRUPT_CAST => {
|
||||
// targetGUID(8), interruptedSpellId(4)
|
||||
const target = cdsGet(u64, cds) orelse break;
|
||||
const interrupted_id = cdsGet(u32, cds) orelse break;
|
||||
if (target != 0 and interrupted_id != 0) {
|
||||
const dst_str = guidToString(target);
|
||||
const dst_name = wow.getNameByGUID(target);
|
||||
const extra_school = getSpellSchool(interrupted_id);
|
||||
log.fmt("SPELL_INTERRUPT: [{d}]{s} interrupted=[{d}]{s}\n", .{
|
||||
spell_id.?, std.mem.span(spell_name), interrupted_id, std.mem.span(getSpellName(interrupted_id)),
|
||||
});
|
||||
// Now we have the interrupting spell ID (spell_id) — previously was 0
|
||||
fireSpellInterrupt(SUB_SPELL_INTERRUPT, src_str, src_name, dst_str, dst_name, spell_id.?, spell_name, spell_school, interrupted_id, getSpellName(interrupted_id), extra_school);
|
||||
}
|
||||
},
|
||||
EFFECT_HEAL, EFFECT_HEAL_MAX_HEALTH => {
|
||||
// targetGUID(8), amount(4), critical(4)
|
||||
_ = cdsGet(u64, cds) orelse break;
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
// Heals from SPELLLOGEXECUTE are handled by SMSG_SPELLHEALLOG — skip here
|
||||
},
|
||||
EFFECT_CREATE_ITEM => {
|
||||
// itemEntry(4)
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
},
|
||||
EFFECT_FEED_PET => {
|
||||
// itemEntry(4)
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
},
|
||||
EFFECT_DURABILITY_DAMAGE => {
|
||||
// targetGUID(8), itemEntry(4), unk(4)
|
||||
_ = cdsGet(u64, cds) orelse break;
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
},
|
||||
else => {
|
||||
// Most other effect types: just targetGUID(8)
|
||||
// This covers INSTAKILL, RESURRECT, DISPEL, SUMMON variants, etc.
|
||||
const target = cdsGet(u64, cds) orelse break;
|
||||
|
||||
if (target != 0) {
|
||||
const dst_str = guidToString(target);
|
||||
const dst_name = wow.getNameByGUID(target);
|
||||
|
||||
if (isSummonEffect(effect_type)) {
|
||||
log.fmt("SPELL_SUMMON: [{d}]{s}\n", .{ spell_id.?, std.mem.span(spell_name) });
|
||||
fireSpell(SUB_SPELL_SUMMON, src_str, src_name, dst_str, dst_name, spell_id.?, spell_name, spell_school);
|
||||
} else if (isResurrectEffect(effect_type)) {
|
||||
log.fmt("SPELL_RESURRECT: [{d}]{s}\n", .{ spell_id.?, std.mem.span(spell_name) });
|
||||
fireSpell(SUB_SPELL_RESURRECT, src_str, src_name, dst_str, dst_name, spell_id.?, spell_name, spell_school);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instakill_hook.callOriginal(.{ caster_ptr, spell_id });
|
||||
cdsSetRead(cds, saved_read);
|
||||
return callOriginalHandler(0x24C, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: SMSG_SPELLINSTAKILLLOG (opcode 0x32F) — handler table swap
|
||||
// Packet: victimGUID(8 raw), uint32 spellId
|
||||
// Fires: SPELL_INSTAKILL
|
||||
// =============================================================================
|
||||
|
||||
fn instaKillDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
const saved_read = cdsGetRead(cds);
|
||||
|
||||
const victim_guid = cdsGet(u64, cds);
|
||||
const spell_id = cdsGet(u32, cds);
|
||||
|
||||
cdsSetRead(cds, saved_read);
|
||||
|
||||
if (victim_guid != null and spell_id != null and victim_guid.? != 0) {
|
||||
const dst_str = guidToString(victim_guid.?);
|
||||
const dst_name = wow.getNameByGUID(victim_guid.?);
|
||||
const school = getSpellSchool(spell_id.?);
|
||||
log.fmt("SPELL_INSTAKILL: [{d}]{s} victim=0x{x}\n", .{ spell_id.?, std.mem.span(getSpellName(spell_id.?)), victim_guid.? });
|
||||
// Caster not in packet — src is unknown
|
||||
fireSpell(SUB_SPELL_INSTAKILL, GUID_ZERO, "", dst_str, dst_name, spell_id.?, getSpellName(spell_id.?), school);
|
||||
}
|
||||
|
||||
return callOriginalHandler(0x32F, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -1038,8 +1349,6 @@ fn instaKillDetour(caster_ptr: u32, spell_id: u32) callconv(hook.cc.fastcall) vo
|
||||
// Fires: PARTY_KILL
|
||||
// =============================================================================
|
||||
|
||||
var party_kill_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn partyKillLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -1059,7 +1368,7 @@ fn partyKillLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.
|
||||
fireBase(SUB_PARTY_KILL, src_str, src_name, dst_str, dst_name);
|
||||
}
|
||||
|
||||
return party_kill_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
return callOriginalHandler(0x1F5, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -1071,8 +1380,6 @@ fn partyKillLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.
|
||||
const OPCODE_SPELL_START: u32 = 0x131;
|
||||
const OPCODE_SPELL_GO: u32 = 0x132;
|
||||
|
||||
var spell_start_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn spellStartDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -1126,7 +1433,8 @@ fn spellStartDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc
|
||||
cdsSetRead(cds, saved_read);
|
||||
}
|
||||
|
||||
return spell_start_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
// Both 0x131 and 0x132 share this handler; callOriginal uses the opcode passed in EDX
|
||||
return callOriginalHandler(@intCast(opcode), unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -1135,8 +1443,6 @@ fn spellStartDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc
|
||||
// Fires: SPELL_CAST_FAILED (when status != 0)
|
||||
// =============================================================================
|
||||
|
||||
var cast_result_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn castResultDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -1158,7 +1464,7 @@ fn castResultDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc
|
||||
}
|
||||
}
|
||||
|
||||
return cast_result_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
return callOriginalHandler(0x130, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -1906,28 +2212,10 @@ pub fn installHooks() void {
|
||||
log.print("Hooked FrameScript_CreateEvents\n");
|
||||
}
|
||||
|
||||
if (spell_dmg_hook.attach(0x5E85E0, &spellNonMeleeDmgLogDetour) != .ok) {
|
||||
log.print("FAILED to hook SpellNonMeleeDmgLogHandler\n");
|
||||
if (init_engine_hook.attach(0x401570, &initGameEngineDetour) != .ok) {
|
||||
log.print("FAILED to hook InitializeGameEngine\n");
|
||||
} else {
|
||||
log.print("Hooked SpellNonMeleeDmgLogHandler\n");
|
||||
}
|
||||
|
||||
if (periodic_hook.attach(0x626DD0, &periodicAuraLogDetour) != .ok) {
|
||||
log.print("FAILED to hook PeriodicAuraLogHandler\n");
|
||||
} else {
|
||||
log.print("Hooked PeriodicAuraLogHandler\n");
|
||||
}
|
||||
|
||||
if (heal_hook.attach(0x62C770, &healDisplayDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessSpellPowerDrainMessage\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessSpellPowerDrainMessage (SPELL_HEAL)\n");
|
||||
}
|
||||
|
||||
if (melee_hook.attach(0x6255B0, &meleeDispatcherDetour) != .ok) {
|
||||
log.print("FAILED to hook MeleeDispatcher\n");
|
||||
} else {
|
||||
log.print("Hooked MeleeDispatcher\n");
|
||||
log.print("Hooked InitializeGameEngine (handler table swaps after return)\n");
|
||||
}
|
||||
|
||||
if (env_dmg_hook.attach(0x62AAC0, &envDamageDetour) != .ok) {
|
||||
@@ -1936,23 +2224,6 @@ pub fn installHooks() void {
|
||||
log.print("Hooked ProcessEnvironmentalDamage\n");
|
||||
}
|
||||
|
||||
if (party_kill_hook.attach(0x628890, &partyKillLogDetour) != .ok) {
|
||||
log.print("FAILED to hook PartyKillLogHandler\n");
|
||||
} else {
|
||||
log.print("Hooked PartyKillLogHandler\n");
|
||||
}
|
||||
|
||||
if (spell_start_hook.attach(0x6E7640, &spellStartDetour) != .ok) {
|
||||
log.print("FAILED to hook SpellStartHandler\n");
|
||||
} else {
|
||||
log.print("Hooked SpellStartHandler\n");
|
||||
}
|
||||
|
||||
if (cast_result_hook.attach(0x6E7330, &castResultDetour) != .ok) {
|
||||
log.print("FAILED to hook CastResultHandler\n");
|
||||
} else {
|
||||
log.print("Hooked CastResultHandler (SPELL_CAST_FAILED self)\n");
|
||||
}
|
||||
|
||||
if (spell_failed_other_hook.attach(0x6E75F0, &spellFailedOtherDetour) != .ok) {
|
||||
log.print("FAILED to hook HandleSpellInterruptUpdate\n");
|
||||
@@ -1960,35 +2231,6 @@ pub fn installHooks() void {
|
||||
log.print("Hooked HandleSpellInterruptUpdate (SPELL_CAST_FAILED others)\n");
|
||||
}
|
||||
|
||||
if (spell_missed_hook.attach(0x62BAB0, &spellMissedDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessSpellCombatResult\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessSpellCombatResult (SPELL_MISSED)\n");
|
||||
}
|
||||
|
||||
if (damage_shield_hook.attach(0x62CA20, &damageShieldDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessSpellDrainEffectMessage\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessSpellDrainEffectMessage (DAMAGE_SHIELD)\n");
|
||||
}
|
||||
|
||||
if (energize_hook.attach(0x62CA00, &energizeDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessStandardPowerGainMessage\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessStandardPowerGainMessage (SPELL_ENERGIZE)\n");
|
||||
}
|
||||
|
||||
if (spell_interrupt_hook.attach(0x626A10, &spellInterruptDetour) != .ok) {
|
||||
log.print("FAILED to hook DisplaySpellInterruptMessage\n");
|
||||
} else {
|
||||
log.print("Hooked DisplaySpellInterruptMessage (SPELL_INTERRUPT)\n");
|
||||
}
|
||||
|
||||
if (instakill_hook.attach(0x62CBE0, &instaKillDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessInstaKillSpellMessage\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessInstaKillSpellMessage (SPELL_INSTAKILL)\n");
|
||||
}
|
||||
|
||||
if (unit_death_hook.attach(0x605860, &unitDeathDetour) != .ok) {
|
||||
log.print("FAILED to hook HandleUnitDeath\n");
|
||||
@@ -2014,11 +2256,7 @@ pub fn installHooks() void {
|
||||
log.print("Hooked ValidateSpellSlot (SPELL_AURA_*_DOSE)\n");
|
||||
}
|
||||
|
||||
if (extra_attacks_hook.attach(0x62D9F0, &extraAttacksDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessExtraAttacksSpellMessage\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessExtraAttacksSpellMessage (SPELL_EXTRA_ATTACKS)\n");
|
||||
}
|
||||
// extra_attacks_hook — now handled by SPELLLOGEXECUTE table swap (0x24C)
|
||||
|
||||
if (dispel_hook.attach(0x62D480, &dispelDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessAuraDispelMessage\n");
|
||||
@@ -2026,11 +2264,7 @@ pub fn installHooks() void {
|
||||
log.print("Hooked ProcessAuraDispelMessage (SPELL_DISPEL)\n");
|
||||
}
|
||||
|
||||
if (spell_effect_hook.attach(0x62ACE0, &spellEffectDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessSpellEffect\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessSpellEffect (SPELL_SUMMON/RESURRECT/ENERGIZE)\n");
|
||||
}
|
||||
// spell_effect_hook — now handled by SPELLLOGEXECUTE table swap (0x24C)
|
||||
|
||||
if (aura_duration_hook.attach(0x4E4390, &auraDurationDetour) != .ok) {
|
||||
log.print("FAILED to hook SetActionCooldownTimer\n");
|
||||
@@ -2047,28 +2281,21 @@ pub fn installHooks() void {
|
||||
|
||||
pub fn removeHooks() void {
|
||||
if (g_is_hook_owner) {
|
||||
// Restore handler table swaps (no code to unpatch)
|
||||
restoreAllHandlers();
|
||||
// Detach remaining JMP-patching detours (non-packet hooks)
|
||||
spell_failed_other_hook.detach();
|
||||
dispel_failed_hook.detach();
|
||||
aura_duration_hook.detach();
|
||||
spell_effect_hook.detach();
|
||||
dispel_hook.detach();
|
||||
extra_attacks_hook.detach();
|
||||
aura_dose_hook.detach();
|
||||
aura_applied_hook.detach();
|
||||
aura_removed_hook.detach();
|
||||
unit_death_hook.detach();
|
||||
instakill_hook.detach();
|
||||
spell_interrupt_hook.detach();
|
||||
damage_shield_hook.detach();
|
||||
spell_missed_hook.detach();
|
||||
cast_result_hook.detach();
|
||||
spell_start_hook.detach();
|
||||
party_kill_hook.detach();
|
||||
// spell_interrupt_hook — now in SPELLLOGEXECUTE table swap
|
||||
env_dmg_hook.detach();
|
||||
melee_hook.detach();
|
||||
heal_hook.detach();
|
||||
periodic_hook.detach();
|
||||
spell_dmg_hook.detach();
|
||||
// energize_hook — now in SPELLLOGEXECUTE table swap
|
||||
init_engine_hook.detach();
|
||||
create_events_hook.detach();
|
||||
resize_events_hook.detach();
|
||||
log.close();
|
||||
|
||||
+14
@@ -144,3 +144,17 @@ pub fn checknumber(L: State, index: i32) f64 {
|
||||
const f: *const fn (State, i32) callconv(hook.cc.fastcall) f64 = @ptrFromInt(0x6F4C80);
|
||||
return f(L, index);
|
||||
}
|
||||
|
||||
// Lua 5.0 pseudo-index for globals table
|
||||
pub const GLOBALS_INDEX: i32 = -10001;
|
||||
|
||||
pub fn setglobal(L: State, name: [*:0]const u8) void {
|
||||
pushstring(L, name);
|
||||
insert(L, -2); // swap: value is now at -1, name at -2 → after insert: name at -2, value at -1
|
||||
settable(L, GLOBALS_INDEX);
|
||||
}
|
||||
|
||||
pub fn getglobal(L: State, name: [*:0]const u8) void {
|
||||
pushstring(L, name);
|
||||
gettable(L, GLOBALS_INDEX);
|
||||
}
|
||||
|
||||
+88
-6
@@ -137,6 +137,74 @@ fn registerLuaFunctions() void {
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Module version registry — GetWeirdUtilsVersion(name?) from Lua
|
||||
// =============================================================================
|
||||
//
|
||||
// Populates a global "WeirdUtils" table with { module_name = "version", ... }
|
||||
// for each enabled module. If the table already exists (from another DLL),
|
||||
// new entries are merged in additively.
|
||||
//
|
||||
// GetWeirdUtilsVersion() → returns the full WeirdUtils table
|
||||
// GetWeirdUtilsVersion("name") → returns version string for that module, or nil
|
||||
|
||||
const all_module_names = @import("build_options").all_module_names;
|
||||
const all_module_versions = @import("build_options").all_module_versions;
|
||||
|
||||
fn registerModuleVersions() void {
|
||||
const L = lua.getContext();
|
||||
|
||||
// Get or create the WeirdUtils global table
|
||||
lua.getglobal(L, "WeirdUtils");
|
||||
if (lua.typeOf(L, -1) != 5) { // 5 = LUA_TTABLE
|
||||
lua.pop(L, 1);
|
||||
lua.newtable(L);
|
||||
}
|
||||
|
||||
// For each enabled module, set WeirdUtils[name] = version
|
||||
inline for (all_module_names, 0..) |name, i| {
|
||||
const enabled = @field(@import("build_options"), "enable_" ++ name);
|
||||
if (enabled) {
|
||||
lua.pushstring(L, @ptrCast(name.ptr));
|
||||
lua.pushstring(L, @ptrCast(all_module_versions[i].ptr));
|
||||
lua.settable(L, -3);
|
||||
}
|
||||
}
|
||||
|
||||
// Set as global
|
||||
lua.setglobal(L, "WeirdUtils");
|
||||
|
||||
// Register query function
|
||||
registerFunction("GetWeirdUtilsVersion", @intFromPtr(&luaGetWeirdUtilsVersion));
|
||||
}
|
||||
|
||||
fn luaGetWeirdUtilsVersion(L_ecx: usize) callconv(hook.cc.fastcall) u32 {
|
||||
const L: lua.State = @ptrFromInt(L_ecx);
|
||||
const nargs = lua.gettop(L);
|
||||
|
||||
if (nargs >= 1 and lua.isstring(L, 1)) {
|
||||
// GetWeirdUtilsVersion("name") → return version or nil
|
||||
const name = lua.tostring(L, 1) orelse {
|
||||
lua.pushnil(L);
|
||||
return 1;
|
||||
};
|
||||
lua.getglobal(L, "WeirdUtils");
|
||||
if (lua.typeOf(L, -1) != 5) {
|
||||
lua.pop(L, 1);
|
||||
lua.pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
lua.pushstring(L, name);
|
||||
lua.gettable(L, -2);
|
||||
lua.remove(L, -2); // remove WeirdUtils table, leave value
|
||||
return 1;
|
||||
}
|
||||
|
||||
// GetWeirdUtilsVersion() → return the whole table
|
||||
lua.getglobal(L, "WeirdUtils");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Embedded addon files
|
||||
// =============================================================================
|
||||
@@ -661,11 +729,22 @@ fn removeFileHooks() void {
|
||||
// Hook: LoadScriptFunctions (0x490250)
|
||||
// =============================================================================
|
||||
|
||||
var lsf_hook: hook.Detour(fn () callconv(hook.cc.stdcall) void) = .{};
|
||||
var register_commands_hook: hook.Detour(fn () callconv(hook.cc.stdcall) void) = .{};
|
||||
var glue_commands_hook: hook.Detour(fn () callconv(hook.cc.stdcall) void) = .{};
|
||||
|
||||
fn loadScriptFunctionsDetour() callconv(hook.cc.stdcall) void {
|
||||
lsf_hook.callOriginal(.{});
|
||||
/// Hook for Player_LoadScriptFunctions (0x490250).
|
||||
/// Fires after login/reload — registers gameplay Lua functions + version table.
|
||||
fn registerAllSystemCommandsDetour() callconv(hook.cc.stdcall) void {
|
||||
register_commands_hook.callOriginal(.{});
|
||||
registerLuaFunctions();
|
||||
registerModuleVersions();
|
||||
}
|
||||
|
||||
/// Hook for Glue_LoadScriptFunctions (0x46ABB0).
|
||||
/// Fires at the login/glue screen — registers version table so addons can query early.
|
||||
fn glueLoadScriptFunctionsDetour() callconv(hook.cc.stdcall) void {
|
||||
glue_commands_hook.callOriginal(.{});
|
||||
registerModuleVersions();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -793,7 +872,8 @@ fn install() void {
|
||||
_ = protection_hook.attach(0x42a320, &luaProtectionDetour);
|
||||
installFileHooks();
|
||||
_ = file_hook.attach(0x648620, &loadFileDetour);
|
||||
_ = lsf_hook.attach(0x490250, &loadScriptFunctionsDetour);
|
||||
_ = register_commands_hook.attach(0x490250, ®isterAllSystemCommandsDetour);
|
||||
_ = glue_commands_hook.attach(0x46ABB0, &glueLoadScriptFunctionsDetour);
|
||||
|
||||
inline for (modules) |m| {
|
||||
if (m.install) |inst| inst();
|
||||
@@ -822,7 +902,8 @@ fn uninstall() void {
|
||||
}
|
||||
|
||||
addons.uninstall();
|
||||
lsf_hook.detach();
|
||||
register_commands_hook.detach();
|
||||
glue_commands_hook.detach();
|
||||
file_hook.detach();
|
||||
removeFileHooks();
|
||||
protection_hook.detach();
|
||||
@@ -900,7 +981,8 @@ fn disableAll() callconv(.c) i32 {
|
||||
logout_hook.detach();
|
||||
engine_init_hook.detach();
|
||||
addons.uninstall();
|
||||
lsf_hook.detach();
|
||||
register_commands_hook.detach();
|
||||
glue_commands_hook.detach();
|
||||
file_hook.detach();
|
||||
removeFileHooks();
|
||||
protection_hook.detach();
|
||||
|
||||
Reference in New Issue
Block a user