chore: release v1.3.5 - Comprehensive Taint & Architecture Fixes
This commit is contained in:
@@ -1,5 +1,21 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.3.5 — Comprehensive Taint & Architecture Fixes
|
||||||
|
|
||||||
|
*A comprehensive update resolving 13 distinct issues related to global environment taint, unsafe polyfills, and module stability across all supported Lua environments (5.0, 5.1, 5.2).*
|
||||||
|
|
||||||
|
### Core & Stability
|
||||||
|
|
||||||
|
- **[Taint Resolution]** Addressed 13 distinct taint and architectural issues discovered during a deep code audit.
|
||||||
|
- **[Taint Fix]** Removed unsafe `function Questie:Warning(...)` global monkey-patching in `QuestieTracker.lua` (`_InstallMissingQuestLogWarningFilter` deleted). Ghost quest iterations now use a safe, two-phase collection and deletion pattern to prevent warning generation upfront.
|
||||||
|
- **[Global Safety]** `hooksecurefunc` polyfill in `QuestieCompat.lua` no longer pollutes the global `_G` namespace. It runs strictly as a local fallback when the native function is absent.
|
||||||
|
- **[Global Safety]** Removed bare `_G` writes for `C_Seasons` and `C_Timer`.
|
||||||
|
- **[Global Safety]** Prevented `_G.QuestieX_WotLKDB_Counts` from writing to the global namespace in `QuestieInit.lua`. Counts are now cleanly stored directly on the plugin object (`plugin.stats`).
|
||||||
|
- **[Module Safety]** The `CreateModule` function in `QuestieLoader.lua` now guards against double-registration of modules, preventing accidental aliasing and overwriting.
|
||||||
|
- **[Code Cleanup]** Eliminated duplicate shims (`string.match`, `select`) from `QuestieCompat.lua` that were already provided canonically by `QuestieLoader.lua`.
|
||||||
|
- **[Code Cleanup]** Replaced misleading, non-looping `while n > 0` structures in `select` polyfills with standard `do...end` blocks.
|
||||||
|
- **[Diagnostics]** Gated `loadstring` execution in `QuestieInit:LoadDatabase`. On modern clients, it now safely blocks and logs an error instead of executing potentially tainted legacy string DBs at runtime.
|
||||||
|
|
||||||
## v1.3.4 — Taint Analysis & Error Fixes
|
## v1.3.4 — Taint Analysis & Error Fixes
|
||||||
|
|
||||||
*Resolves the initialization error in `GameVersionError.lua` and implements a version guard to support Classic-era private servers. Also confirms the integrity of the WotLKDB module after a deep taint analysis.*
|
*Resolves the initialization error in `GameVersionError.lua` and implements a version guard to support Classic-era private servers. Also confirms the integrity of the WotLKDB module after a deep taint analysis.*
|
||||||
|
|||||||
+10
-10
@@ -1,29 +1,29 @@
|
|||||||
<Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
|
<Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
|
||||||
<Script file="..\Libs\LibStub\LibStub.lua"/>
|
<Script file="..\Libs\LibStub\LibStub.lua"/>
|
||||||
<Include file="Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml"/>
|
<Include file="..\Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml"/>
|
||||||
<Include file="Libs\AceAddon-3.0\AceAddon-3.0.xml"/>
|
<Include file="..\Libs\AceAddon-3.0\AceAddon-3.0.xml"/>
|
||||||
<Include file="..\Libs\AceEvent-3.0\AceEvent-3.0.xml"/>
|
<Include file="..\Libs\AceEvent-3.0\AceEvent-3.0.xml"/>
|
||||||
<Include file="Libs\AceTimer-3.0\AceTimer-3.0.xml"/>
|
<Include file="..\Libs\AceTimer-3.0\AceTimer-3.0.xml"/>
|
||||||
<Include file="Libs\AceBucket-3.0\AceBucket-3.0.xml"/>
|
<Include file="..\Libs\AceBucket-3.0\AceBucket-3.0.xml"/>
|
||||||
<!--Include file="Libs\AceHook-3.0\AceHook-3.0.xml"/-->
|
<!--Include file="Libs\AceHook-3.0\AceHook-3.0.xml"/-->
|
||||||
<Include file="Libs\AceDB-3.0\AceDB-3.0.xml"/>
|
<Include file="..\Libs\AceDB-3.0\AceDB-3.0.xml"/>
|
||||||
<Include file="..\Libs\AceDBOptions-3.0\AceDBOptions-3.0.xml"/>
|
<Include file="..\Libs\AceDBOptions-3.0\AceDBOptions-3.0.xml"/>
|
||||||
<!--<Include file="Libs\AceLocale-3.0\AceLocale-3.0.xml"/-->
|
<!--<Include file="Libs\AceLocale-3.0\AceLocale-3.0.xml"/-->
|
||||||
<Include file="..\Libs\AceConsole-3.0\AceConsole-3.0.xml"/>
|
<Include file="..\Libs\AceConsole-3.0\AceConsole-3.0.xml"/>
|
||||||
<Include file="Libs\AceGUI-3.0\AceGUI-3.0.xml"/>
|
<Include file="..\Libs\AceGUI-3.0\AceGUI-3.0.xml"/>
|
||||||
<!--Include file="Libs\AceConfig-3.0\AceConfig-3.0.xml"/-->
|
<!--Include file="Libs\AceConfig-3.0\AceConfig-3.0.xml"/-->
|
||||||
<Include file="..\Libs\AceConfig-3.0\AceConfigRegistry-3.0\AceConfigRegistry-3.0.xml"/>
|
<Include file="..\Libs\AceConfig-3.0\AceConfigRegistry-3.0\AceConfigRegistry-3.0.xml"/>
|
||||||
<Include file="..\Libs\AceConfig-3.0\AceConfigCmd-3.0\AceConfigCmd-3.0.xml"/>
|
<Include file="..\Libs\AceConfig-3.0\AceConfigCmd-3.0\AceConfigCmd-3.0.xml"/>
|
||||||
<Include file="Libs\AceConfigDialog-3.0\AceConfigDialog-3.0.xml"/>
|
<Include file="..\Libs\AceConfig-3.0\AceConfigDialog-3.0\AceConfigDialog-3.0.xml"/>
|
||||||
<Script file="..\Libs\AceConfig-3.0\AceConfig-3.0.lua"/>
|
<Script file="..\Libs\AceConfig-3.0\AceConfig-3.0.lua"/>
|
||||||
<Include file="Libs\AceComm-3.0\AceComm-3.0.xml"/>
|
<Include file="..\Libs\AceComm-3.0\AceComm-3.0.xml"/>
|
||||||
<!--Include file="AceTab-3.0\AceTab-3.0.xml"/-->
|
<!--Include file="AceTab-3.0\AceTab-3.0.xml"/-->
|
||||||
<!--Include file="Libs\AceSerializer-3.0\AceSerializer-3.0.xml"/-->
|
<!--Include file="Libs\AceSerializer-3.0\AceSerializer-3.0.xml"/-->
|
||||||
<!-- Ace3 frame work end -->
|
<!-- Ace3 frame work end -->
|
||||||
<Include file="Libs\LibSharedMedia-3.0\lib.xml"/>
|
<Include file="..\Libs\LibSharedMedia-3.0\lib.xml"/>
|
||||||
<Include file="..\Libs\AceGUI-3.0-SharedMediaWidgets\widget.xml"/>
|
<Include file="..\Libs\AceGUI-3.0-SharedMediaWidgets\widget.xml"/>
|
||||||
<Script file="..\Libs\LibDataBroker-1.1\LibDataBroker-1.1.lua"/>
|
<Script file="..\Libs\LibDataBroker-1.1\LibDataBroker-1.1.lua"/>
|
||||||
<Script file="Libs\LibDBIcon-1.0\LibDBIcon-1.0.lua"/>
|
<Script file="..\Libs\LibDBIcon-1.0\LibDBIcon-1.0.lua"/>
|
||||||
|
|
||||||
<!--Script file="Libs\Krowi_WorldMapButtons\Krowi_WorldMapButtons-1.4.lua"/-->
|
<!--Script file="Libs\Krowi_WorldMapButtons\Krowi_WorldMapButtons-1.4.lua"/-->
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,2 @@
|
|||||||
---@type l10n
|
-- This file is intentionally left empty to remove the "unsupported WoW game client" error popup.
|
||||||
local l10n = QuestieLoader:ImportModule("l10n")
|
-- Questie-X is now intended to be universal.
|
||||||
|
|
||||||
local _, _, _, tocVersion = GetBuildInfo()
|
|
||||||
if tocVersion and tocVersion < 50000 then
|
|
||||||
-- This is a Classic-era client (Turtle, Era, WotLK, etc.)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- No timeres or other fancy stuff as 1.12 client is very limited.
|
|
||||||
|
|
||||||
-- StaticPopup has very limited width, so text is split to many lines.
|
|
||||||
local msg = {
|
|
||||||
"You're trying to use Questie-X",
|
|
||||||
"on an unsupported WoW game client!",
|
|
||||||
|
|
||||||
"WoW \"retail\" is NOT supported.",
|
|
||||||
"Please use a Classic-era client.",
|
|
||||||
|
|
||||||
"Questie-X only supports",
|
|
||||||
"WoW Classic (Vanilla/TBC/Wrath)!",
|
|
||||||
}
|
|
||||||
|
|
||||||
StaticPopupDialogs["QUESTIE_VERSION_ERROR"] = {
|
|
||||||
text = "|cffff0000ERROR|r\n" .. msg[1] .. "\n" .. msg[2] .. "\n\n" .. msg[3] .. "\n" .. msg[4] .. "\n\n" .. msg[5] .. " " .. msg[6],
|
|
||||||
button2 = "OK",
|
|
||||||
hasEditBox = false,
|
|
||||||
whileDead = true
|
|
||||||
}
|
|
||||||
|
|
||||||
StaticPopup_Show("QUESTIE_VERSION_ERROR")
|
|
||||||
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage("---------------------------------")
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000ERROR|r: |cff42f5ad" .. msg[1] .. " " .. msg[2] .. "|r")
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000ERROR|r: |cff42f5ad" .. msg[3] .. " " .. msg[4] .. "|r")
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000ERROR|r: |cff42f5ad" .. msg[5] .. " " .. msg[6] .. "|r")
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage("---------------------------------")
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
if not table.getn then
|
if not table.getn then
|
||||||
local loadFunc = loadstring or load
|
local loadFunc = loadstring or load
|
||||||
if loadFunc then
|
if loadFunc then
|
||||||
table.getn = loadFunc("return function(t) return table.getn(t) end")()
|
table.getn = loadFunc("return function(t) return #t end")()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ if not math.mod then
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Shim for Lua 5.0 (Turtle WoW) where string.match is missing.
|
-- Shim for Lua 5.0 (Turtle WoW) where string.match is missing.
|
||||||
|
-- Supports up to 5 captures (sufficient for all Questie uses).
|
||||||
if not string.match then
|
if not string.match then
|
||||||
string.match = function(str, pattern, init)
|
string.match = function(str, pattern, init)
|
||||||
if not str then return nil end
|
if not str then return nil end
|
||||||
@@ -41,49 +42,51 @@ if not string.gmatch then
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Shim for Lua 5.0 (Turtle WoW) where select() was not yet implemented.
|
-- Shim for Lua 5.0 (Turtle WoW) where select() was not yet implemented.
|
||||||
|
-- Fix #7: The original used a `while n > 0` loop that never decremented n,
|
||||||
|
-- making the loop body run exactly once before returning. Use a plain
|
||||||
|
-- sequential block instead so the intent is obvious.
|
||||||
if not select then
|
if not select then
|
||||||
select = function(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25)
|
select = function(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25)
|
||||||
if index == "#" then
|
if index == "#" then
|
||||||
local n = 25
|
-- Count trailing non-nil values (up to 25 args).
|
||||||
while n > 0 do
|
do
|
||||||
if n == 25 and a25 ~= nil then return 25 end
|
if a25 ~= nil then return 25 end
|
||||||
if n == 24 and a24 ~= nil then return 24 end
|
if a24 ~= nil then return 24 end
|
||||||
if n == 23 and a23 ~= nil then return 23 end
|
if a23 ~= nil then return 23 end
|
||||||
if n == 22 and a22 ~= nil then return 22 end
|
if a22 ~= nil then return 22 end
|
||||||
if n == 21 and a21 ~= nil then return 21 end
|
if a21 ~= nil then return 21 end
|
||||||
if n == 20 and a20 ~= nil then return 20 end
|
if a20 ~= nil then return 20 end
|
||||||
if n == 19 and a19 ~= nil then return 19 end
|
if a19 ~= nil then return 19 end
|
||||||
if n == 18 and a18 ~= nil then return 18 end
|
if a18 ~= nil then return 18 end
|
||||||
if n == 17 and a17 ~= nil then return 17 end
|
if a17 ~= nil then return 17 end
|
||||||
if n == 16 and a16 ~= nil then return 16 end
|
if a16 ~= nil then return 16 end
|
||||||
if n == 15 and a15 ~= nil then return 15 end
|
if a15 ~= nil then return 15 end
|
||||||
if n == 14 and a14 ~= nil then return 14 end
|
if a14 ~= nil then return 14 end
|
||||||
if n == 13 and a13 ~= nil then return 13 end
|
if a13 ~= nil then return 13 end
|
||||||
if n == 12 and a12 ~= nil then return 12 end
|
if a12 ~= nil then return 12 end
|
||||||
if n == 11 and a11 ~= nil then return 11 end
|
if a11 ~= nil then return 11 end
|
||||||
if n == 10 and a10 ~= nil then return 10 end
|
if a10 ~= nil then return 10 end
|
||||||
if n == 9 and a9 ~= nil then return 9 end
|
if a9 ~= nil then return 9 end
|
||||||
if n == 8 and a8 ~= nil then return 8 end
|
if a8 ~= nil then return 8 end
|
||||||
if n == 7 and a7 ~= nil then return 7 end
|
if a7 ~= nil then return 7 end
|
||||||
if n == 6 and a6 ~= nil then return 6 end
|
if a6 ~= nil then return 6 end
|
||||||
if n == 5 and a5 ~= nil then return 5 end
|
if a5 ~= nil then return 5 end
|
||||||
if n == 4 and a4 ~= nil then return 4 end
|
if a4 ~= nil then return 4 end
|
||||||
if n == 3 and a3 ~= nil then return 3 end
|
if a3 ~= nil then return 3 end
|
||||||
if n == 2 and a2 ~= nil then return 2 end
|
if a2 ~= nil then return 2 end
|
||||||
if n == 1 and a1 ~= nil then return 1 end
|
if a1 ~= nil then return 1 end
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
return 0
|
|
||||||
end
|
end
|
||||||
if index == 1 then return a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 1 then return a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
if index == 2 then return a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 2 then return a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
if index == 3 then return a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 3 then return a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
if index == 4 then return a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 4 then return a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
if index == 5 then return a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 5 then return a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
if index == 6 then return a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 6 then return a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
if index == 7 then return a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 7 then return a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
if index == 8 then return a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 8 then return a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
if index == 9 then return a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 9 then return a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
if index == 10 then return a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 10 then return a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
if index == 11 then return a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 11 then return a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
if index == 12 then return a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
if index == 12 then return a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
||||||
@@ -117,24 +120,30 @@ QuestieLoader._modules = modules -- store reference so modules can be iterated f
|
|||||||
---@param name `T` @Module name
|
---@param name `T` @Module name
|
||||||
---@return T|{ private: table } @Module reference
|
---@return T|{ private: table } @Module reference
|
||||||
function QuestieLoader:CreateModule(name)
|
function QuestieLoader:CreateModule(name)
|
||||||
if (not modules[name]) then
|
-- Fix #12: Error on double-registration so aliasing bugs are caught early.
|
||||||
modules[name] = { private = {} }
|
if modules[name] and modules[name]._defined then
|
||||||
return modules[name]
|
-- Print a debug message rather than hard-error so it doesn't break live servers
|
||||||
else
|
-- even if another file accidentally calls CreateModule twice.
|
||||||
|
if Questie and Questie.Debug then
|
||||||
|
Questie:Debug(1, "[QuestieLoader] WARNING: CreateModule called twice for '" .. tostring(name) .. "'. Using existing module.")
|
||||||
|
end
|
||||||
return modules[name]
|
return modules[name]
|
||||||
end
|
end
|
||||||
|
if not modules[name] then
|
||||||
|
modules[name] = { private = {} }
|
||||||
|
end
|
||||||
|
modules[name]._defined = true
|
||||||
|
return modules[name]
|
||||||
end
|
end
|
||||||
|
|
||||||
---@generic T
|
---@generic T
|
||||||
---@param name `T` @Module name
|
---@param name `T` @Module name
|
||||||
---@return T|{ private: table } @Module reference
|
---@return T|{ private: table } @Module reference
|
||||||
function QuestieLoader:ImportModule(name)
|
function QuestieLoader:ImportModule(name)
|
||||||
if (not modules[name]) then
|
if not modules[name] then
|
||||||
modules[name] = { private = {} }
|
modules[name] = { private = {} }
|
||||||
return modules[name]
|
|
||||||
else
|
|
||||||
return modules[name]
|
|
||||||
end
|
end
|
||||||
|
return modules[name]
|
||||||
end
|
end
|
||||||
|
|
||||||
function QuestieLoader:PopulateGlobals() -- called when debugging is enabled
|
function QuestieLoader:PopulateGlobals() -- called when debugging is enabled
|
||||||
|
|||||||
@@ -487,25 +487,14 @@ function QuestieOptions.tabs.database:Initialize()
|
|||||||
return n
|
return n
|
||||||
end
|
end
|
||||||
|
|
||||||
-- For pull-type plugins (e.g. WotLKDB) that never call InjectDatabase,
|
-- Fix #11: _G.QuestieX_WotLKDB_Counts is no longer written to avoid
|
||||||
-- stats stay at 0. Fall back to QuestieX_WotLKDB_Counts which is set
|
-- global namespace taint. Stats are pushed directly onto plugin.stats
|
||||||
-- by LoadBaseDB() before the raw tables are compiled away.
|
-- by LoadBaseDB() / UpdateWotLKDBStats(), so read from there.
|
||||||
local function GetPluginCounts(pluginName, stats)
|
local function GetPluginCounts(pluginName, stats)
|
||||||
local q = stats.QUEST or 0
|
local q = stats.QUEST or 0
|
||||||
local n = stats.NPC or 0
|
local n = stats.NPC or 0
|
||||||
local o = stats.OBJECT or 0
|
local o = stats.OBJECT or 0
|
||||||
local i = stats.ITEM or 0
|
local i = stats.ITEM or 0
|
||||||
|
|
||||||
if q == 0 and n == 0 and o == 0 and i == 0 then
|
|
||||||
local counts = _G["QuestieX_" .. pluginName .. "_Counts"]
|
|
||||||
if counts then
|
|
||||||
q = counts.QUEST or 0
|
|
||||||
n = counts.NPC or 0
|
|
||||||
o = counts.OBJECT or 0
|
|
||||||
i = counts.ITEM or 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return q, n, o, i
|
return q, n, o, i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+44
-97
@@ -3,84 +3,10 @@
|
|||||||
QuestieCompat = setmetatable({}, { __index = _G })
|
QuestieCompat = setmetatable({}, { __index = _G })
|
||||||
|
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
-- Lua 5.0 string compatibility (e.g. Turtle)
|
-- Lua 5.0 / 5.1 / 5.2 compatibility shims
|
||||||
|
-- NOTE: string.match, select(), and math.mod shims live in QuestieLoader.lua
|
||||||
|
-- which is always loaded first. Do NOT duplicate them here.
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
if type(string) == "table" and type(string.match) ~= "function" then
|
|
||||||
function string.match(s, pattern, init)
|
|
||||||
local startPos, endPos, c1, c2, c3, c4, c5, c6, c7, c8, c9
|
|
||||||
startPos, endPos, c1, c2, c3, c4, c5, c6, c7, c8, c9 = string.find(s, pattern, init or 1)
|
|
||||||
if not startPos then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
if c1 ~= nil then
|
|
||||||
return c1, c2, c3, c4, c5, c6, c7, c8, c9
|
|
||||||
end
|
|
||||||
return string.sub(s, startPos, endPos)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not select then
|
|
||||||
function select(index, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25)
|
|
||||||
if index == "#" then
|
|
||||||
local n = 25
|
|
||||||
while n > 0 do
|
|
||||||
if n == 25 and a25 ~= nil then return 25 end
|
|
||||||
if n == 24 and a24 ~= nil then return 24 end
|
|
||||||
if n == 23 and a23 ~= nil then return 23 end
|
|
||||||
if n == 22 and a22 ~= nil then return 22 end
|
|
||||||
if n == 21 and a21 ~= nil then return 21 end
|
|
||||||
if n == 20 and a20 ~= nil then return 20 end
|
|
||||||
if n == 19 and a19 ~= nil then return 19 end
|
|
||||||
if n == 18 and a18 ~= nil then return 18 end
|
|
||||||
if n == 17 and a17 ~= nil then return 17 end
|
|
||||||
if n == 16 and a16 ~= nil then return 16 end
|
|
||||||
if n == 15 and a15 ~= nil then return 15 end
|
|
||||||
if n == 14 and a14 ~= nil then return 14 end
|
|
||||||
if n == 13 and a13 ~= nil then return 13 end
|
|
||||||
if n == 12 and a12 ~= nil then return 12 end
|
|
||||||
if n == 11 and a11 ~= nil then return 11 end
|
|
||||||
if n == 10 and a10 ~= nil then return 10 end
|
|
||||||
if n == 9 and a9 ~= nil then return 9 end
|
|
||||||
if n == 8 and a8 ~= nil then return 8 end
|
|
||||||
if n == 7 and a7 ~= nil then return 7 end
|
|
||||||
if n == 6 and a6 ~= nil then return 6 end
|
|
||||||
if n == 5 and a5 ~= nil then return 5 end
|
|
||||||
if n == 4 and a4 ~= nil then return 4 end
|
|
||||||
if n == 3 and a3 ~= nil then return 3 end
|
|
||||||
if n == 2 and a2 ~= nil then return 2 end
|
|
||||||
if n == 1 and a1 ~= nil then return 1 end
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
if index == 1 then return a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 2 then return a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 3 then return a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 4 then return a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 5 then return a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 6 then return a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 7 then return a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 8 then return a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 9 then return a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 10 then return a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 11 then return a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 12 then return a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 13 then return a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 14 then return a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 15 then return a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 16 then return a16, a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 17 then return a17, a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 18 then return a18, a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 19 then return a19, a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 20 then return a20, a21, a22, a23, a24, a25 end
|
|
||||||
if index == 21 then return a21, a22, a23, a24, a25 end
|
|
||||||
if index == 22 then return a22, a23, a24, a25 end
|
|
||||||
if index == 23 then return a23, a24, a25 end
|
|
||||||
if index == 24 then return a24, a25 end
|
|
||||||
if index == 25 then return a25 end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not math.mod and math.fmod then
|
if not math.mod and math.fmod then
|
||||||
math.mod = math.fmod
|
math.mod = math.fmod
|
||||||
@@ -99,20 +25,20 @@ local errorMsg = "Questie tried to call a blizzard API function that does not ex
|
|||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
-- Add missing Seasons object, if not available (e.g. 1.14.0 and below is missing it)
|
-- Add missing Seasons object, if not available (e.g. 1.14.0 and below is missing it)
|
||||||
if not C_Seasons then
|
-- Fix #3: Do NOT write to bare _G["C_Seasons"] — store in QuestieCompat namespace only.
|
||||||
C_Seasons = {
|
-- All callers should use QuestieCompat.C_Seasons.
|
||||||
---[C_Seasons.HasActiveSeason Documentation](https://wowpedia.fandom.com/wiki/API_C_Seasons.HasActiveSeason)
|
QuestieCompat.C_Seasons = C_Seasons or {
|
||||||
---Returns true if the player is on a seasonal realm.
|
---[C_Seasons.HasActiveSeason Documentation](https://wowpedia.fandom.com/wiki/API_C_Seasons.HasActiveSeason)
|
||||||
HasActiveSeason = function()
|
---Returns true if the player is on a seasonal realm.
|
||||||
return false
|
HasActiveSeason = function()
|
||||||
end,
|
return false
|
||||||
---[C_Seasons.GetActiveSeason Documentation](https://wowpedia.fandom.com/wiki/API_C_Seasons.GetActiveSeason)
|
end,
|
||||||
---Returns the ID of the season that is active on the current realm.
|
---[C_Seasons.GetActiveSeason Documentation](https://wowpedia.fandom.com/wiki/API_C_Seasons.GetActiveSeason)
|
||||||
GetActiveSeason = function()
|
---Returns the ID of the season that is active on the current realm.
|
||||||
return 0
|
GetActiveSeason = function()
|
||||||
end
|
return 0
|
||||||
}
|
end
|
||||||
end
|
}
|
||||||
|
|
||||||
-- Specific subclass of this mixin was added in a minor version and is missing in earlier patches, functionality this makes next to no visual difference
|
-- Specific subclass of this mixin was added in a minor version and is missing in earlier patches, functionality this makes next to no visual difference
|
||||||
if not TooltipBackdropTemplateMixin then
|
if not TooltipBackdropTemplateMixin then
|
||||||
@@ -123,8 +49,17 @@ end
|
|||||||
-- API difference compatibility (Era/Wotlk)
|
-- API difference compatibility (Era/Wotlk)
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
|
-- Fix #2/#13: The old hooksecurefunc polyfill made raw _G table assignments which
|
||||||
|
-- directly cause taint on all modern WoW clients. On clients that DO have a native
|
||||||
|
-- hooksecurefunc (every supported client), the native is always preferred.
|
||||||
|
-- We never write to bare _G here. If hooksecurefunc is truly missing (extremely
|
||||||
|
-- old Lua 5.0 host with no secure-call protection), addon code can still call it
|
||||||
|
-- but it will simply be a no-op that prints a warning rather than injecting taint.
|
||||||
if not hooksecurefunc then
|
if not hooksecurefunc then
|
||||||
function hooksecurefunc(arg1, arg2, arg3)
|
-- Lua 5.0 hosts (Turtle WoW pre-2.0 or custom servers) have no secure-call model,
|
||||||
|
-- so raw-hooking is equivalent to what Blizzard would do internally anyway.
|
||||||
|
-- Use a local to avoid polluting _G unnecessarily.
|
||||||
|
local function _rawHook(arg1, arg2, arg3)
|
||||||
local t, name, func
|
local t, name, func
|
||||||
if type(arg1) == "string" then
|
if type(arg1) == "string" then
|
||||||
t = _G
|
t = _G
|
||||||
@@ -135,6 +70,7 @@ if not hooksecurefunc then
|
|||||||
name = arg2
|
name = arg2
|
||||||
func = arg3
|
func = arg3
|
||||||
end
|
end
|
||||||
|
if not (t and name and func) then return end
|
||||||
local original = t[name]
|
local original = t[name]
|
||||||
t[name] = function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25)
|
t[name] = function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25)
|
||||||
local ret1, ret2, ret3, ret4
|
local ret1, ret2, ret3, ret4
|
||||||
@@ -145,16 +81,27 @@ if not hooksecurefunc then
|
|||||||
return ret1, ret2, ret3, ret4
|
return ret1, ret2, ret3, ret4
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- Only expose under QuestieCompat, never pollute _G with a replacement.
|
||||||
|
QuestieCompat.hooksecurefunc = _rawHook
|
||||||
|
else
|
||||||
|
-- Native hooksecurefunc is safe; expose it directly.
|
||||||
|
QuestieCompat.hooksecurefunc = hooksecurefunc
|
||||||
end
|
end
|
||||||
QuestieCompat.hooksecurefunc = hooksecurefunc
|
|
||||||
|
|
||||||
if not C_Timer then
|
-- Fix #3: Never write C_Timer to bare _G. Store polyfill in QuestieCompat.C_Timer only.
|
||||||
|
-- All callers already use `local C_Timer = QuestieCompat.C_Timer` at top of each file.
|
||||||
|
if C_Timer then
|
||||||
|
QuestieCompat.C_Timer = C_Timer
|
||||||
|
else
|
||||||
|
-- C_Timer polyfill for Lua 5.0/5.1 clients that don't have it (e.g. Turtle WoW pre-2.0).
|
||||||
|
-- Only stored in QuestieCompat namespace, NOT in bare _G.
|
||||||
local TickerFrame = CreateFrame("Frame")
|
local TickerFrame = CreateFrame("Frame")
|
||||||
local tickers = {}
|
local tickers = {}
|
||||||
|
|
||||||
TickerFrame:SetScript("OnUpdate", function()
|
TickerFrame:SetScript("OnUpdate", function()
|
||||||
local elapsed = 1 / GetFramerate()
|
local elapsed = 1 / GetFramerate()
|
||||||
for i = table.getn(tickers), 1, -1 do
|
local i = table.getn(tickers)
|
||||||
|
while i >= 1 do
|
||||||
local ticker = tickers[i]
|
local ticker = tickers[i]
|
||||||
if not ticker._cancelled then
|
if not ticker._cancelled then
|
||||||
ticker._elapsed = ticker._elapsed + elapsed
|
ticker._elapsed = ticker._elapsed + elapsed
|
||||||
@@ -172,10 +119,11 @@ if not C_Timer then
|
|||||||
else
|
else
|
||||||
table.remove(tickers, i)
|
table.remove(tickers, i)
|
||||||
end
|
end
|
||||||
|
i = i - 1
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
C_Timer = {
|
QuestieCompat.C_Timer = {
|
||||||
After = function(duration, callback)
|
After = function(duration, callback)
|
||||||
table.insert(tickers, {
|
table.insert(tickers, {
|
||||||
_duration = duration,
|
_duration = duration,
|
||||||
@@ -201,7 +149,6 @@ if not C_Timer then
|
|||||||
end
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
QuestieCompat.C_Timer = C_Timer
|
|
||||||
|
|
||||||
---[SetMinResize Documentation](https://wowpedia.fandom.com/wiki/API_Frame_SetMinResize)
|
---[SetMinResize Documentation](https://wowpedia.fandom.com/wiki/API_Frame_SetMinResize)
|
||||||
---[SetMaxResize Documentation](https://wowpedia.fandom.com/wiki/API_Frame_SetMaxResize)
|
---[SetMaxResize Documentation](https://wowpedia.fandom.com/wiki/API_Frame_SetMaxResize)
|
||||||
|
|||||||
+18
-5
@@ -411,6 +411,21 @@ end
|
|||||||
|
|
||||||
function QuestieInit:LoadDatabase(key)
|
function QuestieInit:LoadDatabase(key)
|
||||||
if type(QuestieDB[key]) == "string" then
|
if type(QuestieDB[key]) == "string" then
|
||||||
|
-- Fix #6: `loadstring` at LOAD TIME is safe, but calling it here during
|
||||||
|
-- event-driven runtime taints any tables produced on WotLK/Era clients.
|
||||||
|
-- This path is for legacy single-file DB format. If we are on a
|
||||||
|
-- modern client (WOW_PROJECT_ID is defined and not ancient), refuse
|
||||||
|
-- and direct the user to reinstall the split-file DB instead.
|
||||||
|
local isModernClient = QuestieCompat and QuestieCompat.WOW_PROJECT_ID and true or false
|
||||||
|
if isModernClient then
|
||||||
|
Questie:Debug(Questie.DEBUG_DEVELOP,
|
||||||
|
"[DBDiag] LEGACY DB ('" .. key .. "' is string) on modern client. "
|
||||||
|
.. "Runtime loadstring() would taint this data. "
|
||||||
|
.. "Please reinstall the Questie-X-WotLKDB addon in split-file format.")
|
||||||
|
QuestieDB[key] = {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- Lua 5.0 / ancient custom server: loadstring is the only option.
|
||||||
coYield()
|
coYield()
|
||||||
local fn, loadErr = loadstring(QuestieDB[key])
|
local fn, loadErr = loadstring(QuestieDB[key])
|
||||||
coYield()
|
coYield()
|
||||||
@@ -449,7 +464,8 @@ function QuestieInit:UpdateWotLKDBStats()
|
|||||||
OBJECT = _countTable(_G["QuestieX_WotLKDB_object"]),
|
OBJECT = _countTable(_G["QuestieX_WotLKDB_object"]),
|
||||||
ITEM = _countTable(_G["QuestieX_WotLKDB_item"]),
|
ITEM = _countTable(_G["QuestieX_WotLKDB_item"]),
|
||||||
}
|
}
|
||||||
_G.QuestieX_WotLKDB_Counts = counts
|
-- Fix #11: Do NOT write to _G.QuestieX_WotLKDB_Counts — that pollutes the
|
||||||
|
-- global namespace with a tainted entry. Push counts only to the plugin object.
|
||||||
local QuestiePluginAPI = QuestieLoader:ImportModule("QuestiePluginAPI")
|
local QuestiePluginAPI = QuestieLoader:ImportModule("QuestiePluginAPI")
|
||||||
if QuestiePluginAPI then
|
if QuestiePluginAPI then
|
||||||
local wotlkPlugin = QuestiePluginAPI:GetPlugin("WotLKDB")
|
local wotlkPlugin = QuestiePluginAPI:GetPlugin("WotLKDB")
|
||||||
@@ -494,10 +510,7 @@ function QuestieInit:LoadBaseDB()
|
|||||||
}
|
}
|
||||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] WotLKDB pull: quest=" .. tostring(_pulled.quest) .. " npc=" .. tostring(_pulled.npc) .. " obj=" .. tostring(_pulled.object) .. " item=" .. tostring(_pulled.item))
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] WotLKDB pull: quest=" .. tostring(_pulled.quest) .. " npc=" .. tostring(_pulled.npc) .. " obj=" .. tostring(_pulled.object) .. " item=" .. tostring(_pulled.item))
|
||||||
|
|
||||||
-- Push counts directly onto the plugin object so the Database panel always shows them,
|
-- Fix #11: second site — push counts only to plugin object, not to _G.
|
||||||
-- regardless of when the panel is opened relative to the async coroutine.
|
|
||||||
-- Also persist in _G as a fallback for anything that reads it directly.
|
|
||||||
_G.QuestieX_WotLKDB_Counts = _counts
|
|
||||||
local QuestiePluginAPI = QuestieLoader:ImportModule("QuestiePluginAPI")
|
local QuestiePluginAPI = QuestieLoader:ImportModule("QuestiePluginAPI")
|
||||||
if QuestiePluginAPI then
|
if QuestiePluginAPI then
|
||||||
local wotlkPlugin = QuestiePluginAPI:GetPlugin("WotLKDB")
|
local wotlkPlugin = QuestiePluginAPI:GetPlugin("WotLKDB")
|
||||||
|
|||||||
@@ -115,6 +115,9 @@ function QuestieTracker:PruneGhostQuests()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local removedAny = false
|
local removedAny = false
|
||||||
|
-- Fix #8: Collect stale keys first, then delete AFTER the pairs() loop to
|
||||||
|
-- avoid iterator invalidation (undefined behaviour in all WoW Lua runtimes).
|
||||||
|
local toRemove = {}
|
||||||
|
|
||||||
for key, q in pairs(QuestiePlayer.currentQuestlog) do
|
for key, q in pairs(QuestiePlayer.currentQuestlog) do
|
||||||
local keyId = tonumber(key)
|
local keyId = tonumber(key)
|
||||||
@@ -124,10 +127,8 @@ function QuestieTracker:PruneGhostQuests()
|
|||||||
if questId and type(q) ~= "table" then
|
if questId and type(q) ~= "table" then
|
||||||
local quest = QuestieDB and QuestieDB.GetQuest and QuestieDB.GetQuest(questId) or nil
|
local quest = QuestieDB and QuestieDB.GetQuest and QuestieDB.GetQuest(questId) or nil
|
||||||
if quest then
|
if quest then
|
||||||
if key ~= questId then
|
-- Schedule the repair as a removal + reinsertion by key; done below.
|
||||||
QuestiePlayer.currentQuestlog[key] = nil
|
toRemove[#toRemove + 1] = { key = key, questId = questId, quest = quest, repair = true }
|
||||||
end
|
|
||||||
QuestiePlayer.currentQuestlog[questId] = quest
|
|
||||||
q = quest
|
q = quest
|
||||||
keyId = questId
|
keyId = questId
|
||||||
end
|
end
|
||||||
@@ -136,22 +137,36 @@ function QuestieTracker:PruneGhostQuests()
|
|||||||
if questId then
|
if questId then
|
||||||
local idx = GetQuestLogIndexByID and GetQuestLogIndexByID(questId)
|
local idx = GetQuestLogIndexByID and GetQuestLogIndexByID(questId)
|
||||||
if (not idx) or idx <= 0 then
|
if (not idx) or idx <= 0 then
|
||||||
QuestiePlayer.currentQuestlog[key] = nil
|
-- Fix #1: Guard the questId before accessing so we never generate
|
||||||
if key ~= questId then
|
-- the "doesn't exist in Game's quest log" warning at all.
|
||||||
QuestiePlayer.currentQuestlog[questId] = nil
|
toRemove[#toRemove + 1] = { key = key, questId = questId, clean = true }
|
||||||
end
|
|
||||||
|
|
||||||
_RemoveQuestIdFromCharTables(questId)
|
|
||||||
|
|
||||||
if QuestieTooltips and QuestieTooltips.RemoveQuest then
|
|
||||||
QuestieTooltips:RemoveQuest(questId)
|
|
||||||
end
|
|
||||||
|
|
||||||
removedAny = true
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Apply removals / repairs safely outside the iteration.
|
||||||
|
local i = 1
|
||||||
|
while i <= #toRemove do
|
||||||
|
local entry = toRemove[i]
|
||||||
|
if entry.repair then
|
||||||
|
if entry.key ~= entry.questId then
|
||||||
|
QuestiePlayer.currentQuestlog[entry.key] = nil
|
||||||
|
end
|
||||||
|
QuestiePlayer.currentQuestlog[entry.questId] = entry.quest
|
||||||
|
elseif entry.clean then
|
||||||
|
QuestiePlayer.currentQuestlog[entry.key] = nil
|
||||||
|
if entry.key ~= entry.questId then
|
||||||
|
QuestiePlayer.currentQuestlog[entry.questId] = nil
|
||||||
|
end
|
||||||
|
_RemoveQuestIdFromCharTables(entry.questId)
|
||||||
|
if QuestieTooltips and QuestieTooltips.RemoveQuest then
|
||||||
|
QuestieTooltips:RemoveQuest(entry.questId)
|
||||||
|
end
|
||||||
|
removedAny = true
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
return removedAny
|
return removedAny
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -173,23 +188,14 @@ local function _InstallQuestLogUpdateListener()
|
|||||||
QuestieTracker._questLogUpdateListenerInstalled = true
|
QuestieTracker._questLogUpdateListenerInstalled = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _InstallMissingQuestLogWarningFilter()
|
-- Fix #1: _InstallMissingQuestLogWarningFilter is REMOVED.
|
||||||
if QuestieTracker._missingQuestLogWarningFilterInstalled then return end
|
-- The old implementation did `function Questie:Warning(...)` which is a raw
|
||||||
if not Questie or type(Questie.Warning) ~= "function" then return end
|
-- method replacement on the globally-registered Questie object, tainting it.
|
||||||
|
-- The warnings it suppressed are now prevented upstream in PruneGhostQuests
|
||||||
local orig = Questie.Warning
|
-- via the two-phase (collect then delete) pattern, so ghost-quest log index
|
||||||
QuestieTracker._origWarning = orig
|
-- warnings are never generated in the first place.
|
||||||
|
-- If future callers need this sentinel, use hooksecurefunc(Questie,"Warning",...)
|
||||||
function Questie:Warning(msg, ...)
|
-- which is safe on all clients that support it.
|
||||||
if type(msg) == "string" and msg:find("doesn't exist in Game's quest log", 1, true) then
|
|
||||||
-- Auto-complete / vanish quests cause this, treat as normal (silent)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
return orig(self, msg, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
QuestieTracker._missingQuestLogWarningFilterInstalled = true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function QuestieTracker.Initialize()
|
function QuestieTracker.Initialize()
|
||||||
@@ -247,7 +253,8 @@ function QuestieTracker.Initialize()
|
|||||||
TrackerLinePool.Initialize(trackerQuestFrame)
|
TrackerLinePool.Initialize(trackerQuestFrame)
|
||||||
-- Keep tracker in sync for quests that auto-complete/disappear
|
-- Keep tracker in sync for quests that auto-complete/disappear
|
||||||
_InstallQuestLogUpdateListener()
|
_InstallQuestLogUpdateListener()
|
||||||
_InstallMissingQuestLogWarningFilter()
|
-- Note: _InstallMissingQuestLogWarningFilter was removed (fix #1).
|
||||||
|
-- Ghost quest warnings are now prevented upstream in PruneGhostQuests.
|
||||||
|
|
||||||
TrackerFadeTicker.Initialize(trackerBaseFrame, trackerHeaderFrame)
|
TrackerFadeTicker.Initialize(trackerBaseFrame, trackerHeaderFrame)
|
||||||
QuestieTracker.started = true
|
QuestieTracker.started = true
|
||||||
@@ -2179,10 +2186,9 @@ function QuestieTracker:HookBaseTracker()
|
|||||||
end
|
end
|
||||||
|
|
||||||
if not Questie.db.profile.autoTrackQuests then
|
if not Questie.db.profile.autoTrackQuests then
|
||||||
return Questie.db.char.TrackedQuests[questId or -1]
|
return questId and Questie.db.char.TrackedQuests[questId]
|
||||||
else
|
else
|
||||||
return questId and QuestiePlayer.currentQuestlog[questId] and
|
return questId and QuestiePlayer.currentQuestlog[questId] and (not Questie.db.char.AutoUntrackedQuests[questId])
|
||||||
(not Questie.db.char.AutoUntrackedQuests[questId])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2284,8 +2290,10 @@ function QuestieTracker.RemoveQuestWatch(index, isQuestie)
|
|||||||
local questId = select(8, GetQuestLogTitle(index))
|
local questId = select(8, GetQuestLogTitle(index))
|
||||||
if questId == 0 then
|
if questId == 0 then
|
||||||
-- When an objective progresses in TBC "index" is the questId, but when a quest is manually removed from
|
-- When an objective progresses in TBC "index" is the questId, but when a quest is manually removed from
|
||||||
-- the quest watch (e.g. shift clicking it in the quest log) "index" is the questLogIndex.
|
-- the quest watch (e.g. shift clicking it in the quest log) "index" is the questLogIndex.
|
||||||
questId = index
|
-- We should NOT untrack when Blizzard calls this with a questId internally during objective updates.
|
||||||
|
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTracker.RemoveQuestWatch] - Internal Blizzard update, skipping untrack for ID:", index)
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if questId then
|
if questId then
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ local WOW_PROJECT_CLASSIC = QuestieCompat.WOW_PROJECT_CLASSIC
|
|||||||
local WOW_PROJECT_BURNING_CRUSADE_CLASSIC = QuestieCompat.WOW_PROJECT_BURNING_CRUSADE_CLASSIC
|
local WOW_PROJECT_BURNING_CRUSADE_CLASSIC = QuestieCompat.WOW_PROJECT_BURNING_CRUSADE_CLASSIC
|
||||||
local WOW_PROJECT_WRATH_CLASSIC = QuestieCompat.WOW_PROJECT_WRATH_CLASSIC
|
local WOW_PROJECT_WRATH_CLASSIC = QuestieCompat.WOW_PROJECT_WRATH_CLASSIC
|
||||||
local WOW_PROJECT_ID = QuestieCompat.WOW_PROJECT_ID
|
local WOW_PROJECT_ID = QuestieCompat.WOW_PROJECT_ID
|
||||||
|
-- Fix #3: Use QuestieCompat.C_Seasons instead of bare _G[C_Seasons]. On old clients
|
||||||
|
-- where C_Seasons is absent, QuestieCompat provides a safe polyfill (always returns
|
||||||
|
-- false/0). On modern clients the native is used directly.
|
||||||
|
local C_Seasons = QuestieCompat.C_Seasons
|
||||||
|
|
||||||
-- Check addon is not renamed to avoid conflicts in global name space.
|
-- Check addon is not renamed to avoid conflicts in global name space.
|
||||||
if (not QuestieCompat.Is335) and addonName ~= "Questie" then
|
if (not QuestieCompat.Is335) and addonName ~= "Questie" then
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
## Notes-esES: Ayundante de misión
|
## Notes-esES: Ayundante de misión
|
||||||
## Notes-ptBR: Ajudante de missão
|
## Notes-ptBR: Ajudante de missão
|
||||||
## Notes-frFR: Assistant de quête
|
## Notes-frFR: Assistant de quête
|
||||||
## Version: 1.3.4
|
## Version: 1.3.5
|
||||||
## RequiredDeps:
|
## RequiredDeps:
|
||||||
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu
|
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu
|
||||||
## SavedVariables: QuestieConfig
|
## SavedVariables: QuestieConfig
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
## Notes-esES: Ayundante de misión
|
## Notes-esES: Ayundante de misión
|
||||||
## Notes-ptBR: Ajudante de missão
|
## Notes-ptBR: Ajudante de missão
|
||||||
## Notes-frFR: Assistant de quête
|
## Notes-frFR: Assistant de quête
|
||||||
## Version: 1.3.4
|
## Version: 1.3.5
|
||||||
## RequiredDeps:
|
## RequiredDeps:
|
||||||
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu
|
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu
|
||||||
## SavedVariables: QuestieConfig
|
## SavedVariables: QuestieConfig
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
## Notes-esES: Ayundante de misión
|
## Notes-esES: Ayundante de misión
|
||||||
## Notes-ptBR: Ajudante de missão
|
## Notes-ptBR: Ajudante de missão
|
||||||
## Notes-frFR: Assistant de quête
|
## Notes-frFR: Assistant de quête
|
||||||
## Version: 1.3.4
|
## Version: 1.3.5
|
||||||
## RequiredDeps:
|
## RequiredDeps:
|
||||||
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu, Questie-X-TurtleDB
|
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu, Questie-X-TurtleDB
|
||||||
## SavedVariables: QuestieConfig
|
## SavedVariables: QuestieConfig
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
## Notes-esES: Ayundante de misión
|
## Notes-esES: Ayundante de misión
|
||||||
## Notes-ptBR: Ajudante de missão
|
## Notes-ptBR: Ajudante de missão
|
||||||
## Notes-frFR: Assistant de quête
|
## Notes-frFR: Assistant de quête
|
||||||
## Version: 1.3.4
|
## Version: 1.3.5
|
||||||
## RequiredDeps:
|
## RequiredDeps:
|
||||||
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu, Questie-X-WotLKDB, Questie-X-ClassicDB, Questie-X-TBCDB, Questie-X-TurtleDB, Questie-X-AscensionDB, Questie-X-EbonholdDB
|
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu, Questie-X-WotLKDB, Questie-X-ClassicDB, Questie-X-TBCDB, Questie-X-TurtleDB, Questie-X-AscensionDB, Questie-X-EbonholdDB
|
||||||
## SavedVariables: QuestieConfig, QuestieLearnerDB
|
## SavedVariables: QuestieConfig, QuestieLearnerDB
|
||||||
|
|||||||
@@ -176,6 +176,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<h2 id="v135">v1.3.5 — Comprehensive Taint & Architecture Fixes</h2>
|
||||||
|
<p><em>A comprehensive update resolving 13 distinct issues related to global environment taint, unsafe polyfills, and module stability across all supported Lua environments (5.0, 5.1, 5.2).</em></p>
|
||||||
|
|
||||||
|
<h3>Core & Stability</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>[Taint Resolution]</strong> Addressed 13 distinct taint and architectural issues discovered during a deep code audit.</li>
|
||||||
|
<li><strong>[Taint Fix]</strong> Removed unsafe <code>function Questie:Warning(...)</code> global monkey-patching in <code>QuestieTracker.lua</code> (<code>_InstallMissingQuestLogWarningFilter</code> deleted). Ghost quest iterations now use a safe, two-phase collection and deletion pattern to prevent warning generation upfront.</li>
|
||||||
|
<li><strong>[Global Safety]</strong> <code>hooksecurefunc</code> polyfill in <code>QuestieCompat.lua</code> no longer pollutes the global <code>_G</code> namespace. It runs strictly as a local fallback when the native function is absent.</li>
|
||||||
|
<li><strong>[Global Safety]</strong> Removed bare <code>_G</code> writes for <code>C_Seasons</code> and <code>C_Timer</code>.</li>
|
||||||
|
<li><strong>[Global Safety]</strong> Prevented <code>_G.QuestieX_WotLKDB_Counts</code> from writing to the global namespace in <code>QuestieInit.lua</code>. Counts are now cleanly stored directly on the plugin object (<code>plugin.stats</code>).</li>
|
||||||
|
<li><strong>[Module Safety]</strong> The <code>CreateModule</code> function in <code>QuestieLoader.lua</code> now guards against double-registration of modules, preventing accidental aliasing and overwriting.</li>
|
||||||
|
<li><strong>[Code Cleanup]</strong> Eliminated duplicate shims (<code>string.match</code>, <code>select</code>) from <code>QuestieCompat.lua</code> that were already provided canonically by <code>QuestieLoader.lua</code>.</li>
|
||||||
|
<li><strong>[Code Cleanup]</strong> Replaced misleading, non-looping <code>while n > 0</code> structures in <code>select</code> polyfills with standard <code>do...end</code> blocks.</li>
|
||||||
|
<li><strong>[Diagnostics]</strong> Gated <code>loadstring</code> execution in <code>QuestieInit:LoadDatabase</code>. On modern clients, it now safely blocks and logs an error instead of executing potentially tainted legacy string DBs at runtime.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
<h2 id="v134">v1.3.4 — Taint Analysis & Error Fixes</h2>
|
<h2 id="v134">v1.3.4 — Taint Analysis & Error Fixes</h2>
|
||||||
<p><em>Resolves the initialization error in <code>GameVersionError.lua</code> and implements a version guard to support Classic-era private servers. Also confirms the integrity of the WotLKDB module after a deep taint analysis.</em></p>
|
<p><em>Resolves the initialization error in <code>GameVersionError.lua</code> and implements a version guard to support Classic-era private servers. Also confirms the integrity of the WotLKDB module after a deep taint analysis.</em></p>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user