573 lines
18 KiB
Lua
573 lines
18 KiB
Lua
--[[
|
|
Name: AceHook-2.0
|
|
Revision: $Rev: 11577 $
|
|
Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
|
|
Inspired By: Ace 1.x by Turan (turan@gryphon.com)
|
|
Website: http://www.wowace.com/
|
|
Documentation: http://www.wowace.com/index.php/AceHook-2.0
|
|
SVN: http://svn.wowace.com/root/trunk/Ace2/AceHook-2.0
|
|
Description: Mixin to allow for safe hooking of functions, methods, and scripts.
|
|
Dependencies: AceLibrary, AceOO-2.0
|
|
]]
|
|
|
|
local MAJOR_VERSION = "AceHook-2.0"
|
|
local MINOR_VERSION = "$Revision: 11577 $"
|
|
|
|
-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version
|
|
if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
|
|
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
|
|
|
|
if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end
|
|
|
|
--[[---------------------------------------------------------------------------------
|
|
Create the library object
|
|
----------------------------------------------------------------------------------]]
|
|
|
|
local AceOO = AceLibrary:GetInstance("AceOO-2.0")
|
|
local AceHook = AceOO.Mixin {
|
|
"Hook",
|
|
"Unhook",
|
|
"UnhookAll",
|
|
"HookReport",
|
|
"IsHooked",
|
|
"HookScript",
|
|
}
|
|
|
|
local table_setn
|
|
do
|
|
local version = GetBuildInfo()
|
|
if string.find(version, "^2%.") then
|
|
-- 2.0.0
|
|
table_setn = function() end
|
|
else
|
|
table_setn = table.setn
|
|
end
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------------
|
|
Library Definitions
|
|
----------------------------------------------------------------------------------]]
|
|
|
|
local protFuncs = {
|
|
CameraOrSelectOrMoveStart = true, CameraOrSelectOrMoveStop = true,
|
|
TurnOrActionStart = true, TurnOrActionStop = true,
|
|
PitchUpStart = true, PitchUpStop = true,
|
|
PitchDownStart = true, PitchDownStop = true,
|
|
MoveBackwardStart = true, MoveBackwardStop = true,
|
|
MoveForwardStart = true, MoveForwardStop = true,
|
|
Jump = true, StrafeLeftStart = true,
|
|
StrafeLeftStop = true, StrafeRightStart = true,
|
|
StrafeRightStop = true, ToggleMouseMove = true,
|
|
ToggleRun = true, TurnLeftStart = true,
|
|
TurnLeftStop = true, TurnRightStart = true,
|
|
TurnRightStop = true,
|
|
}
|
|
|
|
local _G = getfenv(0)
|
|
|
|
local handlers, funcs, scripts, actives
|
|
|
|
--[[---------------------------------------------------------------------------------
|
|
Private definitions (Not exposed)
|
|
----------------------------------------------------------------------------------]]
|
|
|
|
--[[----------------------------------------------------------------------
|
|
_debug - Internal Method
|
|
-------------------------------------------------------------------------]]
|
|
local function print(text)
|
|
DEFAULT_CHAT_FRAME:AddMessage(text)
|
|
end
|
|
|
|
local function _debug(self, msg)
|
|
local name = self.hooks.name
|
|
if name then
|
|
print(string.format("[%s]: %s", name, msg))
|
|
else
|
|
print(msg)
|
|
end
|
|
end
|
|
|
|
local new, del
|
|
do
|
|
local list = setmetatable({}, {__mode = "k"})
|
|
function new()
|
|
local t = next(list)
|
|
if not t then
|
|
return {}
|
|
end
|
|
list[t] = nil
|
|
return t
|
|
end
|
|
|
|
function del(t)
|
|
setmetatable(t, nil)
|
|
table_setn(t, 0)
|
|
for k in pairs(t) do
|
|
t[k] = nil
|
|
end
|
|
list[t] = true
|
|
end
|
|
end
|
|
|
|
local origMetatable = {
|
|
__call = function(self, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
return self.orig(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
end
|
|
}
|
|
|
|
--[[----------------------------------------------------------------------
|
|
AceHook:_getFunctionHook- internal method
|
|
-------------------------------------------------------------------------]]
|
|
|
|
local function _getFunctionHook(self, func, handler, orig)
|
|
if type(handler) == "string" then
|
|
-- The handler is a method, need to self it
|
|
return function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
if actives[orig] then
|
|
return self[handler](self, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
else
|
|
return orig(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
end
|
|
end
|
|
else
|
|
-- The handler is a function, just call it
|
|
return function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
if actives[orig] then
|
|
return handler(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
else
|
|
return orig(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[----------------------------------------------------------------------
|
|
AceHook:_getMethodHook - Internal Method
|
|
-------------------------------------------------------------------------]]
|
|
local function _getMethodHook(self, object, method, handler, orig, script)
|
|
if type(handler) == "string" then
|
|
-- The handler is a method, need to self it
|
|
if script then
|
|
return function()
|
|
if actives[orig] then
|
|
return self[handler](self, object)
|
|
else
|
|
return orig()
|
|
end
|
|
end
|
|
else
|
|
return function(obj,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
if actives[orig] then
|
|
return self[handler](self, obj, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
else
|
|
return orig(obj, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
-- The handler is a function, just call it
|
|
if script then
|
|
return function()
|
|
if actives[orig] then
|
|
return handler(object)
|
|
else
|
|
return orig()
|
|
end
|
|
end
|
|
else
|
|
return function(obj,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
if actives[orig] then
|
|
return handler(obj,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
else
|
|
return orig(obj, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[----------------------------------------------------------------------
|
|
AceHook:HookFunc - internal method.
|
|
o You can only hook each function once from each source.
|
|
o If there is an inactive hook for this func/handler pair, we reactivate it
|
|
o If there is an inactive hook for another handler, we error out.
|
|
o Looks for handler as a method of the calling class, error if not available
|
|
o If handler is a function, it just uses it directly through the wrapper
|
|
-------------------------------------------------------------------------]]
|
|
local function _hookFunc(self, func, handler)
|
|
local orig = _G[func]
|
|
|
|
if not orig or type(orig) ~= "function" then
|
|
_debug(self, string.format("Attempt to hook a non-existant function %q", func),3)
|
|
return
|
|
end
|
|
|
|
if not handler then handler = func end
|
|
|
|
if self.hooks[func] then
|
|
local orig = self.hooks[func].orig
|
|
-- We have an active hook from this source. Don't multi-hook
|
|
if actives[orig] then
|
|
_debug(self, string.format("%q already has an active hook from this source.", func))
|
|
return
|
|
end
|
|
-- The hook is inactive, so reactivate it
|
|
if handlers[orig] == handler then
|
|
actives[orig] = true
|
|
return
|
|
else
|
|
AceHook:error("There is a stale hook for %q can't hook or reactivate.", func)
|
|
end
|
|
end
|
|
|
|
if type(handler) == "string" then
|
|
if type(self[handler]) ~= "function" then
|
|
AceHook:error("Could not find the the handler %q when hooking function %q", handler, func)
|
|
end
|
|
elseif type(handler) ~= "function" then
|
|
AceHook:error("Could not find the handler you supplied when hooking %q", func)
|
|
end
|
|
|
|
local t = setmetatable(new(), origMetatable)
|
|
self.hooks[func] = t
|
|
t.orig = orig
|
|
|
|
actives[orig] = true
|
|
handlers[orig] = handler
|
|
local newFunc = _getFunctionHook(self, func, handler, orig)
|
|
funcs[orig] = newFunc
|
|
|
|
_G[func] = newFunc
|
|
end
|
|
|
|
--[[----------------------------------------------------------------------
|
|
AceHook:UnhookFunc - internal method
|
|
o If you attempt to unhook a function that has never been hooked, or to unhook in a
|
|
system that has never had a hook before, the system will error with a stack trace
|
|
o If we own the global function, then put the original back in its place and remove
|
|
all references to the Hooks[func] structure.
|
|
o If we don't own the global function (we've been hooked) we deactivate the hook,
|
|
forcing the handler to passthrough.
|
|
-------------------------------------------------------------------------]]
|
|
local function _unhookFunc(self, func)
|
|
if not self.hooks[func] or not funcs[self.hooks[func].orig] then
|
|
_debug(self, string.format("Tried to unhook %q which is not currently hooked.", func))
|
|
return
|
|
end
|
|
|
|
local orig = self.hooks[func].orig
|
|
|
|
if actives[orig] then
|
|
-- See if we own the global function
|
|
if _G[func] == funcs[orig] then
|
|
_G[func] = orig
|
|
self.hooks[func] = del(self.hooks[func])
|
|
handlers[orig] = nil
|
|
funcs[orig] = nil
|
|
scripts[orig] = nil
|
|
actives[orig] = nil
|
|
-- Magically all-done
|
|
else
|
|
actives[orig] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[----------------------------------------------------------------------
|
|
AceHook:HookMeth - Takes an optional fourth argument
|
|
o script - Signifies whether this is a script hook or not
|
|
-------------------------------------------------------------------------]]
|
|
|
|
local function _hookMeth(self, obj, method, handler, script)
|
|
if not handler then handler = method end
|
|
if (not obj or type(obj) ~= "table") then
|
|
AceHook:error("The object you supplied could not be found, or isn't a table.")
|
|
end
|
|
|
|
if self.hooks[obj] and self.hooks[obj][method] then
|
|
local orig = self.hooks[obj][method].orig
|
|
-- We have an active hook from this source. Don't multi-hook
|
|
if actives[orig] then
|
|
_debug(self, string.format("%q already has an active hook from this source.", method))
|
|
return
|
|
end
|
|
-- The hook is inactive, so reactivate it.
|
|
if handlers[orig] == handler then
|
|
actives[orig] = true
|
|
return
|
|
else
|
|
AceHook:error("There is a stale hook for %q can't hook or reactivate.", method)
|
|
end
|
|
end
|
|
-- We're clear to try the hook, let's make some checks first
|
|
if type(handler) == "string" then
|
|
if type(self[handler]) ~= "function" then
|
|
AceHook:error("Could not find the handler %q you supplied when hooking method %q", handler, method)
|
|
end
|
|
elseif type(handler) ~= "function" then
|
|
AceHook:error("Could not find the handler you supplied when hooking method %q", method)
|
|
end
|
|
-- Handler has been found, so now try to find the method we're trying to hook
|
|
local orig
|
|
-- Script
|
|
if script then
|
|
if not obj.GetScript then
|
|
AceHook:error("The object you supplied does not have a GetScript method.")
|
|
end
|
|
if not obj:HasScript(method) then
|
|
AceHook:error("The object you supplied doesn't allow the %q method.", method)
|
|
end
|
|
-- Sometimes there is not a original function for a script.
|
|
orig = obj:GetScript(method)
|
|
if not orig then
|
|
orig = function() end
|
|
end
|
|
-- Method
|
|
else
|
|
orig = obj[method]
|
|
end
|
|
if not orig then
|
|
AceHook:error("Could not find the method or script %q you are trying to hook.", method)
|
|
end
|
|
if not self.hooks[obj] then
|
|
self.hooks[obj] = new()
|
|
end
|
|
local t = setmetatable(new(), origMetatable)
|
|
self.hooks[obj][method] = t
|
|
t.orig = orig
|
|
|
|
actives[orig] = true
|
|
handlers[orig] = handler
|
|
scripts[orig] = script and true or nil
|
|
local newFunc = _getMethodHook(self, obj, method, handler, orig, script)
|
|
funcs[orig] = newFunc
|
|
|
|
if script then
|
|
obj:SetScript(method, newFunc)
|
|
else
|
|
obj[method] = newFunc
|
|
end
|
|
end
|
|
|
|
--[[----------------------------------------------------------------------
|
|
AceHook:UnhookMeth - Internal method
|
|
o If you attempt to unhook a method that has never been hooked, or to unhook in a
|
|
system that has never had a hook before, the system will error with a stack trace
|
|
o If we own the global method, then put the original back in its place and remove
|
|
all references to the Hooks[obj][method] structure.
|
|
o If we don't own the global method (we've been hooked) we deactivate the hook,
|
|
forcing the handler to passthrough.
|
|
-------------------------------------------------------------------------]]
|
|
local function _unhookMeth(self, obj, method)
|
|
if not self.hooks[obj] or not self.hooks[obj][method] or not funcs[self.hooks[obj][method].orig] then
|
|
_debug(self, string.format("Attempt to unhook a method %q that is not currently hooked.", method))
|
|
return
|
|
end
|
|
|
|
local orig = self.hooks[obj][method].orig
|
|
|
|
if actives[orig] then
|
|
-- If this is a script
|
|
if scripts[orig] then
|
|
if obj:GetScript(method) == funcs[orig] then
|
|
-- We own the script. Kill it.
|
|
obj:SetScript(method, orig)
|
|
self.hooks[obj][method] = del(self.hooks[obj][method])
|
|
handlers[orig] = nil
|
|
funcs[orig] = nil
|
|
scripts[orig] = nil
|
|
actives[orig] = nil
|
|
else
|
|
actives[orig] = nil
|
|
end
|
|
else
|
|
if obj[method] == funcs[orig] then
|
|
-- We own the method. Kill it.
|
|
obj[method] = orig
|
|
self.hooks[obj][method] = del(self.hooks[obj][method])
|
|
handlers[orig] = nil
|
|
funcs[orig] = nil
|
|
scripts[orig] = nil
|
|
actives[orig] = nil
|
|
else
|
|
actives[orig] = nil
|
|
end
|
|
end
|
|
end
|
|
if not next(self.hooks[obj]) then
|
|
-- Spank the table
|
|
self.hooks[obj] = del(self.hooks[obj])
|
|
end
|
|
end
|
|
|
|
function AceHook:OnInstanceInit(object)
|
|
if not object.hooks then
|
|
object.hooks = new()
|
|
end
|
|
|
|
local name
|
|
|
|
if type(rawget(object, 'GetLibraryVersion')) == "function" then
|
|
name = object:GetLibraryVersion()
|
|
end
|
|
if not name and type(object.GetName) == "function" then
|
|
name = object:GetName()
|
|
end
|
|
if not name and type(object.name) == "string" then
|
|
name = object.name
|
|
end
|
|
if not name then
|
|
for k,v in pairs(_G) do
|
|
if v == object then
|
|
name = tostring(k)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
object.hooks.name = name
|
|
end
|
|
|
|
AceHook.OnManualEmbed = AceHook.OnInstanceInit
|
|
|
|
--[[----------------------------------------------------------------------
|
|
AceHook:Hook
|
|
self:Hook("functionName", ["handlerName" | handler])
|
|
self:Hook(ObjectName, "Method", ["Handler" | handler])
|
|
-------------------------------------------------------------------------]]
|
|
function AceHook:Hook(arg1, arg2, arg3)
|
|
if type(arg1)== "string" then
|
|
if protFuncs[arg1] then
|
|
if self.hooks.name then
|
|
AceHook:error("%s tried to hook %q, which is a Blizzard protected function.", self.hooks.name, arg1)
|
|
else
|
|
_debug(self, string.format("An Addon tried to hook %q, which is a Blizzard protected function.", arg1))
|
|
end
|
|
else
|
|
_hookFunc(self, arg1, arg2)
|
|
end
|
|
else
|
|
_hookMeth(self, arg1, arg2, arg3)
|
|
end
|
|
end
|
|
|
|
function AceHook:HookScript(arg1, arg2, arg3)
|
|
_hookMeth(self, arg1, arg2, arg3, true)
|
|
end
|
|
|
|
--[[----------------------------------------------------------------------
|
|
AceHook:IsHooked()
|
|
self:Hook("functionName")
|
|
self:Hook(ObjectName, "Method")
|
|
|
|
Returns whether or not the given function is hooked in the current
|
|
namespace. A hooked, but inactive function is considered NOT
|
|
hooked in this context.
|
|
-------------------------------------------------------------------------]]
|
|
function AceHook:IsHooked(obj, method)
|
|
if method and obj then
|
|
if self.hooks and self.hooks[obj] and self.hooks[obj][method] and actives[self.hooks[obj][method].orig] then
|
|
return true, handlers[self.hooks[obj][method].orig]
|
|
end
|
|
else
|
|
if self.hooks and self.hooks[obj] and actives[self.hooks[obj].orig] then
|
|
return true, handlers[self.hooks[obj].orig]
|
|
end
|
|
end
|
|
|
|
return false, nil
|
|
end
|
|
|
|
--[[----------------------------------------------------------------------
|
|
AceHook:Unhook
|
|
self:Unhook("functionName")
|
|
self:Unhook(ObjectName, "Method")
|
|
-------------------------------------------------------------------------]]
|
|
function AceHook:Unhook(arg1, arg2)
|
|
if type(arg1) == "string" then
|
|
_unhookFunc(self, arg1)
|
|
else
|
|
_unhookMeth(self, arg1, arg2)
|
|
end
|
|
end
|
|
|
|
--[[----------------------------------------------------------------------
|
|
AceHook:UnhookAll - Unhooks all active hooks from the calling source
|
|
-------------------------------------------------------------------------]]
|
|
function AceHook:UnhookAll()
|
|
for key, value in pairs(self.hooks) do
|
|
if type(key) == "table" then
|
|
for method in pairs(value) do
|
|
self:Unhook(key, method)
|
|
end
|
|
else
|
|
self:Unhook(key)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function AceHook:OnEmbedDisable(target)
|
|
self.UnhookAll(target)
|
|
end
|
|
|
|
--[[----------------------------------------------------------------------
|
|
AceHook:HookReport - Lists registered hooks from this source
|
|
-------------------------------------------------------------------------]]
|
|
|
|
function AceHook:HookReport()
|
|
_debug(self, "This is a list of all active hooks for this object:")
|
|
if not self.hooks then _debug(self, "No registered hooks.") return end
|
|
|
|
for key, value in pairs(self.hooks) do
|
|
if type(value) == "table" then
|
|
for method in pairs(value) do
|
|
_debug(self, string.format("key: %s method: %q |cff%s|r", tostring(key), method, self.hooks[key][method].active and "00ff00Active" or "ffff00Inactive"))
|
|
end
|
|
else
|
|
_debug(self, string.format("key: %s value: %q |cff%s|r", tostring(key), tostring(value), self.hooks[key].active and "00ff00Active" or "ffff00Inactive"))
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------------
|
|
Stub and Library registration
|
|
----------------------------------------------------------------------------------]]
|
|
|
|
local function activate(self, oldLib, oldDeactivate)
|
|
AceHook = self
|
|
|
|
AceHook.super.activate(self, oldLib, oldDeactivate)
|
|
|
|
if oldLib then
|
|
self.handlers = oldLib.handlers
|
|
self.funcs = oldLib.funcs
|
|
self.scripts = oldLib.scripts
|
|
self.actives = oldLib.actives
|
|
end
|
|
|
|
if not self.handlers then
|
|
self.handlers = {}
|
|
end
|
|
if not self.funcs then
|
|
self.funcs = {}
|
|
end
|
|
if not self.scripts then
|
|
self.scripts = {}
|
|
end
|
|
if not self.actives then
|
|
self.actives = {}
|
|
end
|
|
|
|
handlers = self.handlers
|
|
funcs = self.funcs
|
|
scripts = self.scripts
|
|
actives = self.actives
|
|
|
|
if oldDeactivate then
|
|
oldDeactivate(oldLib)
|
|
end
|
|
end
|
|
|
|
AceLibrary:Register(AceHook, MAJOR_VERSION, MINOR_VERSION, activate)
|