You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

471 lines
14 KiB

if not WeakAuras.IsCorrectVersion() then return end
local AddonName, Private = ...
local WeakAuras = WeakAuras
local L = WeakAuras.L
local LCD
if WeakAuras.IsClassic() then
LCD = LibStub("LibClassicDurations")
LCD:RegisterFrame("WeakAuras")
end
local UnitAura = UnitAura
-- Unit Aura functions that return info about the first Aura matching the spellName or spellID given on the unit.
local WA_GetUnitAura = function(unit, spell, filter)
if filter and not filter:upper():find("FUL") then
filter = filter.."|HELPFUL"
end
for i = 1, 255 do
local name, _, _, _, _, _, _, _, _, spellId = UnitAura(unit, i, filter)
if not name then return end
if spell == spellId or spell == name then
return UnitAura(unit, i, filter)
end
end
end
if WeakAuras.IsClassic() then
local WA_GetUnitAuraBase = WA_GetUnitAura
WA_GetUnitAura = function(unit, spell, filter)
local name, icon, count, debuffType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId, canApplyAura, isBossDebuff, castByPlayer, nameplateShowAll, timeMod = WA_GetUnitAuraBase(unit, spell, filter)
if spellId then
local durationNew, expirationTimeNew = LCD:GetAuraDurationByUnit(unit, spellId, source, name)
if duration == 0 and durationNew then
duration = durationNew
expirationTime = expirationTimeNew
end
end
return name, icon, count, debuffType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId, canApplyAura, isBossDebuff, castByPlayer, nameplateShowAll, timeMod
end
end
local WA_GetUnitBuff = function(unit, spell, filter)
filter = filter and filter.."|HELPFUL" or "HELPFUL"
return WA_GetUnitAura(unit, spell, filter)
end
local WA_GetUnitDebuff = function(unit, spell, filter)
filter = filter and filter.."|HARMFUL" or "HARMFUL"
return WA_GetUnitAura(unit, spell, filter)
end
-- Function to assist iterating group members whether in a party or raid.
local WA_IterateGroupMembers = function(reversed, forceParty)
local unit = (not forceParty and IsInRaid()) and 'raid' or 'party'
local numGroupMembers = unit == 'party' and GetNumSubgroupMembers() or GetNumGroupMembers()
local i = reversed and numGroupMembers or (unit == 'party' and 0 or 1)
return function()
local ret
if i == 0 and unit == 'party' then
ret = 'player'
elseif i <= numGroupMembers and i > 0 then
ret = unit .. i
end
i = i + (reversed and -1 or 1)
return ret
end
end
-- Wrapping a unit's name in its class colour is very common in custom Auras
local WA_ClassColorName = function(unit)
if unit and UnitExists(unit) then
local name = UnitName(unit)
local _, class = UnitClass(unit)
if not class then
return name
else
local classData = (CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS)[class]
local coloredName = ("|c%s%s|r"):format(classData.colorStr, name)
return coloredName
end
else
return "" -- ¯\_(ツ)_/¯
end
end
WeakAuras.WA_ClassColorName = WA_ClassColorName
-- UTF-8 Sub is pretty commonly needed
local WA_Utf8Sub = function(input, size)
local output = ""
if type(input) ~= "string" then
return output
end
local i = 1
while (size > 0) do
local byte = input:byte(i)
if not byte then
return output
end
if byte < 128 then
-- ASCII byte
output = output .. input:sub(i, i)
size = size - 1
elseif byte < 192 then
-- Continuation bytes
output = output .. input:sub(i, i)
elseif byte < 244 then
-- Start bytes
output = output .. input:sub(i, i)
size = size - 1
end
i = i + 1
end
-- Add any bytes that are part of the sequence
while (true) do
local byte = input:byte(i)
if byte and byte >= 128 and byte < 192 then
output = output .. input:sub(i, i)
else
break
end
i = i + 1
end
return output
end
WeakAuras.WA_Utf8Sub = WA_Utf8Sub
local LCG = LibStub("LibCustomGlow-1.0")
WeakAuras.ShowOverlayGlow = LCG.ButtonGlow_Start
WeakAuras.HideOverlayGlow = LCG.ButtonGlow_Stop
local LGF = LibStub("LibGetFrame-1.0")
WeakAuras.GetUnitFrame = LGF.GetUnitFrame
WeakAuras.GetUnitNameplate = function(unit)
if Private.multiUnitUnits.nameplate[unit] then
return LGF.GetUnitNameplate(unit)
end
end
local blockedFunctions = {
-- Lua functions that may allow breaking out of the environment
getfenv = true,
setfenv = true,
loadstring = true,
pcall = true,
xpcall = true,
-- blocked WoW API
SendMail = true,
SetTradeMoney = true,
AddTradeMoney = true,
PickupTradeMoney = true,
PickupPlayerMoney = true,
TradeFrame = true,
MailFrame = true,
EnumerateFrames = true,
RunScript = true,
AcceptTrade = true,
SetSendMailMoney = true,
EditMacro = true,
DevTools_DumpCommand = true,
hash_SlashCmdList = true,
CreateMacro = true,
SetBindingMacro = true,
GuildDisband = true,
GuildUninvite = true,
securecall = true,
DeleteCursorItem = true,
}
local blockedTables = {
SlashCmdList = true,
SendMailMailButton = true,
SendMailMoneyGold = true,
MailFrameTab2 = true,
}
local aura_environments = {}
-- nil == Not initiliazed
-- 1 == config initialized
-- 2 == fully initialized
local environment_initialized = {}
function Private.IsEnvironmentInitialized(id)
return environment_initialized[id] == 2
end
function Private.DeleteAuraEnvironment(id)
aura_environments[id] = nil
environment_initialized[id] = nil
end
function Private.RenameAuraEnvironment(oldid, newid)
aura_environments[oldid], aura_environments[newid] = nil, aura_environments[oldid]
environment_initialized[oldid], environment_initialized[newid] = nil, environment_initialized[oldid]
end
local current_uid = nil
local current_aura_env = nil
-- Stack of of aura environments/uids, allows use of recursive aura activations through calls to WeakAuras.ScanEvents().
local aura_env_stack = {}
function Private.ClearAuraEnvironment(id)
environment_initialized[id] = nil;
end
function Private.ActivateAuraEnvironmentForRegion(region, onlyConfig)
Private.ActivateAuraEnvironment(region.id, region.cloneId, region.state, region.states, onlyConfig)
end
function Private.ActivateAuraEnvironment(id, cloneId, state, states, onlyConfig)
local data = WeakAuras.GetData(id)
local region = WeakAuras.GetRegion(id, cloneId)
if not data then
-- Pop the last aura_env from the stack, and update current_aura_env appropriately.
tremove(aura_env_stack)
if aura_env_stack[#aura_env_stack] then
current_aura_env, current_uid = unpack(aura_env_stack[#aura_env_stack])
else
current_aura_env = nil
current_uid = nil
end
else
-- Existing config is initialized to a high enough value
if environment_initialized[id] == 2 or (onlyConfig and environment_initialized[id] == 1) then
-- Point the current environment to the correct table
current_uid = data.uid
current_aura_env = aura_environments[id]
current_aura_env.id = id
current_aura_env.cloneId = cloneId
current_aura_env.state = state
current_aura_env.states = states
current_aura_env.region = region
-- Push the new environment onto the stack
tinsert(aura_env_stack, {current_aura_env, data.uid})
elseif onlyConfig then
environment_initialized[id] = 1
aura_environments[id] = {}
current_uid = data.uid
current_aura_env = aura_environments[id]
current_aura_env.id = id
current_aura_env.cloneId = cloneId
current_aura_env.state = state
current_aura_env.states = states
current_aura_env.region = region
tinsert(aura_env_stack, {current_aura_env, data.uid})
if not data.controlledChildren then
current_aura_env.config = CopyTable(data.config)
end
else
-- Either this aura environment has not yet been initialized, or it was reset via an edit in WeakaurasOptions
environment_initialized[id] = 2
aura_environments[id] = aura_environments[id] or {}
current_uid = data.uid
current_aura_env = aura_environments[id]
current_aura_env.id = id
current_aura_env.cloneId = cloneId
current_aura_env.state = state
current_aura_env.states = states
current_aura_env.region = region
-- push new environment onto the stack
tinsert(aura_env_stack, {current_aura_env, data.uid})
if data.controlledChildren then
current_aura_env.child_envs = {}
for dataIndex, childID in ipairs(data.controlledChildren) do
local childData = WeakAuras.GetData(childID)
if childData then
if not environment_initialized[childID] then
Private.ActivateAuraEnvironment(childID)
Private.ActivateAuraEnvironment()
end
current_aura_env.child_envs[dataIndex] = aura_environments[childID]
end
end
else
if environment_initialized[id] == 1 then
-- Already done
else
current_aura_env.config = CopyTable(data.config)
end
end
-- Finally, run the init function if supplied
local actions = data.actions.init
if(actions and actions.do_custom and actions.custom) then
local func = Private.customActionsFunctions[id]["init"]
if func then
xpcall(func, geterrorhandler())
end
end
end
end
end
local function blocked(key)
Private.AuraWarnings.UpdateWarning(current_uid, "SandboxForbidden", "error",
string.format(L["Forbidden function or table: %s"], key))
end
local function MakeReadOnly(input, options)
return setmetatable({},
{
__index = function(t, k)
if options.blockedFunctions[k] then
options.blocked(k)
return function() end
elseif options.blockedTables[k] then
options.blocked(k)
return {}
elseif options.override[k] then
return options.override[k]
else
return input[k]
end
end,
__newindex = options.setBlocked,
__metatable = false
})
end
local FakeWeakAurasMixin = {
blockedFunctions = {
-- Other addons might use these, so before moving them to the Private space, we need
-- to discuss these. But Auras have no purpose for calling these
Add = true,
AddMany = true,
AddManyFromAddons = true,
Delete = true,
HideOptions = true,
Rename = true,
NewAura = true,
OptionsFrame = true,
RegisterAddon = true,
RegisterDisplay = true,
RegisterRegionOptions = true,
RegisterSubRegionOptions = true,
RegisterSubRegionType = true,
RegisterRegionType = true,
RegisterTriggerSystem = true,
RegisterTriggerSystemOptions = true,
ShowOptions = true,
-- Note these shouldn't exist in the WeakAuras namespace, but moving them takes a bit of effort,
-- so for now just block them and clean them up later
CollisionResolved = true,
ClearAndUpdateOptions = true,
CloseCodeReview = true,
CloseImportExport = true,
CreateTemplateView = true,
FillOptions = true,
FindUnusedId = true,
GetMoverSizerId = true,
GetDisplayButton = true,
Import = true,
NewDisplayButton = true,
OpenCodeReview = true,
PickDisplay = true,
SetMoverSizer = true,
SetImporting = true,
ToggleOptions = true,
UpdateDisplayButton = true,
UpdateGroupOrders = true,
UpdateThumbnail = true,
validate = true,
getDefaultGlow = true,
},
blockedTables = {
AuraWarnings = true,
ModelPaths = true,
regionPrototype = true,
-- Note these shouldn't exist in the WeakAuras namespace, but moving them takes a bit of effort,
-- so for now just block them and clean them up later
data_stub = true,
displayButtons = true,
regionTypes = true,
regionOptions = true,
spellCache = true,
triggerTemplates = true,
frames = true,
loadFrame = true,
unitLoadFrame = true,
importDisplayButtons = true,
loaded = true
},
override = {
me = GetUnitName("player", true),
myGUID = UnitGUID("player")
},
blocked = blocked,
setBlocked = function()
Private.AuraWarnings.UpdateWarning(current_uid, "FakeWeakAurasSet", "error",
L["Writing to the WeakAuras table is not allowed."], true)
end
}
local FakeWeakAuras = MakeReadOnly(WeakAuras, FakeWeakAurasMixin)
local overridden = {
WA_GetUnitAura = WA_GetUnitAura,
WA_GetUnitBuff = WA_GetUnitBuff,
WA_GetUnitDebuff = WA_GetUnitDebuff,
WA_IterateGroupMembers = WA_IterateGroupMembers,
WA_ClassColorName = WA_ClassColorName,
WA_Utf8Sub = WA_Utf8Sub,
ActionButton_ShowOverlayGlow = WeakAuras.ShowOverlayGlow,
ActionButton_HideOverlayGlow = WeakAuras.HideOverlayGlow,
WeakAuras = FakeWeakAuras
}
local env_getglobal
local exec_env = setmetatable({},
{
__index = function(t, k)
if k == "_G" then
return t
elseif k == "getglobal" then
return env_getglobal
elseif k == "aura_env" then
return current_aura_env
elseif blockedFunctions[k] then
blocked(k)
return function() end
elseif blockedTables[k] then
blocked(k)
return {}
elseif overridden[k] then
return overridden[k]
else
return _G[k]
end
end,
__newindex = function(table, key, value)
if _G[key] then
Private.AuraWarnings.UpdateWarning(current_uid, "OverridingGlobal", "warning",
string.format(L["The aura has overwritten the global '%s', this might affect other auras."], key))
end
rawset(table, key, value)
end,
__metatable = false
})
function env_getglobal(k)
return exec_env[k]
end
local function_cache = {}
function WeakAuras.LoadFunction(string, id, inTrigger)
if function_cache[string] then
return function_cache[string]
else
local loadedFunction, errorString = loadstring(string, "Error in: " .. (id or "Unknown") .. (inTrigger and ("':'".. inTrigger) or ""))
if errorString then
print(errorString)
else
setfenv(loadedFunction, exec_env)
local success, func = pcall(assert(loadedFunction))
if success then
function_cache[string] = func
return func
end
end
end
end
function Private.GetSanitizedGlobal(key)
return exec_env[key]
end