CONST奶舟的插件教程如下:
http://bbs.duowan.com/thread-18088959-1-1.html
唐逸雪的插件教程如下:
http://bbs.duowan.com/thread-19418409-1-1.html
在自己动手写插件之前,建立一个良好的调试环境是必不可少的。因为, 我们很难让插件第一次写出来就按我们预期的想法去工作。参数传错,函数名记错,大小写混乱,少打一个字母,甚至中英文输入法切换时不小心按了一个中文的“,”,都可能导致插件出问题甚至完全无法加载。所以,我们需要一个工具,它能告诉我们,在任意时刻,插件如果出错,它错在了哪,而如果它正确运行,又是怎样的一个状况。
这里我们用到的工具便是DECODA,一个异常强大的LUA调试IDE,可以百度搜索从它的官网下载最新版本,有30天的试用期,用OD做个小PATCH就能用了。如果不会,也有一些以前版本的破解版,可以从CSDN上下载。唯一遗憾的是它是一个英文软件╮(╯▽╰)╭~英文苦手的童鞋们请准备好金山词霸。
Decoda官方网站:
http://www.unknownworlds.com/decoda
下载decoda.exe到电脑并运行、安装之后,打开,可以看到如下的界面:
decoda可以将自身注入到其他进程中,HOOK一些关键的LUA API,并创建LUA解释虚拟机监视LUA栈的动态(不明白这段话也没关系)。现在我们用decoda来启动剑三,将调试器注入到剑三的进程中。( 如果你正在玩如DNF、永恒之塔等使用了NP/TP反外挂工具的游戏,请先将它们彻底关闭,NP/TP会阻止所有DetourCreateProcess的调用。另,杀毒软件也有可能将你的行为视为异常,如卡不死机~)
点击菜单上的DEBUG(调试),再点击下拉菜单中的START DEBUGGING(开始调试),decoda会弹出如下的界面要求你为它提供参数:
第一行Command:剑三启动文件jx3Client.exe所在的路径,一般在你的剑三安装目录\bin\zhCN下,可以点击右边的"..."这个小按钮来直接打开。
第二行Command: 剑三的启动参数,请照着填~为什么要这个启动参数呢,大家可以做个试验:直接运行剑三目录下的jx3client.exe,打开的是剑三升级的程序gameupdater.exe,而再点一下开始游戏,才能真正开始启动剑三。因此,这个参数是告诉jx3client.exe,别打开升级程序了,直接启动游戏伐~因为我们要注入的是J3本身的程序,如果打开剑三升级程序的话,就注入不了了。而这个参数是怎么来的呢,可以用VS写1个小程序,去换掉jx3client.exe,然后运行gameupdater.exe拦截启动参数argv[],就得到了……不会的童鞋还是照着上面的填好了~~这里唐MM在UNPACK那里已经说过了。
第三行working: 本次工程的工作目录,在你选好第一项以后会自动生成,不用去关心。
好了,点击OK,剑三便启动了,看起来是这个样子的:(由于会拦截剑三加载的LUA文件,所以启动过程会相当无比很极端慢,请有耐心哦)
在这个界面会卡很久,请大家耐心等待,直到所有的LUA文件都被拦截为止。
好了,在程序顺利启动之后,在游戏账号登陆界面,我们切换出来到decoda,可以看到如下的界面:
在最左边的那一栏,就是游戏在加载到需要输入账号密码时,读入的基本LUA。由于剑三的表层逻辑都是使用LUA完成的,所以我们看一下剑三自己的LUA文件的内容,也可以熟悉一下游戏的基本工作流程。
我们现在随便点开一个左边的LUA文件,看看它是什么样的:
现在我们点开的是talent.lua,也就是镇派系统的后台操作文件。点一下talent.lua前面的小箭头,可以看到它里面写了哪些函数~这样如果以后记不住lua文件的名字,也可以通过函数的名字来查找定位了。双击talent.lua,刚才还是灰色那个大大的窗口呢,现在出现了很多代码。这就是剑三的镇派后台代码,不过,现在可以不去深究它写了什么~~只是熟悉一下应该怎么打开一个LUA文件。
好了,现在输入账号密码,我们登入自己的人物:
登入人物时,在读条的时候,剑三还会加载一些和当前人物,当前地图有关的额外LUA文件,所以仍然会有一些卡。登陆完成以后,decoda的左边的LUA文件列表进行了刷新。
恩恩,现在我们进入了剑三的世界,那要从哪里开始“调试”呢。我们先问问自己,我们想知道什么呢?嗯~大家都比较关心的一个动作:技能释放,就这个好了。技能释放这个事件剑三在客户端后台是怎么用LUA文件实现的呢,我们来查找一下~
首先,技能的英文名是什么,技能:Skill,好的,我们在decoda左边的LUA文件列表里输入"skill",像这样:
好的,这里搜索出了很多东西。从英文来判断,skill是技能,panel是面板,Recipe是秘笈,update是刷新,恩,那肯定是只有SKILL那2个文件了。这里稍微说一点的就是最后1个SkillUpdate()前面的图标表示这是一个函数而不是一个文件。接下来呢,学过编程语言的都知道.h是HEADER头文件的意思,一般不会把实现放在里面,所以,我们要找的就是skill.lua。
双击Skill.lua,在里面找呀找,发现了一个函数,叫做OnUseSkill(),这个看起来挺像的(其实是剑三自己在那里留了一句注释╮(╯▽╰)╭):
也许,现在只是猜测,我们在使用技能的时候剑三就会调用这个函数。OK,怎么证实呢?~那就是:下断点。
这里的断点和一般程序里的int 3不是一回事,decoda是在VM里拦截了对LUA栈的访问。不过我们不需要知道那么多,我们只需要知道,如果使用技能是这个函数的话,断点顾名思义剑三的运行就会在这里停(打断)下来。
实践是检验真理的唯一标准,我们不妨试试。请看上面的图,我们先在OnUseSkill()这个函数的下面第一行点一下,然后点击DEBUG菜单,点击BREAK POINT,或者直接按F9,那一行的最左边就出现了1个红色小圆点,意思是:程序执行到这里,请停下。
好的,现在我们用1个技能试试,就用天策的招牌之一,撼如雷:
点一下,剑三停住了!无论你怎么点,游戏都不会再有反应。
这个时候我们说,剑三陷入了断点的“陷阱”,或者说在OnUseSkill里被“断下来了”,再次切出去看下decoda,发现刚才的红色小圆点里面出现了一个黄色的小箭头:
表明剑三程序运行到这里被停住了。下面,我们来读一下它的代码。
好的,读一下上面的代码,这次我们不涉及具体的程序写作,所以只读第一个IF语句:
if nSkillID ==605 then
RideHorse()
return
end
if在英语中是如果的意思,SkillID可以翻译为技能ID,RideHorse当然就是骑马拉。
所以,用英文来说上面的代码,翻译过来意思就是:
如果 技能ID 是 605 那么:
骑马~
函数返回(就是后面不执行了)
判断结束(end)
那么,很显然,这几句话的意思是,如果我们点的是“骑御”这个技能(605),那么,就执行上马动作(在后面的教程中我会详细写如何用F11跟进去直到不再是LUA而是CPP里的函数为止,可以看到RIDEHORSE函数主要是判断是上马还是下马)。
好了,很明显,ID为404的撼如雷,不是骑御,所以,我们按F10,单步执行,可以让剑三一条一条语句的执行代码~方便我们看到整个过程是如何一点一点完成的。鼠标移动到撼如雷上按CTRL键可以看到它的ID和等级:
连续按F10,可以看到代码跳过了这两行:
RideHorse()
return
因为用撼如雷时传进去的不是605而是404。因此,如果不成立,程序跳过中间的过程,直接去下面执行了。
好,现在按F5(continue),程序会立即朝下执行,直到遇到下一个断点为止,没有的话,就会继续了~。此时切换回游戏,发现它活过来了,而我们也用出了撼如雷这个技能。
接下来,我们要完成一件事,那就是,在上面的代码中,我们可以看到,如果技能的ID是605就会去判断上下马。那么我们把605改为404,能不能把撼如雷这个技能改成上马呢?
答案是可以的。
再次在刚才我们下断点的那一行按一下F9,就会取消断点。剑三就不会在那里停下来了,现在我们来修改那几行代码,只修改1个数字:技能ID。
将刚才的nSkillID==605改成404,就是雷的ID。然后,点击左上角菜单中的file,再点击save,如图
点击save后,decoda会自动识别lua文件能被调用需要存在的目录(这里涉及到包内相对/绝对目录的问题,不详述),所以不用我们去操心,直接点确定就可以了。
在存好以后,我们并没有真正改变skill.lua的内容。因为程序读取的是从pak中提取的skill.lua,要让剑三使用我们修改过的skill.lua,必须要重新加载。我们可以写一个宏,像这样:
正常使用剑三的童鞋是无法使用这个宏的,因为涉及到一些可以被利用的操作,绕过sheildlist的方法不公开。
点一下这个宏,我们就重新加载了skill.lua,这时的decoda的lua文件窗口变成了这样:
出现了2个skill.lua,其实2个都是被修改过的版本,原来那个已经被替换掉了。(这都是在内存中执行的操作,所以童鞋们不用担心游戏被破坏掉……关一次就没了)
好了,我们直接点击撼如雷这个技能,看到,我们开始读条上马了,天策的技能“撼如雷”被替换为了“骑御”:
下马,我们切到decoda,在刚才那里重新下一个断点,再次点击撼如雷:
程序在这里停下来了,按一下F10,单步,我们发现,就像上图的黄箭头一样,剑三判断我们点的是404技能(撼如雷),开始调用RideHorse了。
既然知道了如何对剑三自己的LUA下断点和监视,很显然,作为同样是LUA文件的插件,也可以这样操作。下面,我们以我自己写的那个一团糟的天策自动PVE插件为例,来看看如何调试自己的插件。
PS:表想用这东西让天策用阳明指- -,这都是改的客户端呀客户端,没什么大用滴。
恩,让我们退回人物选择界面。就像唐MM回复的,decoda最大的问题就是慢的像SHI一样呀╮(╯▽╰)╭,不过好处是可以比较方便的看到call stack,看到如执行插件剑三是用waitforsingleobject来阻塞的线程。现在退回也很慢很慢,请耐心……退回以后,加载一个自己写的插件,这里我加载了天策PVE插件:
再次经历卡卡的过程进入游戏,在decoda的lua文件窗口中输入AXPVE.lua(就是傲血PVE- -),可以看到剑三将它加载了:
双击打开,我们来看看这个一团乱麻的插件。这个插件有一个开关,可以去快捷栏设置里设置,控制是不是要利用petactionbar漏洞自动打怪:
给它设置个快捷键,我们来跟进去看看这个开关能否运作。阅读代码,可以在插件的最后部分看到给菜单栏加开关的代码:
这里用1个cSwitch变量来进行开关的控制~
这个插件是怎么做的呢,请好好阅读顶楼里的2篇大神帖子,可以看到,是利用背后隐藏窗体的OnFrameBreathe()来执行的,1秒钟16次,我们看看这个函数的执行:
如果看不明白,其实很简单,就是:如果 cSwitch 等于 0 则 直接不执行整个插件实体。这样,我们只要控制cSwitch的值就可以达到开关的目的了。我们在这里下个断点。整个剑三会立即,马上停下来。因为每秒钟要执行16次。我们可以分别在打开开关和关掉的时候用F10单步一下,发现它还是起作用的。
接下来我们来实时观察一下我们的血蓝数值。
我们先飞洛阳,由于我是天策2内的关系,没有镇派,很难测试,只有先介绍一下如何观察我们插件中的变量数值的变化。
选择一个木桩,先暂时别打开开关,这样,插件就不会执行到下面去,我们在下面下断点,剑三就不会停在那里拉。现在我们就在刚才的if语句的下面一点点下一个断点:
为什么会有IsZhiCan,UseHanRuLei这种脑残到家的中英文混用变量名字呢,可能因为锅本身就很2伐╮(╯▽╰)╭。
好了,下好断点之后,我们切回游戏,打开开关。游戏立即就被停在UseHanRuLei这里了。
按F10慢慢单步下去,单步一下,就用鼠标移动到刚才那句的变量上,比如UseHanRuLei,可以看到,鼠标旁边出现了一个小标签,给出了UseHanRuLei的值:true(悲催的QQ截不了鼠标悬停的图,下次自己写个伐……)
直到单步到这里:
先停在CurMana=cplayer.nCurrenMana那一句,然后,像上图一样,选中manaRate那个变量(和WORD里选一堆字一样,按下左键拉),很显然manaRate是现在内力比例的意思,就是内力还剩百分之多少。选中之后,放开鼠标,再把鼠标移到蓝色的选中框上面,点下左键,按住,将它拖到decoda下面的watch窗口里去,如图:
Watch窗口在整个decoda中的位置:
下面一排从右数第二个。
Watch窗口里,Name是你给变量起的名字,Value是它现在(执行到当前一句代码时)的值,TYPE是指它的类型,这里manaRate是个number(数字)。
只要把变量拖下来,我们在单步走的时候,一旦改变了这个变量的值,Watch窗口也会立即改变,可以用来监视和分析变量有没有按我们的想法变化。
童鞋们可以从这里的Watch窗口中分析出我在铁牢心法下满血是46434,当前还有87.8914的内力。(不得不吐槽GWWo(╯□╰)o老娘4南皇呀……血还没蜀风一内的多)
困了~今天最后说一下CALL STACK
在watch的左边的左边,有一个call stack窗口,这里的意思是:调用函数的堆栈,和VS里的基本一样。我们先普及一下神马叫调用函数的堆栈(精简版,实际不是这样的):
假设有一个函数:
打坏人()
{
选中坏人();
发技能打他();
return SHI了还是没SHI;
}
而选中坏人()是这样的:
选中坏人()
{
用鼠标点坏人();
return 选中没有;
}
那么,在执行打坏人()这个动作的时候,执行到选中坏人()这一步,系统会先把打坏人()压到堆栈里去,堆栈看起来会是这样:
call stack:
打坏人()
然后,执行选中坏人(),选中坏人里会去调用用鼠标点坏人(),这个时候系统又会把选中坏人压进去:
如果我们在用鼠标点坏人()里下一个断点:
用鼠标点坏人()
{
断点 点一下;
}
就会看到如下的CALL STACK:
call stack:
选中坏人()
打坏人()
而执行完用鼠标点坏人()后,系统会去找:是谁调用的用鼠标点坏人()呀,这时候它会从call stack的最上面(顶)去拿函数,就是选中坏人(),并且跳回到选中坏人()去执行。这时候的CALL STACK又回到刚调用选中坏人()的时候那样了:
call stack:
打坏人()
同理,执行完选中坏人()以后,会从call stack中拿最上面的函数,就是打坏人(),然后跳回打坏人()去执行。
说了这么多,其实可以概括一下:
call stack里面保存了执行到这个断点所属于的函数为止的函数调用顺序,像上面的例子,从下到上,可以看到是通过打坏人()->选中坏人()->用鼠标点坏人()这样调用的。
在decoda中callstack窗口像这样:
箭头表示正在执行axpve.lua中的函数(由于某些原因没截好图(⊙v⊙),没看到函数名字,拍爪)
对于一般的插件作者来说,需要过滤掉其中大部分的WINAPI和CPP的函数,这里大家可以专门去找自己插件的名字.lua里的函数,就能看到是怎么调用过来的了。
CALL STACK可以帮助判断函数调用的流程,迅速找出是不是通过正确的逻辑去调用的某个函数。比如上面的例子,如果连选中坏人()都没执行就去执行用技能了,那肯定是哪里的逻辑写的有问题。
CALL STACK的另外1个巨大作用就是可以看到剑三在调用LUA文件时用了哪些API,都是下钩子的好地方……
好了第一篇就暂时到这里伐╮(╯▽╰)╭,对于用惯了OD和VS自带调试器的人来说,decoda是一个迅速上手的好工具。对于新手来说,能够实时跟进,随时知道自己的插件和系统的逻辑执行到哪一步,也是一个不错的选择。
= =困了,如果最近不忙,将为大家带来第二篇:插件的基本语句、工作原理、剑三API和几个有趣的例子