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:
Xurkon
2026-03-19 19:24:24 -05:00
parent 5cb64a4c30
commit 5d94cf8f67
14 changed files with 176 additions and 101 deletions
+5 -1
View File
@@ -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
+6 -2
View File
@@ -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
+4 -2
View File
@@ -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("")
+4 -7
View File
@@ -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
+6 -1
View File
@@ -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
View File
@@ -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}