flickerstreak@1: --[[ flickerstreak@1: Name: AceHook-2.1 flickerstreak@1: Revision: $Rev: 19980 $ flickerstreak@1: Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) flickerstreak@1: Inspired By: Ace 1.x by Turan (turan@gryphon.com) flickerstreak@1: Website: http://www.wowace.com/ flickerstreak@1: Documentation: http://www.wowace.com/index.php/AceHook-2.1 flickerstreak@1: SVN: http://svn.wowace.com/root/trunk/Ace2/AceHook-2.1 flickerstreak@1: Description: Mixin to allow for safe hooking of functions, methods, and scripts. flickerstreak@1: Dependencies: AceLibrary, AceOO-2.0 flickerstreak@1: ]] flickerstreak@1: flickerstreak@1: local MAJOR_VERSION = "AceHook-2.1" flickerstreak@1: local MINOR_VERSION = "$Revision: 19980 $" flickerstreak@1: flickerstreak@1: -- This ensures the code is only executed if the libary doesn't already exist, or is a newer version flickerstreak@1: if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end flickerstreak@1: if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end flickerstreak@1: flickerstreak@1: if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end flickerstreak@1: flickerstreak@1: --[[--------------------------------------------------------------------------------- flickerstreak@1: Create the library object flickerstreak@1: ----------------------------------------------------------------------------------]] flickerstreak@1: flickerstreak@1: local AceOO = AceLibrary:GetInstance("AceOO-2.0") flickerstreak@1: local AceHook = AceOO.Mixin { flickerstreak@1: "Hook", flickerstreak@1: "HookScript", flickerstreak@1: "SecureHook", flickerstreak@1: "Unhook", flickerstreak@1: "UnhookAll", flickerstreak@1: "HookReport", flickerstreak@1: "IsHooked", flickerstreak@1: } flickerstreak@1: flickerstreak@1: --[[--------------------------------------------------------------------------------- flickerstreak@1: Library Definitions flickerstreak@1: ----------------------------------------------------------------------------------]] flickerstreak@1: flickerstreak@1: local protectedScripts = { flickerstreak@1: OnClick = true, flickerstreak@1: } flickerstreak@1: flickerstreak@1: local _G = getfenv(0) flickerstreak@1: flickerstreak@1: local handlers, scripts, actives, registry, onceSecure flickerstreak@1: flickerstreak@1: --[[--------------------------------------------------------------------------------- flickerstreak@1: Private definitions (Not exposed) flickerstreak@1: ----------------------------------------------------------------------------------]] flickerstreak@1: flickerstreak@1: local new, del flickerstreak@1: do flickerstreak@1: local list = setmetatable({}, {__mode = "k"}) flickerstreak@1: function new() flickerstreak@1: local t = next(list) flickerstreak@1: if not t then flickerstreak@1: return {} flickerstreak@1: end flickerstreak@1: list[t] = nil flickerstreak@1: return t flickerstreak@1: end flickerstreak@1: flickerstreak@1: function del(t) flickerstreak@1: setmetatable(t, nil) flickerstreak@1: for k in pairs(t) do flickerstreak@1: t[k] = nil flickerstreak@1: end flickerstreak@1: list[t] = true flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: local function createFunctionHook(self, func, handler, orig, secure) flickerstreak@1: if not secure then flickerstreak@1: if type(handler) == "string" then flickerstreak@1: -- The handler is a method, need to self it flickerstreak@1: local uid flickerstreak@1: uid = function(...) flickerstreak@1: if actives[uid] then flickerstreak@1: return self[handler](self, ...) flickerstreak@1: else flickerstreak@1: return orig(...) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: return uid flickerstreak@1: else flickerstreak@1: -- The handler is a function, just call it flickerstreak@1: local uid flickerstreak@1: uid = function(...) flickerstreak@1: if actives[uid] then flickerstreak@1: return handler(...) flickerstreak@1: else flickerstreak@1: return orig(...) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: return uid flickerstreak@1: end flickerstreak@1: else flickerstreak@1: -- secure hooks don't call the original method flickerstreak@1: if type(handler) == "string" then flickerstreak@1: -- The handler is a method, need to self it flickerstreak@1: local uid flickerstreak@1: uid = function(...) flickerstreak@1: if actives[uid] then flickerstreak@1: return self[handler](self, ...) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: return uid flickerstreak@1: else flickerstreak@1: -- The handler is a function, just call it flickerstreak@1: local uid flickerstreak@1: uid = function(...) flickerstreak@1: if actives[uid] then flickerstreak@1: return handler(...) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: return uid flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: local function createMethodHook(self, object, method, handler, orig, secure) flickerstreak@1: if not secure then flickerstreak@1: if type(handler) == "string" then flickerstreak@1: local uid flickerstreak@1: uid = function(...) flickerstreak@1: if actives[uid] then flickerstreak@1: return self[handler](self, ...) flickerstreak@1: else flickerstreak@1: return orig(...) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: return uid flickerstreak@1: else flickerstreak@1: -- The handler is a function, just call it flickerstreak@1: local uid flickerstreak@1: uid = function(...) flickerstreak@1: if actives[uid] then flickerstreak@1: return handler(...) flickerstreak@1: else flickerstreak@1: return orig(...) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: return uid flickerstreak@1: end flickerstreak@1: else flickerstreak@1: -- secure hooks don't call the original method flickerstreak@1: if type(handler) == "string" then flickerstreak@1: local uid flickerstreak@1: uid = function(...) flickerstreak@1: if actives[uid] then flickerstreak@1: return self[handler](self, ...) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: return uid flickerstreak@1: else flickerstreak@1: -- The handler is a function, just call it flickerstreak@1: local uid flickerstreak@1: uid = function(...) flickerstreak@1: if actives[uid] then flickerstreak@1: return handler(...) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: return uid flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: local function hookFunction(self, func, handler, secure) flickerstreak@1: local orig = _G[func] flickerstreak@1: flickerstreak@1: if not orig or type(orig) ~= "function" then flickerstreak@1: AceHook:error("Attempt to hook a non-existant function %q", func) flickerstreak@1: end flickerstreak@1: flickerstreak@1: if not handler then flickerstreak@1: handler = func flickerstreak@1: end flickerstreak@1: flickerstreak@1: local uid = registry[self][func] flickerstreak@1: if uid then flickerstreak@1: if actives[uid] then flickerstreak@1: -- We have an active hook from this source. Don't multi-hook flickerstreak@1: AceHook:error("%q already has an active hook from this source.", func) flickerstreak@1: end flickerstreak@1: flickerstreak@1: if handlers[uid] == handler then flickerstreak@1: -- The hook is inactive, so reactivate it flickerstreak@1: actives[uid] = true flickerstreak@1: return flickerstreak@1: else flickerstreak@1: self.hooks[func] = nil flickerstreak@1: registry[self][func] = nil flickerstreak@1: handlers[uid] = nil flickerstreak@1: uid = nil flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: if type(handler) == "string" then flickerstreak@1: if type(self[handler]) ~= "function" then flickerstreak@1: AceHook:error("Could not find the the handler %q when hooking function %q", handler, func) flickerstreak@1: end flickerstreak@1: elseif type(handler) ~= "function" then flickerstreak@1: AceHook:error("Could not find the handler you supplied when hooking %q", func) flickerstreak@1: end flickerstreak@1: flickerstreak@1: uid = createFunctionHook(self, func, handler, orig, secure) flickerstreak@1: registry[self][func] = uid flickerstreak@1: actives[uid] = true flickerstreak@1: handlers[uid] = handler flickerstreak@1: flickerstreak@1: if not secure then flickerstreak@1: _G[func] = uid flickerstreak@1: self.hooks[func] = orig flickerstreak@1: else flickerstreak@1: hooksecurefunc(func, uid) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: local function unhookFunction(self, func) flickerstreak@1: if not registry[self][func] then flickerstreak@1: AceHook:error("Tried to unhook %q which is not currently hooked.", func) flickerstreak@1: end flickerstreak@1: flickerstreak@1: local uid = registry[self][func] flickerstreak@1: flickerstreak@1: if actives[uid] then flickerstreak@1: -- See if we own the global function flickerstreak@1: if self.hooks[func] and _G[func] == uid then flickerstreak@1: _G[func] = self.hooks[func] flickerstreak@1: self.hooks[func] = nil flickerstreak@1: registry[self][func] = nil flickerstreak@1: handlers[uid] = nil flickerstreak@1: actives[uid] = nil flickerstreak@1: -- Magically all-done flickerstreak@1: else flickerstreak@1: actives[uid] = nil flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: local function hookMethod(self, obj, method, handler, script, secure) flickerstreak@1: if not handler then flickerstreak@1: handler = method flickerstreak@1: end flickerstreak@1: flickerstreak@1: if not obj or type(obj) ~= "table" then flickerstreak@1: AceHook:error("The object you supplied could not be found, or isn't a table.") flickerstreak@1: end flickerstreak@1: flickerstreak@1: local uid = registry[self][obj] and registry[self][obj][method] flickerstreak@1: if uid then flickerstreak@1: if actives[uid] then flickerstreak@1: -- We have an active hook from this source. Don't multi-hook flickerstreak@1: AceHook:error("%q already has an active hook from this source.", method) flickerstreak@1: end flickerstreak@1: flickerstreak@1: if handlers[uid] == handler then flickerstreak@1: -- The hook is inactive, reactivate it. flickerstreak@1: actives[uid] = true flickerstreak@1: return flickerstreak@1: else flickerstreak@1: if self.hooks[obj] then flickerstreak@1: self.hooks[obj][method] = nil flickerstreak@1: end flickerstreak@1: registry[self][obj][method] = nil flickerstreak@1: handlers[uid] = nil flickerstreak@1: actives[uid] = nil flickerstreak@1: scripts[uid] = nil flickerstreak@1: uid = nil flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: if type(handler) == "string" then flickerstreak@1: if type(self[handler]) ~= "function" then flickerstreak@1: AceHook:error("Could not find the handler %q you supplied when hooking method %q", handler, method) flickerstreak@1: end flickerstreak@1: elseif type(handler) ~= "function" then flickerstreak@1: AceHook:error("Could not find the handler you supplied when hooking method %q", method) flickerstreak@1: end flickerstreak@1: flickerstreak@1: local orig flickerstreak@1: if script then flickerstreak@1: if not obj.GetScript then flickerstreak@1: AceHook:error("The object you supplied does not have a GetScript method.") flickerstreak@1: end flickerstreak@1: if not obj:HasScript(method) then flickerstreak@1: AceHook:error("The object you supplied doesn't allow the %q method.", method) flickerstreak@1: end flickerstreak@1: flickerstreak@1: orig = obj:GetScript(method) flickerstreak@1: if type(orig) ~= "function" then flickerstreak@1: -- Sometimes there is not a original function for a script. flickerstreak@1: orig = function() end flickerstreak@1: end flickerstreak@1: else flickerstreak@1: orig = obj[method] flickerstreak@1: end flickerstreak@1: if not orig then flickerstreak@1: AceHook:error("Could not find the method or script %q you are trying to hook.", method) flickerstreak@1: end flickerstreak@1: flickerstreak@1: if not self.hooks[obj] then flickerstreak@1: self.hooks[obj] = new() flickerstreak@1: end flickerstreak@1: if not registry[self][obj] then flickerstreak@1: registry[self][obj] = new() flickerstreak@1: end flickerstreak@1: flickerstreak@1: local uid = createMethodHook(self, obj, method, handler, orig, secure) flickerstreak@1: registry[self][obj][method] = uid flickerstreak@1: actives[uid] = true flickerstreak@1: handlers[uid] = handler flickerstreak@1: scripts[uid] = script and true or nil flickerstreak@1: flickerstreak@1: if script then flickerstreak@1: obj:SetScript(method, uid) flickerstreak@1: self.hooks[obj][method] = orig flickerstreak@1: elseif not secure then flickerstreak@1: obj[method] = uid flickerstreak@1: self.hooks[obj][method] = orig flickerstreak@1: else flickerstreak@1: hooksecurefunc(obj, method, uid) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: local function unhookMethod(self, obj, method) flickerstreak@1: if not registry[self][obj] or not registry[self][obj][method] then flickerstreak@1: AceHook:error("Attempt to unhook a method %q that is not currently hooked.", method) flickerstreak@1: return flickerstreak@1: end flickerstreak@1: flickerstreak@1: local uid = registry[self][obj][method] flickerstreak@1: flickerstreak@1: if actives[uid] then flickerstreak@1: if scripts[uid] then -- If this is a script flickerstreak@1: if obj:GetScript(method) == uid then flickerstreak@1: -- We own the script. Revert to normal. flickerstreak@1: obj:SetScript(method, self.hooks[obj][method]) flickerstreak@1: self.hooks[obj][method] = nil flickerstreak@1: registry[self][obj][method] = nil flickerstreak@1: handlers[uid] = nil flickerstreak@1: scripts[uid] = nil flickerstreak@1: actives[uid] = nil flickerstreak@1: else flickerstreak@1: actives[uid] = nil flickerstreak@1: end flickerstreak@1: else flickerstreak@1: if self.hooks[obj] and self.hooks[obj][method] and obj[method] == uid then flickerstreak@1: -- We own the method. Revert to normal. flickerstreak@1: obj[method] = self.hooks[obj][method] flickerstreak@1: self.hooks[obj][method] = nil flickerstreak@1: registry[self][obj][method] = nil flickerstreak@1: handlers[uid] = nil flickerstreak@1: actives[uid] = nil flickerstreak@1: else flickerstreak@1: actives[uid] = nil flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: if self.hooks[obj] and not next(self.hooks[obj]) then flickerstreak@1: self.hooks[obj] = del(self.hooks[obj]) flickerstreak@1: end flickerstreak@1: if not next(registry[self][obj]) then flickerstreak@1: registry[self][obj] = del(registry[self][obj]) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: -- ("function" [, handler] [, hookSecure]) or (object, "method" [, handler] [, hookSecure]) flickerstreak@1: function AceHook:Hook(object, method, handler, hookSecure) flickerstreak@1: if type(object) == "string" then flickerstreak@1: method, handler, hookSecure = object, method, handler flickerstreak@1: if handler == true then flickerstreak@1: handler, hookSecure = nil, true flickerstreak@1: end flickerstreak@1: AceHook:argCheck(handler, 3, "function", "string", "nil") flickerstreak@1: AceHook:argCheck(hookSecure, 4, "boolean", "nil") flickerstreak@1: if issecurevariable(method) or onceSecure[method] then flickerstreak@1: if hookSecure then flickerstreak@1: onceSecure[method] = true flickerstreak@1: else flickerstreak@1: AceHook:error("Attempt to hook secure function %q. Use `SecureHook' or add `true' to the argument list to override.", method) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: hookFunction(self, method, handler, false) flickerstreak@1: else flickerstreak@1: if handler == true then flickerstreak@1: handler, hookSecure = nil, true flickerstreak@1: end flickerstreak@1: AceHook:argCheck(object, 2, "table") flickerstreak@1: AceHook:argCheck(method, 3, "string") flickerstreak@1: AceHook:argCheck(handler, 4, "function", "string", "nil") flickerstreak@1: AceHook:argCheck(hookSecure, 5, "boolean", "nil") flickerstreak@1: if not hookSecure and issecurevariable(object, method) then flickerstreak@1: AceHook:error("Attempt to hook secure method %q. Use `SecureHook' or add `true' to the argument list to override.", method) flickerstreak@1: end flickerstreak@1: hookMethod(self, object, method, handler, false, false) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: -- ("function", handler) or (object, "method", handler) flickerstreak@1: function AceHook:SecureHook(object, method, handler) flickerstreak@1: if type(object) == "string" then flickerstreak@1: method, handler = object, method flickerstreak@1: AceHook:argCheck(handler, 3, "function", "string", "nil") flickerstreak@1: hookFunction(self, method, handler, true) flickerstreak@1: else flickerstreak@1: AceHook:argCheck(object, 2, "table") flickerstreak@1: AceHook:argCheck(method, 3, "string") flickerstreak@1: AceHook:argCheck(handler, 4, "function", "string", "nil") flickerstreak@1: hookMethod(self, object, method, handler, false, true) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: function AceHook:HookScript(frame, script, handler) flickerstreak@1: AceHook:argCheck(frame, 2, "table") flickerstreak@1: if not frame[0] or type(frame.IsProtected) ~= "function" then flickerstreak@1: AceHook:error("Bad argument #2 to `HookScript'. Expected frame.") flickerstreak@1: end flickerstreak@1: AceHook:argCheck(script, 3, "string") flickerstreak@1: AceHook:argCheck(handler, 4, "function", "string", "nil") flickerstreak@1: if frame:IsProtected() and protectedScripts[script] then flickerstreak@1: AceHook:error("Cannot hook secure script %q.", script) flickerstreak@1: end flickerstreak@1: hookMethod(self, frame, script, handler, true, false) flickerstreak@1: end flickerstreak@1: flickerstreak@1: -- ("function") or (object, "method") flickerstreak@1: function AceHook:IsHooked(obj, method) flickerstreak@1: if type(obj) == "string" then flickerstreak@1: if registry[self][obj] and actives[registry[self][obj]] then flickerstreak@1: return true, handlers[registry[self][obj]] flickerstreak@1: end flickerstreak@1: else flickerstreak@1: AceHook:argCheck(obj, 2, "string", "table") flickerstreak@1: AceHook:argCheck(method, 3, "string") flickerstreak@1: if registry[self][obj] and registry[self][obj][method] and actives[registry[self][obj][method]] then flickerstreak@1: return true, handlers[registry[self][obj][method]] flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: return false, nil flickerstreak@1: end flickerstreak@1: flickerstreak@1: -- ("function") or (object, "method") flickerstreak@1: function AceHook:Unhook(obj, method) flickerstreak@1: if type(obj) == "string" then flickerstreak@1: unhookFunction(self, obj) flickerstreak@1: else flickerstreak@1: AceHook:argCheck(obj, 2, "string", "table") flickerstreak@1: AceHook:argCheck(method, 3, "string") flickerstreak@1: unhookMethod(self, obj, method) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: function AceHook:UnhookAll() flickerstreak@1: for key, value in pairs(registry[self]) do flickerstreak@1: if type(key) == "table" then flickerstreak@1: for method in pairs(value) do flickerstreak@1: self:Unhook(key, method) flickerstreak@1: end flickerstreak@1: else flickerstreak@1: self:Unhook(key) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: function AceHook:HookReport() flickerstreak@1: DEFAULT_CHAT_FRAME:AddMessage("This is a list of all active hooks for this object:") flickerstreak@1: if not next(registry[self]) then flickerstreak@1: DEFAULT_CHAT_FRAME:AddMessage("No hooks") flickerstreak@1: end flickerstreak@1: flickerstreak@1: for key, value in pairs(registry[self]) do flickerstreak@1: if type(value) == "table" then flickerstreak@1: for method, uid in pairs(value) do flickerstreak@1: DEFAULT_CHAT_FRAME:AddMessage(string.format("object: %s method: %q |cff%s|r%s", tostring(key), method, actives[uid] and "00ff00Active" or "ffff00Inactive", not self.hooks[key][method] and " |cff7f7fff-Secure-|r" or "")) flickerstreak@1: end flickerstreak@1: else flickerstreak@1: DEFAULT_CHAT_FRAME:AddMessage(string.format("function: %q |cff%s|r%s", tostring(key), actives[value] and "00ff00Active" or "ffff00Inactive", not self.hooks[key] and " |cff7f7fff-Secure-|r" or "")) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: function AceHook:OnInstanceInit(object) flickerstreak@1: if not object.hooks then flickerstreak@1: object.hooks = new() flickerstreak@1: end flickerstreak@1: if not registry[object] then flickerstreak@1: registry[object] = new() flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: AceHook.OnManualEmbed = AceHook.OnInstanceInit flickerstreak@1: flickerstreak@1: function AceHook:OnEmbedDisable(target) flickerstreak@1: self.UnhookAll(target) flickerstreak@1: end flickerstreak@1: flickerstreak@1: local function activate(self, oldLib, oldDeactivate) flickerstreak@1: AceHook = self flickerstreak@1: flickerstreak@1: self.handlers = oldLib and oldLib.handlers or {} flickerstreak@1: self.registry = oldLib and oldLib.registry or {} flickerstreak@1: self.scripts = oldLib and oldLib.scripts or {} flickerstreak@1: self.actives = oldLib and oldLib.actives or {} flickerstreak@1: self.onceSecure = oldLib and oldLib.onceSecure or {} flickerstreak@1: flickerstreak@1: handlers = self.handlers flickerstreak@1: registry = self.registry flickerstreak@1: scripts = self.scripts flickerstreak@1: actives = self.actives flickerstreak@1: onceSecure = self.onceSecure flickerstreak@1: flickerstreak@1: self:activate(oldLib, oldDeactivate) flickerstreak@1: flickerstreak@1: if oldDeactivate then flickerstreak@1: oldDeactivate(oldLib) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: AceLibrary:Register(AceHook, MAJOR_VERSION, MINOR_VERSION, activate)