--[[--------------------------------------------------------------------------------- AceHook Library by Cladhaire (cladhaire@gmail.com) This first section is the standard embedded library stub declaration. This code manages the versioning between different instances of the AceHooks library. See http://www.iriel.org/wow/addondev/embedlibrary1.html for more information ----------------------------------------------------------------------------------]] 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, } --[[--------------------------------------------------------------------------------- AceHooks Library implementation. -----------------------------------------------------------------------------------]] local MAJOR_VERSION = "1.4" local MINOR_VERSION = 2006042401 local AceHook = {} -- This is a compatability change for AceHook 1.3 upgrade if AceHookLib and not AceHookLib.NewVersion then function AceHookLib:NewVersion(version, minor) local versionData = self.versions[version] if not versionData or versionData.minor < minor then return true end end end -- Only declare this class if necessary if not AceHookLib or AceHookLib:NewVersion(MAJOR_VERSION, MINOR_VERSION) then function AceHook:GetLibraryVersion() return MAJOR_VERSION, MINOR_VERSION end function AceHook:LibActivate(stub, oldLib, oldList) local maj, min = self:GetLibraryVersion() if oldLib then for i,namespace in pairs(oldLib.embedList or {}) do self:Embed(namespace) end end -- Return nil to force the instance copy. return nil end function AceHook:HookDebug(msg) local maj,min = MAJOR_VERSION, MINOR_VERSION if DEFAULT_CHAT_FRAME then DEFAULT_CHAT_FRAME:AddMessage("[|cffffff00AceHook "..maj.."|r] " .. tostring(msg)) else print("[AceHook "..maj.."] " .. tostring(msg)) end end --[[---------------------------------------------------------------------- AceHook:Hook self:Hook("functionName", ["handlerName" | handler]) self:Hook(ObjectName, "Method", ["Handler" | handler]) -------------------------------------------------------------------------]] function AceHook:Hook(arg1, arg2, arg3) if not self.Hooks then self.Hooks = {} end if type(arg1)== "string" then if protFuncs[arg1] then local name = self.name if not name then local g = getfenv(0) for k,v in pairs(g) do if v == self then name = k break end end end if name then error(tostring(name) .. " tried to hook " .. arg1 .. ", which is a Blizzard protected function.",3) else self:HookDebug("An Addon tried to hook " .. arg1 .. ", which is a Blizzard protected function.") end else self:HookFunc(arg1, arg2) end else self:HookMeth(arg1, arg2, arg3) end end function AceHook:HookScript(arg1, arg2, arg3) if not self.Hooks then self.Hooks = {} end self:HookMeth(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 self.Hooks[obj][method].active then return true end else if self.Hooks and self.Hooks[obj] and self.Hooks[obj].active then return true end end return false end --[[---------------------------------------------------------------------- AceHook:Unhook self:Unhook("functionName") self:Unhook(ObjectName, "Method") -------------------------------------------------------------------------]] function AceHook:Unhook(arg1, arg2) if type(arg1) == "string" then self:UnhookFunc(arg1) else self:UnhookMeth(arg1, arg2) end end --[[---------------------------------------------------------------------- AceHook:UnhookAll - Unhooks all active hooks from the calling source -------------------------------------------------------------------------]] function AceHook:UnhookAll(script) if not self.Hooks then return end for key, value in pairs(self.Hooks) do if type(key) == "table" then for method in pairs(value) do if (self.Hooks[key][method].script == script) then self:Unhook(key, method) end end else self:Unhook(key) end end end function AceHook:UnhookAllScripts() self:UnhookAll(true) end --[[---------------------------------------------------------------------- AceHook:Print - Utility function for HookReport, for embedding AceHook:HookReport - Lists registered hooks from this source -------------------------------------------------------------------------]] function AceHook:HookReport() self:HookDebug("This is a list of all active hooks for this object:") if not self.Hooks then self:HookDebug("No registered hooks.") return end for key, value in pairs(self.Hooks) do if type(key) == "table" then for method in pairs(value) do self:HookDebug("key: "..tostring(key).." method: "..tostring(method)..((self.Hooks[key][method].active and "|cff00ff00 Active" or "|cffffff00 Inactive"))) end else self:HookDebug("key: " .. tostring(key) .. " value: " .. tostring(value) .. ((self.Hooks[key].active and "|cff00ff00 Active" or "|cffffff00 Inactive"))) end end end --[[---------------------------------------------------------------------- AceHook:CallHook("functionName" [, arg1, arg2, arg3, ...]) AceHook:CallHook(ObjectName, "Method" [, arg1, arg2, arg3, ...]) DEPRECATED: This function has been deprecated and replaced by a direct call from the self.Hooks table. This avoids function calls and decreases the overhead from a CallHook. The new wrapper functions limit any CallHook to 20 arguments. The new forms are as follows: self.Hooks.functionName.orig(arg1, arg2, arg3) self.Hooks[ObjectName].MethodName.orig(ObjectName, arg1, arg2, arg3) -------------------------------------------------------------------------]] function AceHook:CallHook(obj,meth,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) if type(obj) == "string" then -- Function Call if not self.Hooks or not self.Hooks[obj] then self:HookDebug( "Attempt to CallHook when no hook exists for " .. obj) return else local func = obj return self.Hooks[func].orig(meth,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) end else if not self.Hooks[obj] or not self.Hooks[obj][meth] then self:HookDebug( "Attempt to CallHook when no hook exists for " .. tostring(meth)) return else return self.Hooks[obj][meth].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 --[[---------------------------------------------------------------------- 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 -------------------------------------------------------------------------]] function AceHook:HookFunc(func, handler) local f = getglobal(func) if not f or type(f) ~= "function" then self:HookDebug("Attempt to hook a non-existant function '"..func.."'.",3) return end if not handler then handler = func end if self.Hooks[func] then -- We have an active hook from this source. Don't multi-hook if self.Hooks[func].active then self:HookDebug( func .. " already has an active hook from this source.") return end -- The hook is inactive, so reactivate it if self.Hooks[func].handler == handler then self.Hooks[func].active = true return else error( "There is a stale hook for " .. func .. " can't hook or reactivate.",3) end end local methodHandler if type(handler) == "string" then if self[handler] then methodHandler = true else error( "Could not find the the handler " ..handler.." when hooking function " .. func,3) end elseif type(handler) ~= "function" then error( "Could not find the handler you supplied when hooking " .. func,3) end self.Hooks[func] = {} self.Hooks[func].orig = f self.Hooks[func].active = true self.Hooks[func].handler = handler self.Hooks[func].func = self:_getFunctionHook(func, handler, methodHandler) setglobal(func, self.Hooks[func].func) 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. -------------------------------------------------------------------------]] function AceHook:UnhookFunc(func) if not self.Hooks or not self.Hooks[func] then self:HookDebug("Tried to unhook '" ..func .. "' which is not currently hooked.") return end if self.Hooks[func].active then -- See if we own the global function if getglobal(func) == self.Hooks[func].func then setglobal(func, self.Hooks[func].orig) self.Hooks[func] = nil -- Magically all-done else self.Hooks[func].active = nil end end end --[[---------------------------------------------------------------------- AceHook:_getFunctionHook- internal method -------------------------------------------------------------------------]] function AceHook:_getFunctionHook(func, handler, methodHandler) if methodHandler 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 self.Hooks[func].active 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 self.Hooks[func].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 self.Hooks[func].active 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 self.Hooks[func].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:HookMeth - Takes an optional fourth argument o script - Signifies whether this is a script hook or not -------------------------------------------------------------------------]] function AceHook:HookMeth(obj, method, handler, script) if not handler then handler = method end if (not obj or type(obj) ~= "table") then error("The object you supplied could not be found, or isn't a table.", 3) end if self.Hooks[obj] and self.Hooks[obj][method] then -- We have an active hook from this source. Don't multi-hook if self.Hooks[obj][method].active then self:HookDebug( method .. " already has an active hook from this source.") return end -- The hook is inactive, so reactivate it. if self.Hooks[obj][method].handler == handler then self.Hooks[obj][method].active = true return else error( "There is a stale hook for " .. method .. " can't hook or reactivate.",3) end end -- We're clear to try the hook, let's make some checks first local methodHandler if type(handler) == "string" then if self[handler] then methodHandler = true else error( "Could not find the handler ("..handler..") you supplied when hooking method " .. method,3) end elseif type(handler) ~= "function" then error( "Could not find the handler you supplied when hooking method " .. method,3) end -- Handler has been found, so now try to find the method we're trying to hook local orig, setscript -- Script if script then if not obj.GetScript then error("The object you supplied does not have a GetScript method.", 3) end if not obj:HasScript(method) then error("The object you supplied doesn't allows the " .. method .. " method.", 3) end -- Sometimes there is not a original function for a script. orig = obj:GetScript(method) or function () end -- Method else orig = obj[method] end if not orig then error("Could not find the method or script ("..method..") you are trying to hook.",3) end if not self.Hooks[obj] then self.Hooks[obj] = {} end self.Hooks[obj][method] = {} self.Hooks[obj][method].orig = orig self.Hooks[obj][method].active = true self.Hooks[obj][method].handler = handler self.Hooks[obj][method].script = script self.Hooks[obj][method].func = self:_getMethodHook(obj, method, handler, methodHandler) if script then obj:SetScript(method, self.Hooks[obj][method].func) else obj[method] = self.Hooks[obj][method].func 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. -------------------------------------------------------------------------]] function AceHook:UnhookMeth(obj, method) if not self.Hooks or not self.Hooks[obj] or not self.Hooks[obj][method] then self:HookDebug("Attempt to unhook a method ("..method..") that is not currently hooked.") return end if self.Hooks[obj][method].active then -- If this is a script if self.Hooks[obj][method].script then if obj:GetScript(method) == self.Hooks[obj][method].func then -- We own the global function. Kill it. obj:SetScript(method, self.Hooks[obj][method].orig) self.Hooks[obj][method] = nil return else self.Hooks[obj][method].active = nil end else if obj[method] == self.Hooks[obj][method].func then -- We own the global function. Kill it. obj[method] = self.Hooks[obj][method].orig self.Hooks[obj][method] = nil return else self.Hooks[obj][method].active = nil end end end if not next(self.Hooks[obj]) then -- Spank the table self.Hooks[obj] = nil end end --[[---------------------------------------------------------------------- AceHook:_getMethodHook - Internal Method -------------------------------------------------------------------------]] function AceHook:_getMethodHook(obj, method, handler, methodHook) if methodHook then -- The handler is a method, need to self it return function(o,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) if self.Hooks[obj][method].active 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) elseif self.Hooks[obj][method].orig then return self.Hooks[obj][method].orig(obj, 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(o,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) if self.Hooks[obj][method].active then return handler(o,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) elseif self.Hooks[obj][method].orig then return self.Hooks[obj][method].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 --[[---------------------------------------------------------------------- AceHook:Embed - Called to embed the vital methods into another object -------------------------------------------------------------------------]] function AceHook:Embed(object) object.Hook = self.Hook object.Unhook = self.Unhook object.UnhookAll = self.UnhookAll object.CallHook = self.CallHook object.HookFunc = self.HookFunc object.HookMeth = self.HookMeth object.UnhookFunc = self.UnhookFunc object.UnhookMeth = self.UnhookMeth object._getFunctionHook = self._getFunctionHook object._getMethodHook = self._getMethodHook object.HookReport = self.HookReport object.IsHooked = self.IsHooked object.HookScript = self.HookScript object.UnhookScript = self.Unhook object.UnhookAllScripts = self.UnhookAllScripts object.CallScript = self.CallHook if object.debug then object.HookDebug = object.debug else object.HookDebug = self.HookDebug end end end -- End conditional declaration --[[--------------------------------------------------------------------------------- This is the stub declaration for non-Ace environments ----------------------------------------------------------------------------------]] if not AceLibStub then AceLibStub = {} -- Instance replacement method. Handles "upgrading" to a new version function AceLibStub:ReplaceInstance(old,new) for k,v in pairs(old) do old[k]=nil end for k,v in pairs(new) do old[k]=v end end -- Returns a new stub. This allows multiple libraries to use the same stub function AceLibStub:NewStub(name) local newStub = {} self:ReplaceInstance(newStub, self) newStub.versions = {} newStub.libName = name return newStub end -- Returns true if the supplied version would be an upgrade to the current version -- This allows for bypass code in library declaration. function AceLibStub:NewVersion(version, minor) local versionData = self.versions[version] if not versionData or versionData.minor < minor then return true end end -- Handles referencing methods in the calling namespace itself, to accomplish -- inheritance instead of composition. This will not be used in every library -- but its important to track what namespaces have used :Embed() since -- :ReplaceInstance() won't update those references. function AceLibStub:Embed(namespace, version) local lib = self:GetInstance(version) if not lib.Embed then return end lib:Embed(namespace) if not lib.embedList then lib.embedList = setmetatable({}, {__mode="v"}) end table.insert(lib.embedList, namespace) lib = nil end -- Returns the most recent version of the given major library function AceLibStub:GetInstance(version) if not version then error("You must specify a version when requesting a library instance" ,2) end local versionData = self.versions[version] if not versionData then error("Cannot find " .. self.libName .. " instance with version '" .. version .. "'",2) return end return versionData.instance end -- Registers a new version of a given library. Takes the library object. function AceLibStub:Register(newInstance) if not newInstance.GetLibraryVersion then return end local version,minor = newInstance:GetLibraryVersion() local versionData = self.versions[version] if not versionData then -- This is new versionData = { instance=newInstance, minor=minor, old={}} self.versions[version] = versionData newInstance:LibActivate(self) return newInstance end if minor <= versionData.minor then -- This one is already obsolete if newInstance.LibDiscard then newInstance:LibDiscard() end return versionData.instance end -- This is an update local oldInstance = versionData.instance local oldList = versionData.old versionData.instance = newInstance versionData.minor = minor local skipCopy = newInstance:LibActivate(self, oldInstance, oldList) table.insert(oldList, oldInstance) if not skipCopy then for i,old in ipairs(oldList) do self:ReplaceInstance(old, newInstance) end end return newInstance end end --[[--------------------------------------------------------------------------------- Stub and Library registration ----------------------------------------------------------------------------------]] -- Bind a stub in the global namespace if it doesn't already exist if not AceHookLib then AceHookLib = AceLibStub:NewStub("AceHook") end -- Register this library version with the global stub AceHookLib:Register(AceHook) AceHook = nil