chore: release v1.3.5 - Comprehensive Taint & Architecture Fixes
This commit is contained in:
@@ -1,5 +1,21 @@
|
||||
# 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
|
||||
|
||||
*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">
|
||||
<Script file="..\Libs\LibStub\LibStub.lua"/>
|
||||
<Include file="Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml"/>
|
||||
<Include file="Libs\AceAddon-3.0\AceAddon-3.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\AceEvent-3.0\AceEvent-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\AceTimer-3.0\AceTimer-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\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\AceLocale-3.0\AceLocale-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\AceConfigRegistry-3.0\AceConfigRegistry-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"/>
|
||||
<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="Libs\AceSerializer-3.0\AceSerializer-3.0.xml"/-->
|
||||
<!-- 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"/>
|
||||
<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"/-->
|
||||
|
||||
|
||||
@@ -1,37 +1,2 @@
|
||||
---@type l10n
|
||||
local l10n = QuestieLoader:ImportModule("l10n")
|
||||
|
||||
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("---------------------------------")
|
||||
-- This file is intentionally left empty to remove the "unsupported WoW game client" error popup.
|
||||
-- Questie-X is now intended to be universal.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
if not table.getn then
|
||||
local loadFunc = loadstring or load
|
||||
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
|
||||
|
||||
@@ -20,6 +20,7 @@ if not math.mod then
|
||||
end
|
||||
|
||||
-- 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
|
||||
string.match = function(str, pattern, init)
|
||||
if not str then return nil end
|
||||
@@ -41,49 +42,51 @@ if not string.gmatch then
|
||||
end
|
||||
|
||||
-- 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
|
||||
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
|
||||
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
|
||||
-- Count trailing non-nil values (up to 25 args).
|
||||
do
|
||||
if a25 ~= nil then return 25 end
|
||||
if a24 ~= nil then return 24 end
|
||||
if a23 ~= nil then return 23 end
|
||||
if a22 ~= nil then return 22 end
|
||||
if a21 ~= nil then return 21 end
|
||||
if a20 ~= nil then return 20 end
|
||||
if a19 ~= nil then return 19 end
|
||||
if a18 ~= nil then return 18 end
|
||||
if a17 ~= nil then return 17 end
|
||||
if a16 ~= nil then return 16 end
|
||||
if a15 ~= nil then return 15 end
|
||||
if a14 ~= nil then return 14 end
|
||||
if a13 ~= nil then return 13 end
|
||||
if a12 ~= nil then return 12 end
|
||||
if a11 ~= nil then return 11 end
|
||||
if a10 ~= nil then return 10 end
|
||||
if a9 ~= nil then return 9 end
|
||||
if a8 ~= nil then return 8 end
|
||||
if a7 ~= nil then return 7 end
|
||||
if a6 ~= nil then return 6 end
|
||||
if a5 ~= nil then return 5 end
|
||||
if a4 ~= nil then return 4 end
|
||||
if a3 ~= nil then return 3 end
|
||||
if a2 ~= nil then return 2 end
|
||||
if 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 == 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
|
||||
@@ -117,24 +120,30 @@ QuestieLoader._modules = modules -- store reference so modules can be iterated f
|
||||
---@param name `T` @Module name
|
||||
---@return T|{ private: table } @Module reference
|
||||
function QuestieLoader:CreateModule(name)
|
||||
if (not modules[name]) then
|
||||
modules[name] = { private = {} }
|
||||
return modules[name]
|
||||
else
|
||||
-- Fix #12: Error on double-registration so aliasing bugs are caught early.
|
||||
if modules[name] and modules[name]._defined then
|
||||
-- Print a debug message rather than hard-error so it doesn't break live servers
|
||||
-- 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]
|
||||
end
|
||||
if not modules[name] then
|
||||
modules[name] = { private = {} }
|
||||
end
|
||||
modules[name]._defined = true
|
||||
return modules[name]
|
||||
end
|
||||
|
||||
---@generic T
|
||||
---@param name `T` @Module name
|
||||
---@return T|{ private: table } @Module reference
|
||||
function QuestieLoader:ImportModule(name)
|
||||
if (not modules[name]) then
|
||||
if not modules[name] then
|
||||
modules[name] = { private = {} }
|
||||
return modules[name]
|
||||
else
|
||||
return modules[name]
|
||||
end
|
||||
return modules[name]
|
||||
end
|
||||
|
||||
function QuestieLoader:PopulateGlobals() -- called when debugging is enabled
|
||||
|
||||
@@ -487,25 +487,14 @@ function QuestieOptions.tabs.database:Initialize()
|
||||
return n
|
||||
end
|
||||
|
||||
-- For pull-type plugins (e.g. WotLKDB) that never call InjectDatabase,
|
||||
-- stats stay at 0. Fall back to QuestieX_WotLKDB_Counts which is set
|
||||
-- by LoadBaseDB() before the raw tables are compiled away.
|
||||
-- Fix #11: _G.QuestieX_WotLKDB_Counts is no longer written to avoid
|
||||
-- global namespace taint. Stats are pushed directly onto plugin.stats
|
||||
-- by LoadBaseDB() / UpdateWotLKDBStats(), so read from there.
|
||||
local function GetPluginCounts(pluginName, stats)
|
||||
local q = stats.QUEST or 0
|
||||
local n = stats.NPC or 0
|
||||
local o = stats.OBJECT 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
|
||||
end
|
||||
|
||||
|
||||
+44
-97
@@ -3,84 +3,10 @@
|
||||
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
|
||||
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)
|
||||
if not C_Seasons then
|
||||
C_Seasons = {
|
||||
---[C_Seasons.HasActiveSeason Documentation](https://wowpedia.fandom.com/wiki/API_C_Seasons.HasActiveSeason)
|
||||
---Returns true if the player is on a seasonal realm.
|
||||
HasActiveSeason = function()
|
||||
return false
|
||||
end,
|
||||
---[C_Seasons.GetActiveSeason Documentation](https://wowpedia.fandom.com/wiki/API_C_Seasons.GetActiveSeason)
|
||||
---Returns the ID of the season that is active on the current realm.
|
||||
GetActiveSeason = function()
|
||||
return 0
|
||||
end
|
||||
}
|
||||
end
|
||||
-- Fix #3: Do NOT write to bare _G["C_Seasons"] — store in QuestieCompat namespace only.
|
||||
-- All callers should use QuestieCompat.C_Seasons.
|
||||
QuestieCompat.C_Seasons = C_Seasons or {
|
||||
---[C_Seasons.HasActiveSeason Documentation](https://wowpedia.fandom.com/wiki/API_C_Seasons.HasActiveSeason)
|
||||
---Returns true if the player is on a seasonal realm.
|
||||
HasActiveSeason = function()
|
||||
return false
|
||||
end,
|
||||
---[C_Seasons.GetActiveSeason Documentation](https://wowpedia.fandom.com/wiki/API_C_Seasons.GetActiveSeason)
|
||||
---Returns the ID of the season that is active on the current realm.
|
||||
GetActiveSeason = function()
|
||||
return 0
|
||||
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
|
||||
if not TooltipBackdropTemplateMixin then
|
||||
@@ -123,8 +49,17 @@ end
|
||||
-- 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
|
||||
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
|
||||
if type(arg1) == "string" then
|
||||
t = _G
|
||||
@@ -135,6 +70,7 @@ if not hooksecurefunc then
|
||||
name = arg2
|
||||
func = arg3
|
||||
end
|
||||
if not (t and name and func) then return end
|
||||
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)
|
||||
local ret1, ret2, ret3, ret4
|
||||
@@ -145,16 +81,27 @@ if not hooksecurefunc then
|
||||
return ret1, ret2, ret3, ret4
|
||||
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
|
||||
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 tickers = {}
|
||||
|
||||
TickerFrame:SetScript("OnUpdate", function()
|
||||
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]
|
||||
if not ticker._cancelled then
|
||||
ticker._elapsed = ticker._elapsed + elapsed
|
||||
@@ -172,10 +119,11 @@ if not C_Timer then
|
||||
else
|
||||
table.remove(tickers, i)
|
||||
end
|
||||
i = i - 1
|
||||
end
|
||||
end)
|
||||
|
||||
C_Timer = {
|
||||
QuestieCompat.C_Timer = {
|
||||
After = function(duration, callback)
|
||||
table.insert(tickers, {
|
||||
_duration = duration,
|
||||
@@ -201,7 +149,6 @@ if not C_Timer then
|
||||
end
|
||||
}
|
||||
end
|
||||
QuestieCompat.C_Timer = C_Timer
|
||||
|
||||
---[SetMinResize Documentation](https://wowpedia.fandom.com/wiki/API_Frame_SetMinResize)
|
||||
---[SetMaxResize Documentation](https://wowpedia.fandom.com/wiki/API_Frame_SetMaxResize)
|
||||
|
||||
+18
-5
@@ -411,6 +411,21 @@ end
|
||||
|
||||
function QuestieInit:LoadDatabase(key)
|
||||
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()
|
||||
local fn, loadErr = loadstring(QuestieDB[key])
|
||||
coYield()
|
||||
@@ -449,7 +464,8 @@ function QuestieInit:UpdateWotLKDBStats()
|
||||
OBJECT = _countTable(_G["QuestieX_WotLKDB_object"]),
|
||||
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")
|
||||
if QuestiePluginAPI then
|
||||
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))
|
||||
|
||||
-- Push counts directly onto the plugin object so the Database panel always shows them,
|
||||
-- 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
|
||||
-- Fix #11: second site — push counts only to plugin object, not to _G.
|
||||
local QuestiePluginAPI = QuestieLoader:ImportModule("QuestiePluginAPI")
|
||||
if QuestiePluginAPI then
|
||||
local wotlkPlugin = QuestiePluginAPI:GetPlugin("WotLKDB")
|
||||
|
||||
@@ -115,6 +115,9 @@ function QuestieTracker:PruneGhostQuests()
|
||||
end
|
||||
|
||||
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
|
||||
local keyId = tonumber(key)
|
||||
@@ -124,10 +127,8 @@ function QuestieTracker:PruneGhostQuests()
|
||||
if questId and type(q) ~= "table" then
|
||||
local quest = QuestieDB and QuestieDB.GetQuest and QuestieDB.GetQuest(questId) or nil
|
||||
if quest then
|
||||
if key ~= questId then
|
||||
QuestiePlayer.currentQuestlog[key] = nil
|
||||
end
|
||||
QuestiePlayer.currentQuestlog[questId] = quest
|
||||
-- Schedule the repair as a removal + reinsertion by key; done below.
|
||||
toRemove[#toRemove + 1] = { key = key, questId = questId, quest = quest, repair = true }
|
||||
q = quest
|
||||
keyId = questId
|
||||
end
|
||||
@@ -136,22 +137,36 @@ function QuestieTracker:PruneGhostQuests()
|
||||
if questId then
|
||||
local idx = GetQuestLogIndexByID and GetQuestLogIndexByID(questId)
|
||||
if (not idx) or idx <= 0 then
|
||||
QuestiePlayer.currentQuestlog[key] = nil
|
||||
if key ~= questId then
|
||||
QuestiePlayer.currentQuestlog[questId] = nil
|
||||
end
|
||||
|
||||
_RemoveQuestIdFromCharTables(questId)
|
||||
|
||||
if QuestieTooltips and QuestieTooltips.RemoveQuest then
|
||||
QuestieTooltips:RemoveQuest(questId)
|
||||
end
|
||||
|
||||
removedAny = true
|
||||
-- Fix #1: Guard the questId before accessing so we never generate
|
||||
-- the "doesn't exist in Game's quest log" warning at all.
|
||||
toRemove[#toRemove + 1] = { key = key, questId = questId, clean = true }
|
||||
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
|
||||
end
|
||||
|
||||
@@ -173,23 +188,14 @@ local function _InstallQuestLogUpdateListener()
|
||||
QuestieTracker._questLogUpdateListenerInstalled = true
|
||||
end
|
||||
|
||||
local function _InstallMissingQuestLogWarningFilter()
|
||||
if QuestieTracker._missingQuestLogWarningFilterInstalled then return end
|
||||
if not Questie or type(Questie.Warning) ~= "function" then return end
|
||||
|
||||
local orig = Questie.Warning
|
||||
QuestieTracker._origWarning = orig
|
||||
|
||||
function Questie:Warning(msg, ...)
|
||||
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
|
||||
-- Fix #1: _InstallMissingQuestLogWarningFilter is REMOVED.
|
||||
-- The old implementation did `function Questie:Warning(...)` which is a raw
|
||||
-- method replacement on the globally-registered Questie object, tainting it.
|
||||
-- The warnings it suppressed are now prevented upstream in PruneGhostQuests
|
||||
-- via the two-phase (collect then delete) pattern, so ghost-quest log index
|
||||
-- warnings are never generated in the first place.
|
||||
-- If future callers need this sentinel, use hooksecurefunc(Questie,"Warning",...)
|
||||
-- which is safe on all clients that support it.
|
||||
|
||||
|
||||
function QuestieTracker.Initialize()
|
||||
@@ -247,7 +253,8 @@ function QuestieTracker.Initialize()
|
||||
TrackerLinePool.Initialize(trackerQuestFrame)
|
||||
-- Keep tracker in sync for quests that auto-complete/disappear
|
||||
_InstallQuestLogUpdateListener()
|
||||
_InstallMissingQuestLogWarningFilter()
|
||||
-- Note: _InstallMissingQuestLogWarningFilter was removed (fix #1).
|
||||
-- Ghost quest warnings are now prevented upstream in PruneGhostQuests.
|
||||
|
||||
TrackerFadeTicker.Initialize(trackerBaseFrame, trackerHeaderFrame)
|
||||
QuestieTracker.started = true
|
||||
@@ -2179,10 +2186,9 @@ function QuestieTracker:HookBaseTracker()
|
||||
end
|
||||
|
||||
if not Questie.db.profile.autoTrackQuests then
|
||||
return Questie.db.char.TrackedQuests[questId or -1]
|
||||
return questId and Questie.db.char.TrackedQuests[questId]
|
||||
else
|
||||
return questId and QuestiePlayer.currentQuestlog[questId] and
|
||||
(not Questie.db.char.AutoUntrackedQuests[questId])
|
||||
return questId and QuestiePlayer.currentQuestlog[questId] and (not Questie.db.char.AutoUntrackedQuests[questId])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2284,8 +2290,10 @@ function QuestieTracker.RemoveQuestWatch(index, isQuestie)
|
||||
local questId = select(8, GetQuestLogTitle(index))
|
||||
if questId == 0 then
|
||||
-- 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.
|
||||
questId = index
|
||||
-- the quest watch (e.g. shift clicking it in the quest log) "index" is the questLogIndex.
|
||||
-- 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
|
||||
|
||||
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_WRATH_CLASSIC = QuestieCompat.WOW_PROJECT_WRATH_CLASSIC
|
||||
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.
|
||||
if (not QuestieCompat.Is335) and addonName ~= "Questie" then
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
## Notes-esES: Ayundante de misión
|
||||
## Notes-ptBR: Ajudante de missão
|
||||
## Notes-frFR: Assistant de quête
|
||||
## Version: 1.3.4
|
||||
## Version: 1.3.5
|
||||
## RequiredDeps:
|
||||
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu
|
||||
## SavedVariables: QuestieConfig
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
## Notes-esES: Ayundante de misión
|
||||
## Notes-ptBR: Ajudante de missão
|
||||
## Notes-frFR: Assistant de quête
|
||||
## Version: 1.3.4
|
||||
## Version: 1.3.5
|
||||
## RequiredDeps:
|
||||
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu
|
||||
## SavedVariables: QuestieConfig
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
## Notes-esES: Ayundante de misión
|
||||
## Notes-ptBR: Ajudante de missão
|
||||
## Notes-frFR: Assistant de quête
|
||||
## Version: 1.3.4
|
||||
## Version: 1.3.5
|
||||
## RequiredDeps:
|
||||
## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu, Questie-X-TurtleDB
|
||||
## SavedVariables: QuestieConfig
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
## Notes-esES: Ayundante de misión
|
||||
## Notes-ptBR: Ajudante de missão
|
||||
## Notes-frFR: Assistant de quête
|
||||
## Version: 1.3.4
|
||||
## Version: 1.3.5
|
||||
## 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
|
||||
## SavedVariables: QuestieConfig, QuestieLearnerDB
|
||||
|
||||
@@ -176,6 +176,24 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<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