fix: v1.4.4 - AceGUI pool fixes, event handling nil checks, QuestieLearner serialization, l10n format fixes

This commit is contained in:
Xurkon
2026-03-20 21:16:58 -05:00
parent f32520078c
commit 2df8a7b96d
26 changed files with 569 additions and 83 deletions
@@ -57,6 +57,7 @@ function _QuestieJourney:CreateObjectiveText(desc)
end
function _QuestieJourney:HandleTabChange(container, group)
if not container then return end
if not _QuestieJourney.containerCache then
_QuestieJourney.containerCache = container
end
+35 -5
View File
@@ -28,12 +28,31 @@ local LOG_DEVELOP = false
local function DebugLog(tier, msg)
if tier == "CRITICAL" and LOG_CRITICAL then
Questie:Print("|cFF00FF00[QL-CRITICAL]|r " .. msg)
-- print("[QuestieLearnerComms] " .. msg)
elseif tier == "DEVELOP" and LOG_DEVELOP then
Questie:Debug(Questie.DEBUG_DEVELOP, "|cFF00FFFF[QL-DEV]|r " .. msg)
-- print("[QuestieLearnerComms] " .. msg)
end
end
local function SanitizeData(data, depth)
depth = depth or 0
if depth > 10 then return nil end -- Prevent infinite recursion
if type(data) ~= "table" then return {} end
local sanitized = {}
for k, v in pairs(data) do
if type(k) ~= "string" and type(k) ~= "number" then
-- Skip non-string/number keys
elseif type(v) == "function" or type(v) == "userdata" or type(v) == "thread" then
-- Skip these types
elseif type(v) == "table" then
sanitized[k] = SanitizeData(v, depth + 1)
else
sanitized[k] = v
end
end
return sanitized
end
-- Throttling (Token Bucket)
local bucketCapacity = 9
local bucketWindow = 60
@@ -167,18 +186,29 @@ function _QuestieLearnerComms:ProcessReinforcement()
end
function QuestieLearnerComms:BroadcastLearnedData(op, entityType, entityId, data)
-- 1. Create Payload
if not data or type(data) ~= "table" then return end
-- 1. Create Payload (sanitize data to remove functions before serialization)
local sanitizedData = SanitizeData(data)
if not sanitizedData or next(sanitizedData) == nil then return end
local payload = {
_ver = ProtocolVersion,
op = op, -- "NEW", "UPDATE", "CONFIRM"
typ = entityType,
id = entityId,
d = data,
d = sanitizedData,
ts = time()
}
-- 2. Serialize and Compress
local serialized = AceSerializer:Serialize(payload)
local serialized
local success, err = pcall(AceSerializer.Serialize, AceSerializer, payload)
if not success then
DebugLog("CRITICAL", "AceSerializer error: " .. tostring(err))
return
end
serialized = err
local compressed = LibDeflate:CompressDeflate(serialized, {level = 9})
local encoded = LibDeflate:EncodeForPrint(compressed)
+49 -1
View File
@@ -17,6 +17,8 @@ end
-- Polyfill for xpcall variadic arguments (missing in standard Lua 5.0/5.1 WoW clients).
-- Modern Ace3 uses xpcall(func, err, ...) which drops arguments on legacy clients,
-- leading to 'self' being nil in addon callbacks.
-- Fix #14: Do NOT write to bare _G.xpcall — store in QuestieCompat namespace only.
-- Writing to _G.xpcall pollutes the global namespace and can cause taint on protected contexts.
local _xpcall = xpcall
local xpcall_supported = false
pcall(function()
@@ -24,7 +26,7 @@ pcall(function()
end)
if not xpcall_supported then
_G.xpcall = function(func, err, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25)
QuestieCompat.xpcall = function(func, err, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25)
-- To avoid the GC overhead of building {...} on every event fire, we pre-check argument counts.
-- We support up to 25 arguments just like our select() polyfill.
if arg25 ~= nil then return _xpcall(function() return func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25) end, err) end
@@ -56,6 +58,33 @@ if not xpcall_supported then
-- No extra args provided
return _xpcall(func, err)
end
else
-- Native xpcall works fine, expose it
QuestieCompat.xpcall = _xpcall
end
------------------------------------------
-- GetCurrentRegion polyfill (WotLK/Classic)
------------------------------------------
-- GetCurrentRegion and GetCurrentRegionName are modern API functions that don't exist in WotLK.
-- AceDB-3.0 uses these for realm identification. Provide fallbacks based on locale.
if not GetCurrentRegion then
local regionByLocale = {
["enUS"] = 1, ["enGB"] = 1, ["koKR"] = 2, ["frFR"] = 3, ["deDE"] = 3,
["zhCN"] = 5, ["zhTW"] = 4, ["esES"] = 3, ["esMX"] = 1, ["ruRU"] = 3,
["ptBR"] = 1, ["itIT"] = 3,
}
GetCurrentRegion = function()
return regionByLocale[GetLocale()] or 1
end
end
if not GetCurrentRegionName then
local regionNames = { "US", "KR", "EU", "TW", "CN" }
GetCurrentRegionName = function()
return regionNames[GetCurrentRegion()] or "US"
end
end
-- addon is running on 3.3.5 WotLK client
@@ -91,6 +120,25 @@ if not TooltipBackdropTemplateMixin then
TooltipBackdropTemplateMixin = BackdropTemplateMixin
end
-------------------------------------------
-- AceComm/AceSerializer compatibility (WotLK)
-------------------------------------------
-- Ambiguate is used to disambiguate realm names but doesn't exist in WotLK.
-- On WotLK, realm names are already unique in the format, so we can just return the name.
if not Ambiguate then
Ambiguate = function(name, kind)
return name
end
end
-- RegisterAddonMessagePrefix may not exist in all WotLK versions.
if not RegisterAddonMessagePrefix then
RegisterAddonMessagePrefix = function(prefix)
-- No-op on versions that don't support it
end
end
-------------------------------------------
-- API difference compatibility (Era/Wotlk)
-------------------------------------------
+14 -5
View File
@@ -16,6 +16,12 @@ local QuestieCorrections = QuestieLoader:ImportModule("QuestieCorrections")
---@type l10n
local l10n = QuestieLoader:ImportModule("l10n")
---@type QuestieCompat
local QuestieCompat = QuestieLoader:ImportModule("QuestieCompat")
---@type C_Timer
local C_Timer = QuestieCompat.C_Timer
local DebugInformation = {} -- stores text of debug data dump per session
local debugIndex = 0 -- current debug index, used so we can still retrieve info from previous offers
local openDebugWindows = {} -- determines if existing debug window is already open, prevents duplicates
@@ -642,15 +648,18 @@ 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.
-- FIX: Added InCombatLockdown guard, deferred execution, 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.
-- Using C_Timer.After to defer execution to after the protected context completes.
hooksecurefunc("SetItemRef", function(link)
if InCombatLockdown() then return end
local linkType = link:sub(1, LINK_LENGTHS);
if linkType == LINK_CODE then
pcall(QuestieDebugOffer.ShowOffer, link)
C_Timer.After(0, function()
pcall(QuestieDebugOffer.ShowOffer, link)
end)
end
end);
+1
View File
@@ -288,6 +288,7 @@ end
--- Fires when a System Message (yellow text) is output to the main chat window
---@param message string The message value from the CHAT_MSG_SYSTEM event
function _EventHandler:ChatMsgSystem(message)
if not message then return end
-- When a new quest is accepted or completed quest is turned in, update the LibDataBroker text with the appropriate message
if string.find(message, questCompletedMessage) == 1 or string.find(message, questAcceptedMessage) == 1 then
MinimapIcon:UpdateText(message)
+1
View File
@@ -55,6 +55,7 @@ end
--Always compare to the UnitLevel parameter, returning the highest.
---@param level Level
function QuestiePlayer:SetPlayerLevel(level)
if level == nil then return end
local localLevel = UnitLevel("player");
_QuestiePlayer.playerLevel = math_max(localLevel, level);
end
+8 -8
View File
@@ -190,18 +190,18 @@ function MapIconTooltip:Show()
end
elseif iconData.Type == "available" or iconData.Type == "complete" then
local tip = _MapIconTooltip:GetAvailableOrCompleteTooltip(icon)
if not npcAndObjectOrder[tip.title] then
npcAndObjectOrder[tip.title] = {npcNames = {}, quests = {}};
if not npcAndObjectOrder["default"] then
npcAndObjectOrder["default"] = {npcNames = {}, quests = {}};
end
npcAndObjectOrder[tip.title].npcNames[iconData.Name] = true
npcAndObjectOrder[tip.title].quests[tip.title] = tip
npcAndObjectOrder["default"].npcNames[iconData.Name] = true
npcAndObjectOrder["default"].quests[tip.title] = tip
elseif iconData.Type == "monster" or iconData.Type == "killcredit" or iconData.Type == "spell" or iconData.Type == "object" or iconData.Type == "event" or iconData.Type == "item" then
local tip = _MapIconTooltip:GetAvailableOrCompleteTooltip(icon)
if not npcAndObjectOrder[tip.title] then
npcAndObjectOrder[tip.title] = {npcNames = {}, quests = {}};
if not npcAndObjectOrder["default"] then
npcAndObjectOrder["default"] = {npcNames = {}, quests = {}};
end
npcAndObjectOrder[tip.title].npcNames[iconData.Name] = true
npcAndObjectOrder[tip.title].quests[tip.title] = tip
npcAndObjectOrder["default"].npcNames[iconData.Name] = true
npcAndObjectOrder["default"].quests[tip.title] = tip
elseif iconData.CustomTooltipData then
manualOrder[iconData.CustomTooltipData.Title] = { Body = { iconData.CustomTooltipData.Body or "" } }
elseif iconData.ManualTooltipData then