11-14-05, 12:06 PM | #1 |
Can API fcns be hooked?
I was trying to write a little experimental mod that involves hooking CastSpell and CastSpellByName. I have the replacement procedures add a message to the default chat window, bump a counter, and then call the original procedure with all the original parms.
When I examine the procedure values after loading, they are as I would expect them to be, in the sense that CastSpell and CastSpellByName are equal to my versions of the same, and the original values that I stored away are different (and happen to have lower values). BUT, when I take my character into combat and use my special rogue moves (Gouge, Backstab, etc) no extra message are displayed to the chat window, and the counter is not being bumped. Can anyone please help me understand what's going on? Is it not possible to truly hook procedures that are part of the core API? Here's a code snippet below. --Qzot Code:
function POSC_CastSpell(...) POSC:log('CastSpell called.'); POSC.used = POSC.used+1; POSC.originalCastSpell(unpack(arg)); end function POSC_CastSpellByName(...) POSC:log('CastSpellByName called.'); POSC.used = POSC.used+1; POSC.originalCastSpellByName(unpack(arg)); end function POSC:OnLoad() self.originalCastSpell = CastSpell; CastSpell = POSC_CastSpell; self.originalCastSpellByName = CastSpellByName; CastSpellByName = POSC_CastSpellByName; self:log('Loaded.'); end function POSC:logCurrentProcedureValues() self:log('CastSpell=', CastSpell); self:log('POSC_CastSpell=', POSC_CastSpell); self:log('POSC.originalCastSpell=', POSC.originalCastSpell); self:log('CastSpellByName=', CastSpellByName); self:log('POSC_CastSpellByName=', POSC_CastSpellByName); self:log('POSC.originalCastSpellByName=', POSC.originalCastSpellByName); end POSC:OnLoad(); |
|
11-14-05, 01:27 PM | #2 |
I would suspect that functions like CastSpell that are defined in the complied code instead on in the UI LUA code are not changeable on purpose for integrity reasons.
|
|
11-14-05, 03:36 PM | #3 |
There is nothing stopping you from hooking those functions. The problem you are having are as follows:
:log? what the heck is this. I don't see it defined in your code. You keep using : and . interchangeable. This works to some degree the way you used it, but there is absolutely no reason for you to use it. For example: table:item(...) is the same as table.item(self, ...) The only time you used it was when you wanted to pass POSC as self. You could just as easily replace self with POSC and make it easier for anyone else looking at your code to understand. It also makes bugs easier to track in the long run. I stay away from passing variables for things that never change anyway. Which brings me to the other issue. POSC should be getting defined as a table initially, and then add stuff to it. POSC might be the name of your frame, in which case it is already a table. I have no way of knowing that, but if that is the case, I wouldn't pack too much into it anyway. Create POSC as a lua table, and then make your frame POSC_Frame or something to that effect. Here is an example: Code:
POSC = {}; -- your new table POSC.OnLoad = function() POSC.oldCastSpell = CastSpell; -- backup original function CastSpell = POSC.CastSpell; -- replate original with custom POSC.oldCastSpellByName = CastSpellByName; -- backup original function CastSpellByName = POSC.CastSpellByName; -- replace original with custom POSC.Log("Loaded..."); -- output to chat log? POSC.Used = 0; -- define this once, so that it can start counting up from 0 end; POSC.CastSpell = function(...) POSC.Log("CastSpell Called."); POSC.Used = Posc.Used + 1; -- add to the counter POSC.oldCastSpell(unpack(arg)); end; POSC.CastSpellByName = function(...) POSC.Log("CastSpellByName Called."); POSC.Used = POSC.Used + 1; -- add to the counter POSC.oldCastSpellByName(unpack(arg)); end; POSC.Log = function(text) -- I assume this is a function to output the text argument to the chat window? -- Replace this with the actual function... end; POSC.OnLoad; Last edited by Beladona : 11-14-05 at 04:19 PM. |
|
11-14-05, 04:18 PM | #4 |
Beladona,
I do define POSC before this code, and I have a log function defined. I only showed a 'snippet', as stated, and assumed people would fill in the blanks for what came before in the file. POSC is not the name of my frame. In fact, my .toc does not reference a frame, but only a lua file, which is why my code contains a call to POSC:OnLoad() rather than having the xml generate the onload call. As to strings, the Lua reference manual (http://www.lua.org/manual/5.0/manual.html#2) says that literal strings may be delimited by single or double quotes, and makes no distinction between the two. I'm not quite sure what you mean about "correct Lua". I prefer to use : syntax when possible. For example, my log function uses field of POSC which store the package name and version for the log message. For complete reference, here is the portion of the code which was not included in the previous snippet. While the stylistic preferences may be different that what someone else might choose, I still don't quite understand why spell casts are not being caught by the hooked procedures. Code:
--[[ Copyright (c) Palo Alto Research Center, Inc. ("PARC"), 2005. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA This file contributed by the Play-On Project at PARC <[email protected]> ]]-- local PGM_NAME, PGM_TITLE, PGM_NOTES, PGM_ENABLED, PGM_LOADABLE, PGM_REASON, PGM_SECURITY = GetAddOnInfo("PO_SpellCorrector"); local _,_,PROGRAM_NAME = string.find(PGM_NAME, "PO_([^_]+)"); local _,_,PROGRAM_VERSION = string.find(PGM_TITLE, "<([%w\\.]+)>"); if (PROGRAM_VERSION == nil) then PROGRAM_VERSION = "<unknown>"; end; local PROGRAM_NAME_AND_VERSION = PROGRAM_NAME .. PROGRAM_VERSION; local realmName = GetCVar("realmName"); POSC = {used=0}; function POSC:full_fmt(...) local result = ""; for i,v in ipairs(arg) do result = result .. tostring(v); end return result; end function POSC:msg(...) if (ChatFrame1) then ChatFrame1:AddMessage(self:full_fmt(unpack(arg)), 1, 0.8, 0.4); end end function POSC:log(...) local prefix = date("%H:%M:%S") -- .. " " .. realmName .. " " .. PROGRAM_NAME_AND_VERSION .. ": "; local line = self:full_fmt(prefix, unpack(arg)); self:msg(line); if (OBSERVATIONS and OBSERVATIONS.LOGS) then if (nil == OBSERVATIONS.LOGS[filename]) then OBSERVATIONS.LOGS[filename] = {}; local header = self:full_fmt(date("%H:%M:%S "), " ", " Logging started.", date(), realmName, UnitName('player'), GetRealZoneText()); table.insert(OBSERVATIONS.LOGS[filename], string.rep("-", string.len(header))); table.insert(OBSERVATIONS.LOGS[filename], header); end table.insert(OBSERVATIONS.LOGS[filename], line); end end function POSC:Select(condition, iftrue, iffalse) if (condition) then return iftrue; else return iffalse; end end function POSC:IfNil(value, default) return self:Select(value == nil, default, value); end function POSC_CastSpell(...) POSC:log('CastSpell called.'); POSC.used = POSC.used+1; POSC.originalCastSpell(unpack(arg)); end function POSC_CastSpellByName(...) POSC:log('CastSpellByName called.'); POSC.used = POSC.used+1; POSC.originalCastSpellByName(unpack(arg)); end function POSC:OnLoad() self.originalCastSpell = CastSpell; CastSpell = POSC_CastSpell; self.originalCastSpellByName = CastSpellByName; CastSpellByName = POSC_CastSpellByName; self:log('Loaded.'); end function POSC:logCurrentProcedureValues() self:log('CastSpell=', CastSpell); self:log('POSC_CastSpell=', POSC_CastSpell); self:log('POSC.originalCastSpell=', POSC.originalCastSpell); self:log('CastSpellByName=', CastSpellByName); self:log('POSC_CastSpellByName=', POSC_CastSpellByName); self:log('POSC.originalCastSpellByName=', POSC.originalCastSpellByName); end POSC:OnLoad(); |
|
11-14-05, 04:29 PM | #5 |
whatever works for you. I just see no reason to use : if you don't need to. all it does is pass self as the first argument, which in your case I guess you use it, although not in all instances.
Adding the rest of your code helps a lot to debug the problem, so I will look into it some more and play with it. |
|
11-14-05, 04:33 PM | #6 |
Beladona,
Belatedly, I noticed a comment in the code in your reply. (I think you must have been editing your reply while I was replying to your reply -- for example, your comment that double quotes is more correct than single quotes is now gone.) Anyway, you questioned why I chose to use (...) and then (unpack(arg)) syntax, and even tho it's really OT from my original question, I thought I'd give some sort of an explanation. Since I'm hooking Blizzard's function, and then calling it (I think Erich Gamma, et al, in Design Patterns call this a "Chain of Responsibility" pattern), my function becomes responsible for passing all the parameters it receives to the original function. While the syntax I chose is messier, it does exactly that: it guarantees to send to the original function exactly what it received. I admit this would only have limited utility ... for example, if Blizzard started optionally passing an extra parameter in some future release, this would still work correctly. I got into the habit of coding hooked procedures this way after seeing parameters missing or marked as having unknown meaning on WoWWiki for API functions that I know are there. |
|
11-14-05, 04:34 PM | #7 |
Lol.
Looks like we're still passing each other in the ether. Thanks for taking a look. |
|
11-14-05, 04:37 PM | #8 |
And in the interests of completeness, here's the TOC:
Code:
## Title: PlayOn's SpellCorrector <1800beta01> ## Copyright (c) Palo Alto Research Center, Inc. ("PARC"), 2005. ## All rights reserved. ## This program is free software; you can redistribute it and/or modify it ## under the terms of version 2 of the GNU General Public License as published by the ## Free Software Foundation. ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ## See the GNU General Public License for more details. ## You should have received a copy of the GNU General Public License along ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ## This file contributed by the Play-On Project at PARC <[email protected]> ## Interface: 1800 PO_SpellCorrector.lua |
|
11-14-05, 06:26 PM | #9 |
ARGHH! Here's some more info from experiments...
I've tried out my rogue with 3 different versions of Sinister Strike. When I copy Sinister Strike from the spellbook to the action bar and use it, I don't get any of the reporting via the addon, as listed above.
But, when I create the following macro and use it in the place of Sinister Strike Code:
/script CastSpell(32, "spell") /s CallCastSpell I get output something like Code:
16.08.30 SpellCorrector1800beta01: CastSpell called. [Foobar] CallCastSpell And then I have a third form, where the macro looks like the more obvious Code:
/cast Sinister Strike Code:
16.08.12 SpellCorrector1800beta01: CastSpellByName called. Code:
local myCastSpell = CastSpell; Any other thoughts on the problem? |
|
11-15-05, 08:12 AM | #10 |
very strange. I may have to load the code myself in-game and see what it does for my warrior. You are right that it looks like using the function innately within the actionbar doesn't seem to call your hooked function...
|
|
11-15-05, 08:23 AM | #11 |
your problem seems to originate from ActionBarFrame.xml
In this file, the OnClick script is set to the following: Code:
if ( IsShiftKeyDown() ) then PickupAction(ActionButton_GetPagedID(this)); else if ( MacroFrame_SaveMacro ) then MacroFrame_SaveMacro(); end UseAction(ActionButton_GetPagedID(this), 1); end ActionButton_UpdateState(); UseAction(ActionButton_GetPagedID(this), 1); This is the piece of code that actually calls whatever is in the button you are pressing. Unfortuantely it is a global api function. They can be used in lua scripts, but they are not exposed within any lua, meaning they cannot be hooked. Obviously due to it not being exposed we can't see how it is using the CastSpell function, although my guess is that it does not. It probably does everything the original unhooked CastSpell does, and more, but since you can't hook it you can't add any verbose log output... This falls back to the old "you can't use a skill without hardware input" rule. They didn't expose the function to hooking because doing so would allow automatic macro script to take over... Last edited by Beladona : 11-15-05 at 08:25 AM. |
|
11-15-05, 11:29 AM | #12 | ||
Great detective work!
Beladona,
Great detective work! I'd been coming pretty much to the same conclusion by reading through the API on WoWWiki, but you've gone the extra mile and isolated it in the xml. I'll also hook UseAction to make sure that I can do so and see the message, but from a cursory perusal of the API, it's not clear that I can determine which spell is hooked up to which action button. That warrants further explanation. (Details: It appears possible to know what spell is on which action button if the addon is running at the time the action is loaded onto the action button. But once loaded, there does not appear to be an easy way to distinguish between a macro action and a spell action. Textures could help. Tooltips could help. It all looks ugly.)
|
|||
11-16-05, 08:22 AM | #13 |
I am just saying that some global api functions are locked and cannot be hooked. UseAction might be hookable, but I am not sure. I couldn't find it in any lua scripts at all, which means it is not exposed. Try hooking it to see what happens, as it never hurts to experiement...
Maybe a combination of hooking UseAction, CastSpell, and CastSpellByName will get you what you need... I would try hooking it myself, but I am on my laptop, in the middle of reinstall, and still need to patch up to 1.8 Last edited by Beladona : 11-16-05 at 09:42 AM. |
|
11-16-05, 08:26 AM | #14 |
As posted in the WoW forums in your topic:
Code:
What you're specifically looking for is the following: MyMod_oldUseAction = UseAction function MyMod_newUseAction(a1, a2, a3) -- Call the original function MyMod_oldUseAction(a1, a2, a3) -- Test to see if this is a macro. -- If this is a macro, GetActiontext(a1) returns the label if GetActiontext(a1) then return end MyMod_Tooltip:SetAction(a1) local spellName = MyMod_TooltipTextLeft1:GetText() DEFAULT_CHAT_FRAME("Just caught a " .. (spellName or "nil")) end UseAction = MyMod_newUseAction |
|
11-16-05, 01:54 PM | #15 |
Cladhaire,
Yes, since seeing that post in the WoW forums, I've tried hooking UseAction. This basically works, but involves using a tooltip to determine whether the action stored in a button is a macro or a spellcast, and to find the spell of the name. I'm still experimenting to determine whether I can get the spell level. (Hopefully, since this appears in the tooltip for a spell that's been copied to an action bar, it will be available somewhere.) Interestingly, if you cast a spell directly from the spellbook, it *does* call CastSpell. Weird. --Q |
|
11-16-05, 04:42 PM | #16 |
I Think Right1 has the Rank information, or if it is a racial ability it contains Racial.
Righttext=MyMod_TooltipTextRight1:GetText() Then you could use string.find to return the rank.. Rank=string.find(Righttext, "Rank %d+") ... I'm just learning LUA and WoW stuff so slap me around if I'm completely off base. Edit something like this Code:
MyMod_oldUseAction = UseAction function MyMod_newUseAction(a1, a2, a3) -- Call the original function MyMod_oldUseAction(a1, a2, a3) -- Test to see if this is a macro. -- If this is a macro, GetActiontext(a1) returns the label if GetActionText(a1) then return end MyMod_Tooltip:SetAction(a1) local spellName = MyMod_TooltipTextLeft1:GetText() local spellRankCheck = MyMod_TooltipTextRight1:GetText() DEFAULT_CHAT_FRAME:AddMessage("Just caught a " .. (spellName or "nil")) DEFAULT_CHAT_FRAME:AddMessage("Just caught a " .. (spellRankCheck or "nil")) if not spellRankCheck then return end if ((string.find(spellRankCheck, "Rank")==1)) then DEFAULT_CHAT_FRAME:AddMessage("SpellRank: " .. (spellRankCheck)) else DEFAULT_CHAT_FRAME:AddMessage("Other Info:" .. (spellRankCheck)) end MyMod_TooltipTextRight1:SetText(nil) MyMod_TooltipTextLeft1:SetText(nil) end UseAction = MyMod_newUseAction Last edited by Grumpey : 11-16-05 at 09:12 PM. |
|
11-30-05, 10:43 PM | #17 |
I'm pretty sure all functions are hookable. If not, then very, very few are not.
UseAction is also found in ActionButton.lua, in ActionButtonDown() and Up(). The spellbook knows all the spells' ids and booktype, so it's not surprising to just use CastSpell, which takes the id and booktype. Getting the spell's name and rank would require another step to get the name from the fontstrings. Besides, most things to do with spells are done with id and booktype. |
|
01-04-06, 02:46 AM | #18 | |
|
||
WoWInterface » Developer Discussions » General Authoring Discussion » Can API fcns be hooked? |
«
Previous Thread
|
Next Thread
»
|
Display Modes |
Linear Mode |
Switch to Hybrid Mode |
Switch to Threaded Mode |
|
|