feat: v1.4.0 - Code review fixes and taint resolution
- C_Timer OnUpdate uses elapsed param - IsAchievementCompletion checks completion boolean - C_Map.GetPlayerMapPosition fixes - QuestieLearner GUID function forward declarations - Taint guards on secure hooks (InCombatLockdown + pcall) Fixes ADDON_ACTION_BLOCKED: UseAction() errors
This commit is contained in:
@@ -174,12 +174,16 @@ function QuestEventHandler:RegisterEvents()
|
||||
|
||||
hooksecurefunc("DeleteCursorItem", function()
|
||||
-- Hook DeleteCursorItem so we know when the player clicks the Accept button
|
||||
-- FIX: Added InCombatLockdown guard and pcall to prevent tainting secure execution paths.
|
||||
if InCombatLockdown() then return end
|
||||
if deletedQuestItem then
|
||||
Questie:Debug(Questie.DEBUG_DEVELOP,
|
||||
"[QuestieQuest] DeleteCursorItem: Quest Item deleted. Update all quests.")
|
||||
|
||||
C_Timer.After(0.25, function()
|
||||
_QuestEventHandler:UpdateAllQuests()
|
||||
pcall(function()
|
||||
_QuestEventHandler:UpdateAllQuests()
|
||||
end)
|
||||
deletedQuestItem = false
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -16,6 +16,10 @@ function Hooks:HookQuestLogTitle()
|
||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[Hooks] Hooking Quest Log Title")
|
||||
|
||||
hooksecurefunc("QuestLogTitleButton_OnClick", function(self, button)
|
||||
-- FIX: Added InCombatLockdown guard to prevent tainting secure execution paths.
|
||||
-- This hook can be called during combat if the player interacts with the quest log
|
||||
-- while in combat, which may cause taint that propagates to protected functions.
|
||||
if InCombatLockdown() then return end
|
||||
if (not self) or self.isHeader then
|
||||
return
|
||||
end
|
||||
@@ -47,10 +51,10 @@ function Hooks:HookQuestLogTitle()
|
||||
if questId and questId > 0 then
|
||||
if Questie.db.char.TrackedQuests[questId] or (Questie.db.profile.autoTrackQuests and (not Questie.db.char.AutoUntrackedQuests[questId])) then
|
||||
-- Quest is currently tracked — hidden it
|
||||
QuestieTracker:UntrackQuestId(questId)
|
||||
pcall(QuestieTracker.UntrackQuestId, QuestieTracker, questId)
|
||||
else
|
||||
-- Quest is currently hidden — show it
|
||||
QuestieTracker:AQW_Insert(questLogLineIndex, QUEST_WATCH_NO_EXPIRE)
|
||||
pcall(QuestieTracker.AQW_Insert, QuestieTracker, questLogLineIndex, QUEST_WATCH_NO_EXPIRE)
|
||||
end
|
||||
end
|
||||
if WatchFrame_Update then
|
||||
|
||||
@@ -361,6 +361,8 @@ _AddPlayerQuestProgress = function (quest, starterName, starterZoneName, finishe
|
||||
end
|
||||
|
||||
hooksecurefunc("ChatFrame_OnHyperlinkShow", function(...)
|
||||
-- FIX: Added InCombatLockdown guard to prevent tainting secure execution paths.
|
||||
if InCombatLockdown() then return end
|
||||
local _, link, _, button = ...
|
||||
if (IsShiftKeyDown() and ChatEdit_GetActiveWindow() and button == "LeftButton") then
|
||||
local linkType, questId, _ = string.split(":", link)
|
||||
@@ -368,8 +370,8 @@ hooksecurefunc("ChatFrame_OnHyperlinkShow", function(...)
|
||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTooltips:OnHyperlinkShow] Relinking Quest Link to chat:", link)
|
||||
questId = tonumber(questId)
|
||||
|
||||
local quest = QuestieDB.GetQuest(questId)
|
||||
if quest then
|
||||
local success, quest = pcall(QuestieDB.GetQuest, QuestieDB, questId)
|
||||
if success and quest then
|
||||
local msg = ChatFrame1EditBox:GetText()
|
||||
if msg then
|
||||
ChatFrame1EditBox:SetText("")
|
||||
|
||||
@@ -144,8 +144,7 @@ else
|
||||
local TickerFrame = CreateFrame("Frame")
|
||||
local tickers = {}
|
||||
|
||||
TickerFrame:SetScript("OnUpdate", function()
|
||||
local elapsed = 1 / GetFramerate()
|
||||
TickerFrame:SetScript("OnUpdate", function(self, elapsed)
|
||||
local i = table.getn(tickers)
|
||||
while i >= 1 do
|
||||
local ticker = tickers[i]
|
||||
@@ -415,10 +414,7 @@ end
|
||||
QuestieCompat.C_Map = QuestieCompat.C_Map or {}
|
||||
|
||||
function QuestieCompat.C_Map.GetPlayerMapPosition(uiMapID)
|
||||
local x, y = GetPlayerMapPosition(uiMapID)
|
||||
if x == 0 and y == 0 then
|
||||
x, y = GetPlayerMapPosition("player")
|
||||
end
|
||||
local x, y = GetPlayerMapPosition("player")
|
||||
return x, y
|
||||
end
|
||||
|
||||
@@ -440,7 +436,8 @@ end
|
||||
|
||||
--- IsAchievementCompleted Shim
|
||||
QuestieCompat.IsAchievementCompleted = QuestieCompat.IsAchievementCompleted or function(achievementID)
|
||||
return GetAchievementNumCriteria(achievementID) > 0
|
||||
local completed = select(4, GetAchievementInfo(achievementID))
|
||||
return completed or false
|
||||
end
|
||||
|
||||
--- LibUIDropDownMenu Shim
|
||||
|
||||
@@ -642,10 +642,15 @@ local LINK_COLOR = CreateColorFromHexString("cff71d5ff");
|
||||
local LINK_LENGTHS = LINK_CODE:len();
|
||||
|
||||
-- handles clicking on link
|
||||
-- FIX: Added InCombatLockdown guard and pcall to prevent tainting secure execution paths.
|
||||
-- SetItemRef can be called during action button clicks (e.g., quest item tooltips) which
|
||||
-- run in a protected execution context. If the hook runs insecure code, it can taint
|
||||
-- the call chain and cause "ADDON_ACTION_BLOCKED: tried to call UseAction()" errors.
|
||||
hooksecurefunc("SetItemRef", function(link)
|
||||
if InCombatLockdown() then return end
|
||||
local linkType = link:sub(1, LINK_LENGTHS);
|
||||
if linkType == LINK_CODE then
|
||||
QuestieDebugOffer.ShowOffer(link)
|
||||
pcall(QuestieDebugOffer.ShowOffer, link)
|
||||
end
|
||||
end);
|
||||
|
||||
|
||||
+40
-76
@@ -1163,23 +1163,52 @@ local function GetIdAndTypeFromGUID(guid)
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local function GetNpcIdFromGUID(guid)
|
||||
local id, unitType = GetIdAndTypeFromGUID(guid)
|
||||
if unitType == "Creature" or unitType == "Vehicle" then return id end
|
||||
-- Forward declarations for GUID parsing functions used by event handlers above.
|
||||
-- The full implementations are at lines 1567 and 1604.
|
||||
local GetNpcIdFromGUID = function(guid)
|
||||
if not guid or type(guid) ~= "string" then return nil end
|
||||
local strId = guid:match("Creature%-%d+%-%d+%-%d+%-%d+%-(%d+)")
|
||||
if strId then return tonumber(strId) end
|
||||
if guid:match("^0x") then
|
||||
local hex = guid:sub(3)
|
||||
local prefix = hex:sub(1, 4)
|
||||
local isCreature = (
|
||||
prefix == "F130" or prefix == "F131" or
|
||||
prefix == "F110" or prefix == "F111" or
|
||||
prefix == "F150" or prefix == "F151" or
|
||||
(prefix:sub(1,1) == "F" and prefix ~= "F140" and prefix ~= "F141")
|
||||
)
|
||||
if not isCreature then return nil end
|
||||
if #hex >= 10 then
|
||||
local id = tonumber(hex:sub(5, 10), 16)
|
||||
if id and id > 0 then return id end
|
||||
end
|
||||
if #hex >= 8 then
|
||||
local id = tonumber(hex:sub(5, 8), 16)
|
||||
if id and id > 0 then return id end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function GetObjectIdFromGUID(guid)
|
||||
local id, unitType = GetIdAndTypeFromGUID(guid)
|
||||
if unitType == "GameObject" then return id end
|
||||
local GetObjectIdFromGUID = function(guid)
|
||||
if not guid or type(guid) ~= "string" then return nil end
|
||||
local strId = guid:match("GameObject%-%d+%-%d+%-%d+%-%d+%-(%d+)")
|
||||
if strId then return tonumber(strId) end
|
||||
if guid:match("^0x") then
|
||||
local hex = guid:sub(3)
|
||||
if #hex >= 10 then
|
||||
local id = tonumber(hex:sub(5, 10), 16)
|
||||
if id and id > 0 then return id end
|
||||
end
|
||||
if #hex >= 8 then
|
||||
local id = tonumber(hex:sub(5, 8), 16)
|
||||
if id and id > 0 then return id end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Expose for use in event handlers below
|
||||
_Learner.GetNpcIdFromGUID = GetNpcIdFromGUID
|
||||
_Learner.GetObjectIdFromGUID = GetObjectIdFromGUID
|
||||
_Learner.GetIdAndTypeFromGUID = GetIdAndTypeFromGUID
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Event handlers
|
||||
------------------------------------------------------------------------
|
||||
@@ -1571,71 +1600,6 @@ function QuestieLearner:OnGetItemInfoReceived(itemId)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Combat log: kill tracking with GUID-keyed cache
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- Extract the NPC entry ID from a GUID string.
|
||||
-- Supports both modern string format (Creature-0-...-entryID) and
|
||||
-- 3.3.5a/Ascension hex format (0x[4-char prefix][6-char entryID][spawn]).
|
||||
-- Logic mirrors DataExporter's DE:GetCreatureIDFromGUID.
|
||||
local function GetNpcIdFromGUID(guid)
|
||||
if not guid or type(guid) ~= "string" then return nil end
|
||||
|
||||
-- Modern string format: "Creature-0-XXXX-XXXX-XXXX-entryID-XXXX"
|
||||
local strId = guid:match("Creature%-%d+%-%d+%-%d+%-%d+%-(%d+)")
|
||||
if strId then return tonumber(strId) end
|
||||
|
||||
-- 3.3.5a / Ascension hex format: 0x[prefix:4][entryID:6][spawn:...]
|
||||
if guid:match("^0x") then
|
||||
local hex = guid:sub(3)
|
||||
local prefix = hex:sub(1, 4)
|
||||
|
||||
-- Known creature prefixes (F130/F131 = standard WotLK, F110/F111 = Ascension)
|
||||
local isCreature = (
|
||||
prefix == "F130" or prefix == "F131" or
|
||||
prefix == "F110" or prefix == "F111" or
|
||||
prefix == "F150" or prefix == "F151" or
|
||||
(prefix:sub(1,1) == "F" and prefix ~= "F140" and prefix ~= "F141")
|
||||
)
|
||||
if not isCreature then return nil end
|
||||
|
||||
-- Entry ID sits at hex chars 5-10 (6 hex chars = 24-bit field)
|
||||
if #hex >= 10 then
|
||||
local id = tonumber(hex:sub(5, 10), 16)
|
||||
if id and id > 0 then return id end
|
||||
end
|
||||
-- Fallback for shorter GUIDs
|
||||
if #hex >= 8 then
|
||||
local id = tonumber(hex:sub(5, 8), 16)
|
||||
if id and id > 0 then return id end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Same logic for game objects (interactable quest objects)
|
||||
local function GetObjectIdFromGUID(guid)
|
||||
if not guid or type(guid) ~= "string" then return nil end
|
||||
|
||||
local strId = guid:match("GameObject%-%d+%-%d+%-%d+%-%d+%-(%d+)")
|
||||
if strId then return tonumber(strId) end
|
||||
|
||||
if guid:match("^0x") then
|
||||
local hex = guid:sub(3)
|
||||
if #hex >= 10 then
|
||||
local id = tonumber(hex:sub(5, 10), 16)
|
||||
if id and id > 0 then return id end
|
||||
end
|
||||
if #hex >= 8 then
|
||||
local id = tonumber(hex:sub(5, 8), 16)
|
||||
if id and id > 0 then return id end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Cache recent kills: guid → {npcId, name, x, y, zoneId, ts}
|
||||
_Learner.recentKills = _Learner.recentKills or {}
|
||||
-- Previous objective counts for active quests: questId → {[idx] = count}
|
||||
|
||||
Reference in New Issue
Block a user