Files
coa-decursive/Decursive/Dcr_LiveList.lua
T
florian.berthold 8ac1651db0 coa: sanitize debuff icon path before SetTexture
Ascension has been shipping auras whose UnitAura()-returned icon path
is nil or unresolvable on the local client. Decursive's LiveList fed
it straight into Texture:SetTexture(), and on the 3.3.5 engine that
crashes the renderer downstream — #132 ACCESS_VIOLATION near
Ascension.exe text + null+offset (texture-loader struct left
half-initialized). Confirmed across multiple recent DLL hashes
(0baf62b, 6a3088c, 7bba9c8, 502ca3c), recurring fault @ 0x00749EEB.

Fix: when Debuff.Texture isn't a non-empty string, fall back to
INV_Misc_QuestionMark and log the offender once (via AddDebugText so
it lands in Logs/Trace.txt) for future identification of the bad aura.

Bumped toc to Asc-1.1.3-coa.
2026-05-20 00:34:21 +02:00

591 lines
22 KiB
Lua

--[[
This file is part of Decursive.
Decursive (v 2.5.1-6-gd3885c5) add-on for World of Warcraft UI
Copyright (C) 2006-2007-2008-2009 John Wellesz (archarodim AT teaser.fr) ( http://www.2072productions.com/to/decursive.php )
Starting from 2009-10-31 and until said otherwise by its author, Decursive
is no longer free software, all rights are reserved to its author (John Wellesz).
The only official and allowed distribution means are www.2072productions.com, www.wowace.com and curse.com.
To distribute Decursive through other means a special authorization is required.
Decursive is inspired from the original "Decursive v1.9.4" by Quu.
The original "Decursive 1.9.4" is in public domain ( www.quutar.com )
Decursive is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
--]]
-------------------------------------------------------------------------------
local addonName, T = ...;
-- big ugly scary fatal error message display function {{{
if not T._FatalError then
-- the beautiful error popup : {{{ -
StaticPopupDialogs["DECURSIVE_ERROR_FRAME"] = {
text = "|cFFFF0000Decursive Error:|r\n%s",
button1 = "OK",
OnAccept = function()
return false;
end,
timeout = 0,
whileDead = 1,
hideOnEscape = 1,
showAlert = 1,
}; -- }}}
T._FatalError = function (TheError) StaticPopup_Show ("DECURSIVE_ERROR_FRAME", TheError); end
end
-- }}}
if not T._LoadedFiles or not T._LoadedFiles["Dcr_DebuffsFrame.xml"] or not T._LoadedFiles["Dcr_DebuffsFrame.lua"] then
if not DecursiveInstallCorrupted then T._FatalError("Decursive installation is corrupted! (Dcr_DebuffsFrame.xml or Dcr_DebuffsFrame.lua not loaded)"); end;
DecursiveInstallCorrupted = true;
return;
end
local D = Dcr;
--D:SetDateAndRevision("$Date: 2008-09-16 00:25:13 +0200 (mar., 16 sept. 2008) $", "$Revision: 81755 $");
local L = D.L;
local LC = D.LC;
local DC = DcrC;
local DS = DC.DS;
--D.LiveList = OOP.Class();
-- http://www.lua.org/pil/13.4.1.html
-- define the namespace
D.LiveList = {};
local LiveList = D.LiveList;
-- a prototype for LiveList objects, empty, defaults are defined in the :New for better performances
LiveList.prototype = {};
LiveList.metatable ={ __index = LiveList.prototype };
function LiveList:new(...)
local instance = setmetatable({}, self.metatable);
instance:init(...);
return instance;
end
local MicroUnitF = D.MicroUnitF;
LiveList.ExistingPerID = {};
LiveList.Number = 0;
LiveList.NumberShown = 0;
D.ForLLDebuffedUnitsNum = 0;
-- temporary variables often used in function
local Debuff, Debuffs, IsCharmed, MF, i, Index, RangeStatus;
-- local shortcuts to often called global functions
local pairs = _G.pairs;
local ipairs = _G.ipairs;
local next = _G.next;
local select = _G.select;
local unpack = _G.unpack;
local table = _G.table;
local UnitExists = _G.UnitExists;
local IsSpellInRange = _G.IsSpellInRange;
local UnitClass = _G.UnitClass;
local UnitIsFriend = _G.UnitIsFriend;
local UnitGUID = _G.UnitGUID;
local floor = _G.math.floor;
local str_upper = _G.string.upper;
local GetRaidTargetIndex= _G.GetRaidTargetIndex;
-- defines what is printed when the object is read as a string
function LiveList:ToString() -- {{{
return "Decursive Live-List object";
end -- }}}
-- The Factory for LiveList objects
function LiveList:Create() -- {{{
if self.Number + 1 > D.profile.Amount_Of_Afflicted then
return false;
end
self.Number = self.Number + 1;
self.ExistingPerID[self.Number] = self:new(DcrLiveList, self.Number);
return self.ExistingPerID[self.Number];
end -- }}}
function LiveList:DisplayItem (ID, UnitID, Debuff) -- {{{
--D:Debug("(LiveList) Displaying LVItem %d for UnitID %s", ID, UnitID);
local LVItem = false;
if ID > self.Number + 1 then
return error(("LiveList:DisplayItem: bad argument #1 'ID (= %d)' must be < LiveList.Number + 1 (LiveList.Number = %d)"):format(ID, self.Number),2);
end
if not self.ExistingPerID[ID] then
LVItem=self:Create();
else
LVItem = self.ExistingPerID[ID];
end
if not LVItem then
return false;
end
if not Debuff then
Debuff = D.ManagedDebuffUnitCache[UnitID][1];
end
LVItem:SetDebuff(UnitID, Debuff, nil);
--D:Debug("XXXX => Updating ll item %d for %s", ID, UnitID);
if not LVItem.IsShown then
--D:Debug("(LiveList) Showing LVItem %d", ID);
LVItem.Frame:Show();
self.NumberShown = self.NumberShown + 1;
LVItem.IsShown = true;
end
end -- }}}
function LiveList:RestAllPosition () -- {{{
for _, LVitem in ipairs(self.ExistingPerID) do
LVitem.Frame:ClearAllPoints();
LVitem.Frame:SetPoint(LVitem:GiveAnchor());
end
end -- }}}
function LiveList.prototype:GiveAnchor() -- {{{
local ItemHeight = self.Frame:GetHeight();
if D.profile.ReverseLiveDisplay then
end
if self.ID == 1 then
if D.profile.ReverseLiveDisplay then
return "BOTTOMLEFT", DecursiveMainBar, "BOTTOMLEFT", 5, -1 * (ItemHeight + 1) * D.profile.Amount_Of_Afflicted;
else
return "TOPLEFT", DecursiveMainBar, "BOTTOMLEFT", 5, 0;
end
else
if D.profile.ReverseLiveDisplay then
return "BOTTOMLEFT", LiveList.ExistingPerID[self.ID - 1].Frame, "TOPLEFT", 0, 1;
else
return "TOPLEFT", LiveList.ExistingPerID[self.ID - 1].Frame, "BOTTOMLEFT", 0, -1;
end
end
end -- }}}
function LiveList.prototype:init(Container,ID) -- {{{
--LiveList.super.prototype.init(self); -- needed
D:Debug("(LiveList) Initializing LiveList object '%s'", ID);
--ObjectRelated
self.ID = ID;
self.IsShown = false;
self.Parent = Container;
--Debuff info
self.UnitID = false;
self.UnitName = false;
self.RaidTargetIndex = false;
self.PrevUnitName = false;
self.PrevUnitID = false;
self.PrevRaidTargetIndex= false;
self.UnitClass = false;
self.Debuff = {};
self.PrevDebuffIndex = false;
self.PrevDebuffName = false;
self.PrevDebuffTypeName = false;
self.PrevDebuffApplicaton = false;
self.PrevDebuffTexture = false;
self.IsCharmed = false;
self.PrevIsCharmed = false;
self.Alpha = false;
-- Create the frame
self.Frame = CreateFrame ("Button", "DcrLiveListItem"..ID, self.Parent, "DcrLVItemTemplate");
-- Set some basic properties
self.Frame:SetFrameStrata("LOW");
self.Frame:RegisterForClicks("AnyDown");
-- Set the anchor of this item
self.Frame:SetPoint(self:GiveAnchor());
-- create the background
self.BackGroundTexture = self.Frame:CreateTexture("DcrLiveListItem"..ID.."BackTexture", "BACKGROUND", "DcrLVBackgroundTemplate");
-- Create the Icon Texture
self.IconTexture = self.Frame:CreateTexture("DcrLiveListItem"..ID.."Icon", "ARTWORK", "DcrLVIconTemplate");
-- Create the Debuff application count font string
self.DebuffAppsFontString = self.Frame:CreateFontString("DcrLiveListItem"..ID.."Count", "OVERLAY", "DcrLLAfflictionCountFont");
-- Create the character name Fontstring
self.UnitNameFontString = self.Frame:CreateFontString("DcrLiveListItem"..ID.."UnitName", "OVERLAY", "DcrLLUnitNameFont");
-- Create the unitID Fontstring
self.UnitIDFontString = self.Frame:CreateFontString("DcrLiveListItem"..ID.."UnitID", "OVERLAY", "DcrLLUnitIDFont");
--self.UnitIDFontString:SetHeight(3);
-- Create the debuff type fontstring
self.DebuffTypeFontString = self.Frame:CreateFontString("DcrLiveListItem"..ID.."Type", "OVERLAY", "DcrLLDebuffTypeFont");
-- Create the Raid Target Icon Texture
self.RaidIconTexture = self.Frame:CreateTexture("DcrLiveListItem"..ID.."RaidIcon", "ARTWORK", "DcrLVRaidIconTemplate");
-- Create the debuff name fontstring
self.DebuffNameFontString = self.Frame:CreateFontString("DcrLiveListItem"..ID.."Name", "OVERLAY", "DcrLLDebuffNameFont");
-- a reference to this object
self.Frame.Object = self;
self.Frame:Show();
end -- }}}
function LiveList.prototype:SetDebuff(UnitID, Debuff, IsCharmed) -- {{{
self.UnitID = UnitID;
self.UnitName = D:PetUnitName(UnitID, true);
self.Debuff = Debuff;
self.IsCharmed = IsCharmed;
self.RaidTargetIndex = GetRaidTargetIndex(UnitID);
if D.profile.LiveListAlpha ~= self.Alpha then
self.Frame:SetAlpha(D.profile.LiveListAlpha);
self.Alpha = D.profile.LiveListAlpha;
end
-- Set the graphical elements to the right values
-- Icon
-- CoA: Ascension sometimes ships auras whose icon path is missing/invalid on
-- the local client; passing those into SetTexture has crashed the engine
-- (#132 ACCESS_VIOLATION, fault inside Ascension.exe texture loader).
-- Fall back to the question-mark icon and record the offender once so it
-- shows up in Logs/Trace.txt.
if self.PrevDebuffTexture ~= Debuff.Texture then
local tex = Debuff.Texture;
if type(tex) ~= "string" or tex == "" then
if not D.coa_LoggedBadTextures then D.coa_LoggedBadTextures = {}; end
local key = tostring(tex);
if not D.coa_LoggedBadTextures[key] then
D.coa_LoggedBadTextures[key] = true;
D:AddDebugText("CoA: bad debuff icon, falling back. Name=", Debuff.Name, "Type=", Debuff.TypeName, "raw=", tex);
end
tex = "Interface\\Icons\\INV_Misc_QuestionMark";
end
self.IconTexture:SetTexture(tex);
self.PrevDebuffTexture = Debuff.Texture;
end
-- Raid Icon
if self.PrevRaidTargetIndex ~= self.RaidTargetIndex then
self.RaidIconTexture:SetTexture(self.RaidTargetIndex and DC.RAID_ICON_TEXTURE_LIST[self.RaidTargetIndex] or nil);
self.PrevRaidTargetIndex = self.RaidTargetIndex;
end
-- Applications count
if self.PrevDebuffApplicaton ~= Debuff.Applications then
if (Debuff.Applications > 1) then
self.DebuffAppsFontString:SetText(Debuff.Applications);
self.PrevDebuffApplicaton = Debuff.Applications;
else
self.DebuffAppsFontString:SetText(" ");
self.PrevDebuffApplicaton = " ";
end
end
-- Unit Name
if self.PrevUnitName ~= self.UnitName then
self.UnitClass = (select(2, UnitClass(UnitID)));
self.UnitNameFontString:SetText(self.UnitName);
if self.UnitClass then
self.UnitNameFontString:SetTextColor(unpack(DC.ClassesColors[self.UnitClass]));
end
self.PrevUnitName = self.UnitName;
--D:Debug("(LiveList) Updating %d with %s", self.ID, UnitID);
end
-- Unit ID
if self.PrevUnitID ~= UnitID then
self.UnitIDFontString:SetText("( "..UnitID.." )");
self.PrevUnitID = UnitID;
end
-- Debuff Type Name
if self.PrevDebuffTypeName ~= Debuff.TypeName then
if Debuff.Type then
self.DebuffTypeFontString:SetText(D:ColorText(L[str_upper(Debuff.TypeName)], "FF" .. DC.TypeColors[Debuff.Type] ));
--self.DebuffTypeFontString:SetTextColor(DC.TypeColors[Debuff.Type]);
else
self.DebuffTypeFontString:SetText("Unknown");
end
self.PrevDebuffTypeName = Debuff.TypeName;
end
-- Debuff Name
if self.PrevDebuffName ~= Debuff.Name then
self.DebuffNameFontString:SetText(Debuff.Name);
self.PrevDebuffName = Debuff.Name;
end
end -- }}}
function LiveList:GetDebuff(UnitID) -- {{{
-- (note that this function is only called for the mouseover and target if the MUFs are active)
--D:Debug("(LiveList) Getting Debuff for ", UnitID);
if (UnitID == "target" or UnitID == "mouseover") and not UnitIsFriend(UnitID, "player") then
if D.ManagedDebuffUnitCache[UnitID] and D.ManagedDebuffUnitCache[UnitID][1] and D.ManagedDebuffUnitCache[UnitID][1].Type then
D.ManagedDebuffUnitCache[UnitID][1].Type = false; -- clear target/mouseover debuff
D.UnitDebuffed[UnitID] = false; -- XXX changed from 'target' to UnitID on 2010-06-08
end
--D:Debug("(LiveList) GetDebuff() |cFF00DDDDcanceled|r, unit %s is hostile or gone.", UnitID);
return false;
end
-- decrease the total debuff number if the MUFs system isn't already doing it and if it's not the mouseover or target unit
if not D.profile.ShowDebuffsFrame and D.UnitDebuffed[UnitID] and UnitID ~= "mouseover" and UnitID ~= "target" then
D.ForLLDebuffedUnitsNum = D.ForLLDebuffedUnitsNum - 1;
end
-- Get the unit Debuffs
if not D.profile.ShowDebuffsFrame or not MicroUnitF.UnitToMUF[UnitID] or UnitID == "mouseover" or UnitID == "target" then
Debuffs, IsCharmed = D:UnitCurableDebuffs(UnitID);
--Debuffs, IsCharmed = D:UnitCurableDebuffs(UnitID, true);
else -- The MUFs are active and Unit is not mouseover and is not target
MF = MicroUnitF.UnitToMUF[UnitID];
if MF then
Debuffs = MF.Debuffs;
--[=[
else -- (ticket #6)
D:AddDebugText("Sanity check failed in LiveList:GetDebuff() no MUF for unit", UnitID, "MUFs are", D.profile.ShowDebuffsFrame, "MUFnum:", MicroUnitF.Number, "MUFshown:", MicroUnitF.UnitShown, "UnitNum:", D.Status.UnitNum, "UnitExists:", UnitExists(UnitID), "Auto MUF show/hide:", D.profile.AutoHideDebuffsFrame, "InCombatLockdown():", InCombatLockdown());
D:AddDebugText("Stack:\n", debugstack(2));
--]=]
end
end
if (Debuffs and Debuffs[1] and Debuffs[1].Type) then -- there is a Debuff
D.UnitDebuffed[UnitID] = true; -- register that this unit is debuffed
-- increase the total debuff number
if not D.profile.ShowDebuffsFrame and UnitID ~= "mouseover" and UnitID ~= "target" then
D.ForLLDebuffedUnitsNum = D.ForLLDebuffedUnitsNum + 1;
end
else
D.UnitDebuffed[UnitID] = false; -- unregister this unit
end
return D.UnitDebuffed[UnitID];
end -- }}}
function LiveList:DelayedGetDebuff(UnitID) -- {{{
if not D:DelayedCallExixts("Dcr_GetDebuff"..UnitID) then
D.DebuffUpdateRequest = D.DebuffUpdateRequest + 1;
D:Debug("LiveList: GetDebuff scheduled for, ", UnitID);
D:ScheduleDelayedCall("Dcr_GetDebuff"..UnitID, self.GetDebuff, (D.profile.ScanTime / 2) * (1 + floor(D.DebuffUpdateRequest / 7.5)), self, UnitID);
end
end -- }}}
local IndexOffset = 0; -- used when target and/or mouseover are found
local DebuffedUnitsNumber = 0;
local _;
function LiveList:Update_Display() -- {{{
if not D.DcrFullyInitialized then
return;
end
-- Update the unit array
--[[
if (D.Groups_datas_are_invalid) then
D:GetUnitArray();
end
--]]
Index = 0;
if D.profile.ShowDebuffsFrame and D.profile.LV_OnlyInRange then -- The MUFs are here and we test for range
DebuffedUnitsNumber = MicroUnitF.UnitsDebuffedInRange;
else -- the MUFs are not here or we don't test for range
DebuffedUnitsNumber = D.ForLLDebuffedUnitsNum;
end
-- Check the units in order of importance:
-- First the Target
if D.Status.TargetExists and not D.Status.Unit_Array_GUIDToUnit[UnitGUID("target")] and self:GetDebuff("target") then -- TargetExists implies that the unit is a friend
Index = Index + 1;
self:DisplayItem(Index, "target");
--D:Debug("frenetic target update");
DebuffedUnitsNumber = DebuffedUnitsNumber + 1;
if not D.Status.SoundPlayed then
D:PlaySound ("target", "LV target" );
end
end
-- Then the MouseOver
if not D.Status.MouseOveringMUF and D.UnitDebuffed["mouseover"] and not D.Status.Unit_Array_GUIDToUnit[UnitGUID("mouseover")] and self:GetDebuff("mouseover") then -- this won't catch new debuff if all debuffs disappeard while overing the unit...
Index = Index + 1;
self:DisplayItem(Index, "mouseover");
--D:Debug("frenetic mouseover update");
DebuffedUnitsNumber = DebuffedUnitsNumber + 1;
if not D.Status.SoundPlayed then
D:PlaySound ("mouseover", "LV mouseover" );
end
end
-- the sound played status is reset here because the live list is able to display target and mouseover units and far away ones...
if DebuffedUnitsNumber == 0 then
D.Status.SoundPlayed = false;
end
IndexOffset = Index;
-- Then continue with all the remaining units if at least one of them is debuffed
-- We need this loop because:
-- 1, we have to show an ordered list (always true)
-- 2, we want to test if the unit is in spell range (only if the option is active and the MUFs hidden)
-- There is no event to do the last and a not simple table.sort() would be needed for the first...
if DebuffedUnitsNumber > 0 and Index < D.profile.Amount_Of_Afflicted then
for _, UnitID in ipairs(D.Status.Unit_Array) do
-- if the unit is debuffed and still exists and is not stealthed check this only if the MUFs engine is not there, redudent tests otherwise...
if D.UnitDebuffed[UnitID] and UnitExists(UnitID) then
-- we don't care about range
if not D.profile.LV_OnlyInRange then
Index = Index + 1;
self:DisplayItem(Index, UnitID);
-- play the sound if not already done
if not D.Status.SoundPlayed then
D:PlaySound (UnitID, "LV scan NR" );
end
else -- we care about range
if D.profile.ShowDebuffsFrame and MicroUnitF.UnitToMUF[UnitID] then
RangeStatus = MicroUnitF.UnitToMUF[UnitID].UnitStatus; -- MicroUnitF.UnitToMUF[UnitID] is nil sometimes XXX
RangeStatus = (RangeStatus == DC.AFFLICTED or RangeStatus == DC.AFFLICTED_AND_CHARMED) and true or false;
else
if D.Status.CuringSpells[D.ManagedDebuffUnitCache[UnitID][1].Type] then
RangeStatus = IsSpellInRange(D.Status.CuringSpells[D.ManagedDebuffUnitCache[UnitID][1].Type], UnitID);
else
D:AddDebugText(
"LiveList:Update_Display(): couldn't get range, DType:", D.ManagedDebuffUnitCache[UnitID][1].Type,
"DTypeName:", D.ManagedDebuffUnitCache[UnitID][1].TypeName,
"DName:", D.ManagedDebuffUnitCache[UnitID][1].Name,
"MUFs are:", D.profile.ShowDebuffsFrame,
"InCombatLockdown():", InCombatLockdown(),
"UnitID:", UnitID
);
RangeStatus = 0;
end
RangeStatus = (RangeStatus and RangeStatus ~= 0) and true or false;
end
if (RangeStatus) then
Index = Index + 1;
self:DisplayItem(Index, UnitID);
-- play the sound if not already done
if not D.Status.SoundPlayed then
D:PlaySound (UnitID, "LV R" );
end
end
end
end
-- don't loop if we reach the max displayed unit num or if all debuffed units have been displayed
if Index == D.profile.Amount_Of_Afflicted or Index == DebuffedUnitsNumber + IndexOffset then
break;
end
end
end
-- reset the sound if no units were displayed
if not D.profile.ShowDebuffsFrame and Index == 0 and D.Status.SoundPlayed then
Dcr:Debug("LV: No more unit displayed, sound re-enabled");
D.Status.SoundPlayed = false; -- re-enable the sound if no more debuff
end
-- Hide unneeded Items
if self.NumberShown > Index then -- if there are more units shown than the actual number of debuffed units
for i = Index + 1, self.NumberShown do
if self.ExistingPerID[i] and self.ExistingPerID[i].IsShown then
--D:Debug("(LiveList) Hidding LVItem %d", i);
self.ExistingPerID[i].Frame:Hide();
self.ExistingPerID[i].IsShown = false;
self.NumberShown = self.NumberShown - 1;
else
break;
end
end
end
end -- }}}
function LiveList:DisplayTestItem() -- {{{
if not self.TestItemDisplayed and D.Status.Unit_Array[1] then
self.TestItemDisplayed = GetTime();
D:DummyDebuff(D.Status.Unit_Array[1], "Test item");
end
end -- }}}
function LiveList:HideTestItem() -- {{{
self.TestItemDisplayed = false;
local i = 1;
for UnitID, Debuffed in pairs(D.UnitDebuffed) do
if Debuffed then
D:ScheduleDelayedCall("Dcr_rmt"..i, D.DummyDebuff, i * (D.profile.ScanTime / 2), D, UnitID);
i = i + 1;
end
end
end -- }}}
-- this displays the tooltips of the live-list
function LiveList:DebuffTemplate_OnEnter(frame) --{{{
if (D.profile.AfflictionTooltips and frame.Object.UnitID) then
DcrDisplay_Tooltip:SetOwner(frame, "ANCHOR_CURSOR");
DcrDisplay_Tooltip:ClearLines();
DcrDisplay_Tooltip:SetUnitDebuff(frame.Object.UnitID,frame.Object.Debuff.index); -- OK
DcrDisplay_Tooltip:Show();
else
D:Debug(D.profile.AfflictionTooltips, frame.Object.UnitID );
end
end --}}}
function LiveList:Onclick() -- {{{
D:Println(L["HLP_LL_ONCLICK_TEXT"]);
end -- }}}
T._LoadedFiles["Dcr_LiveList.lua"] = "2.5.1-6-gd3885c5";