From b5025f9ec73cc8373ead0dcb5c5c9d04fa6ee746 Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:58:42 -0500 Subject: [PATCH] Gracefully disable pfUI when ClassicAPI is missing Add API_Check.lua as the first TOC entry. When the ClassicAPI DLL is absent or below the minimum version it sets pfUI_disabled and stands up an inert stub so the rest of the addon no-ops instead of flooding load errors: modules and skins register their bodies into no-ops, and the setfenv'd api/lib files run inside an environment where CreateFrame and any missing global resolve to a null object -- so no real frames or live handlers are created and missing API calls just return null. pfUI.lua bails early on pfUI_disabled. --- API_Check.lua | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++ pfUI.lua | 42 ++++++++------------ pfUI.toc | 2 + 3 files changed, 121 insertions(+), 27 deletions(-) create mode 100644 API_Check.lua diff --git a/API_Check.lua b/API_Check.lua new file mode 100644 index 00000000..6d3698bc --- /dev/null +++ b/API_Check.lua @@ -0,0 +1,104 @@ +do + -- ClassicAPI dependency check. + -- pfUI relies pervasively on the modern C_* / SuperWoW / nameplate / focus + -- API surface that ClassicAPI polyfills, so presence is required. + local PFUI_CLASSIC_API_MIN = 11301 -- (X*10000 + Y*100 + Z) + local PFUI_CLASSIC_API_LATEST = PFUI_CLASSIC_API_MIN + local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI" + local PFUI_CLASSIC_API_LATEST_URL = PFUI_CLASSIC_API_WEBSITE .. "/releases/latest" + local function FormatVersion(packed) + local x = math.floor(packed / 10000) + local y = math.floor(math.mod(packed, 10000) / 100) + local z = math.mod(packed, 100) + return string.format("v%d.%d.%d", x, y, z) + end + if not CLASSIC_API_VERSION or CLASSIC_API_VERSION < PFUI_CLASSIC_API_MIN then + local minVersion = FormatVersion(PFUI_CLASSIC_API_MIN) + pfUI_disabled = true + pfUI_locale = {} + pfUI_profiles = {} + pfUI_translation = {} + + -- ClassicAPI is required. Rather than let the ~140 files that follow flood + -- errors as they load, stand up an inert stub so everything no-ops: + -- * modules/skins register their body into a no-op, so it never runs; + -- * the setfenv'd api/lib files run inside `env`, where CreateFrame and + -- any missing global (C_*, SuperWoW, ...) resolve to a null object -- + -- so no real frames or live OnUpdate/OnEvent handlers get created and + -- missing API calls just return null instead of erroring. + local null -- forward declaration so the metamethods can return it + local nullmt = { + __index = function() return null end, + __newindex = function() end, + __call = function() return null end, + __concat = function() return "" end, + __tostring = function() return "" end, + } + for _, op in ipairs({ "__add","__sub","__mul","__div","__mod","__pow","__unm" }) do + nullmt[op] = function() return null end + end + for _, op in ipairs({ "__lt","__le","__eq" }) do nullmt[op] = function() return false end end + null = setmetatable({}, nullmt) + + local realG = getfenv(0) + pfUI = setmetatable({}, { __index = function() return null end }) + pfUI.env = setmetatable({}, { + __index = function(_, k) + if k == "CreateFrame" then return function() return null end end + local v = realG[k] + if v ~= nil then return v end + return null + end, + }) + function pfUI:RegisterModule(_, _, _) end + function pfUI:RegisterSkin(_, _, _) end + function pfUI:GetEnvironment() return pfUI.env end + local detail + if not CLASSIC_API_VERSION then + detail = "The ClassicAPI DLL isn't loaded. The |cff33ffcc!!!ClassicAPI|r addon ships bundled with it -- delete your |cff33ffcc!!!ClassicAPI|r folder and install the latest release from:" + else + detail = "ClassicAPI " .. minVersion .. " or newer is required. Delete your |cff33ffcc!!!ClassicAPI|r folder and reinstall the latest release from:" + end + + local function ShowRequiredPopup() + StaticPopupDialogs["PFUI_CLASSICAPI_REQUIRED"] = { + text = "|cff33ffccpf|cffffffffUI|r has been disabled.\n\n" .. detail, + button1 = OKAY, + hasEditBox = 1, + editBoxWidth = 280, + timeout = 0, + whileDead = 1, + hideOnEscape = 1, + preferredIndex = 3, + OnShow = function() + local editBox = getglobal(this:GetName().."EditBox") + if editBox then + editBox:SetText(PFUI_CLASSIC_API_LATEST_URL) + editBox:HighlightText() + editBox:SetFocus() + end + end, + } + StaticPopup_Show("PFUI_CLASSICAPI_REQUIRED") + DEFAULT_CHAT_FRAME:AddMessage( + "|cff33ffccpf|cffffffffUI|r disabled: " .. detail .. " " .. PFUI_CLASSIC_API_LATEST_URL, + 1, 0.3, 0.3 + ) + end + local loginFrame = CreateFrame("Frame") + loginFrame:RegisterEvent("PLAYER_ENTERING_WORLD") + loginFrame:SetScript("OnEvent", function() + loginFrame:UnregisterEvent("PLAYER_ENTERING_WORLD") + ShowRequiredPopup() + end) + elseif CLASSIC_API_VERSION < PFUI_CLASSIC_API_LATEST then + EventUtil.ContinueOnPlayerLogin(function() + C_Timer.After(8, function() + DEFAULT_CHAT_FRAME:AddMessage( + "|cff33ffccpf|rUI: ClassicAPI " .. FormatVersion(PFUI_CLASSIC_API_LATEST) .. " is available — " .. PFUI_CLASSIC_API_LATEST_URL, + 1, 0.85, 0.3 + ) + end) + end) + end +end diff --git a/pfUI.lua b/pfUI.lua index 476e5c38..07798ba3 100644 --- a/pfUI.lua +++ b/pfUI.lua @@ -3,6 +3,10 @@ function SlashCmdList.RELOAD(msg, editbox) ReloadUI() end +if pfUI_disabled then return end + +local addonName = ... + SLASH_PFUI1 = '/pfui' function SlashCmdList.PFUI(msg, editbox) pfUI.gui:SetShown(not pfUI.gui:IsShown()) @@ -13,7 +17,7 @@ function SlashCmdList.GM(msg, editbox) ToggleHelpFrame(1) end -pfUI = CreateFrame("Frame", nil, UIParent) +local pfUI = CreateFrame("Frame", addonName, UIParent) pfUI:RegisterEvent("ADDON_LOADED") -- setup bootvar @@ -117,35 +121,24 @@ pfUI.env = {} -- the flag; one that lacks it should drop the flag and get the safe fallback. pfUI.handlesHookScript = true -if not pfUI.disabled then - pfUI.events = Mixin({}, CallbackRegistryMixin) - pfUI.events:OnLoad() - pfUI.events:SetUndefinedEventsAllowed(true) -end +pfUI.events = Mixin({}, CallbackRegistryMixin) +pfUI.events:OnLoad() +pfUI.events:SetUndefinedEventsAllowed(true) -- check if macro addons are loaded (disables macrotweak/macroscan) function pfUI:MacroAddonsLoaded() return IsAddOnLoaded("Supermacro") or IsAddOnLoaded("SuperCleveRoidMacros") or IsAddOnLoaded("UltimaMacros") end --- detect current addon path -local tocs = { "", "-master", "-tbc", "-wotlk" } -for _, name in pairs(tocs) do - local current = string.format("pfUI%s", name) - local title = C_AddOns.GetAddOnName(current) - if title then - pfUI.name = current - pfUI.path = "Interface\\AddOns\\" .. current - break - end -end +pfUI.name = addonName +pfUI.path = "Interface\\AddOns\\" .. addonName -- handle/convert media dir paths pfUI.media = setmetatable({}, { __index = function(tab,key) local value = tostring(key) - if strfind(value, "img:") then + if value:find("img:") then value = string.gsub(value, "img:", pfUI.path .. "\\img\\") - elseif strfind(value, "font:") then + elseif value:find("font:") then value = string.gsub(value, "font:", pfUI.path .. "\\fonts\\") else value = string.gsub(value, "Interface\\AddOns\\pfUI\\", pfUI.path .. "\\") @@ -154,9 +147,7 @@ pfUI.media = setmetatable({}, { __index = function(tab,key) return value end}) --- cache client version -local _, _, _, client = GetBuildInfo() -pfUI.client = client or 11200 +pfUI.client = INTERFACE_VERSION -- setup pfUI namespace setmetatable(pfUI.env, {__index = getfenv(0)}) @@ -390,14 +381,13 @@ function pfUI:CheckNewModules() end local function BackwardsCompatRegister(func, arg3) - if arg3 and type(func) == "string" and type(arg3) == "function" and string.find(func, "vanilla") then + if arg3 and type(func) == "string" and type(arg3) == "function" and func:find("vanilla") then return arg3 end return func end function pfUI:RegisterModule(name, func, arg3) - if pfUI.disabled then return end if pfUI.module[name] then return end func = BackwardsCompatRegister(func, arg3) pfUI.module[name] = func @@ -408,7 +398,6 @@ function pfUI:RegisterModule(name, func, arg3) end function pfUI:RegisterSkin(name, func, arg3) - if pfUI.disabled then return end if pfUI.skin[name] then return end func = BackwardsCompatRegister(func, arg3) pfUI.skin[name] = func @@ -429,7 +418,6 @@ function pfUI:LoadSkin(s) end pfUI:SetScript("OnEvent", function() - if pfUI.disabled then return end -- make sure to initialize and set our fonts -- each time an addon got loaded but only @@ -443,7 +431,7 @@ pfUI:SetScript("OnEvent", function() -- "@project-version@" until release tooling substitutes it — mark those -- explicitly as "dev" instead of pretending they're a real numbered build. local raw = tostring(GetAddOnMetadata(pfUI.name, "Version")) - if strfind(raw, "@") then + if raw:find("@") then pfUI.version.major, pfUI.version.minor, pfUI.version.fix = 0, 0, 0 pfUI.version.string = "dev" else diff --git a/pfUI.toc b/pfUI.toc index 2c3713e4..a19eebd5 100644 --- a/pfUI.toc +++ b/pfUI.toc @@ -8,6 +8,8 @@ ## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB ## X-Website: https://github.com/brues-code/pfUI +API_Check.lua + pfUI.lua init\env.xml