23.6.6 其实有一些错的和不到位的地方,但是鸽了
写给入门小白的教程(虽然我自己也是)
这个教程不会从lua基础开始,基础语法入门请看资料网站里的lua入门
主要是介绍ref菜单里object explorer里那一堆参数怎么找,怎么用,怎么改
因为喔也不会各种高级方法,只能止步于教会怎么使用sdk.hook这个东西
不过这个方法很通用,可以实现绝大多数的基础数值修改需求
以及,这个教程大多是经验型,不是系统型,只能说大概会用,道理和名词不一定对
O佬因为现实忙,教程鸽到下一作了,残念😭
不过O佬给了一些遗留的未完成资料,我会把它加到帖子里
ps:15.0岚龙版本已经对圆月进行了大改,收刀已经不用再修改,但可以看个思路
欢迎补充
reframework-book(里面包含了ref前置提供的各种API和操作的说明,很重要)
唯姐姐的lua入门教程(虽然是世界的但学的是用法)
菜鸟lua教程
技能ID表(一些技能类参数用的都是ID,可能会用到)
技能名称中英文对照
可能有用的一些函数(自己找的一些参数,可能有人用的上)
具体其实应该去reframework-book查看,包括什么类型能用什么样的操作,操作完返回的是什么,这里只是一个简短说明(而且说不定有错)
- sdk.hook 钩子,勾住一个方法并在它执行前后进行一些操作
- sdk.find_type_definition("typename") 拿到一个type
- sdk.get_managed_singleton("name") 拿到singletons里的object
- sdk.get_native_singleton("name") 拿到native singletons的viod*
- self:get_field("fieldname") 拿到特定field的值
- self:set_field("fieldname",arg) 修改特定field的值
- self:call("methodname",arg1,arg2,.....) 调用方法(method)
可以去输入法设置里把中文模式输入英文符号勾上,写代码时候会很舒服,因为输入字符串之类的时候每次需要输入引号得切中英输入,勾上这个会舒服很多(早没发现,淦)
或者使用中英文标点切换的快捷键
主要是学会ObjectExplorer的使用,能找到type,method,field
点击ref菜单的developertools(会小卡一会儿,正常现象)
主要分四大块
- Singletons
- Native Singletons
- Renderer
- Types
- 搜索区
- Type Name
- Method Name
- Field Signature
- ReObject Address
加粗的几个比较常用
Singletons和Native Singletons是平时寻找参数的地方,里面包含了很多Type(类型),但并不是全部。
Types罗列了所有的type 所以打开会很卡
搜索区一般搜Singletons和Native Singletons里一眼看过去比较难找的或者自己见过的,再或者就是搜猜的关键字来找一些需要的功能
忘了说,搜索区的功能对Singletons和Native Singletons也是生效的,就是输入关键字Singletons和Native Singletons的东西也会被筛选
我们打开Singletons里的一个名为snow.player.PlayerManager的type
主要看红框部分
可以看到一个Type主要由以下部分组成
Type Information 这个Type的基本信息
Reflection Properties 可以理解为属性,不用管,一般改不了
TDB Methods 方法:可供使用的Method(可以理解为lua的函数)
TDB Fields 字段:可修改变量(少数静态常量static const不能改)
从什么Type继承而来
我们主要看的就是Type里的method和field
我们先从field看起
这是一个经常用到的type,即当前玩家武器的基础数据
其通用名称是snow.player.武器英文名
我们去它的fields里看看
前面知道field是参数,那么这里的fields就是关于盾斧的一些基本参数
左边白字是数据类型,右边的蓝字是他们的name(变量名)
从蓝字的英文命名可以知道它们的基本作用
比如
_ShieldBuffTimer——盾,buff,计时器——红盾的buff时间
_SwordBuffTimer——剑,buff,计时器——红剑的buff时间
一般情况下的时间单位是帧,60帧=1秒
如何使用lua去修改这个找到的field呢,这个就要结合method了
前面说过method可以差不多看做lua的函数(至少入门确实可以这么看),打开method的菜单,可以看到大致有两类,一类是比较简洁的命名,一类是后面跟着一大串参数名,跟着参数名这一类代表可以传入参数(一般和call一起使用)
每个method基本都有确定的功能,所以一般要寻找和field对应的method
这里稍微找找就能看到两个method
setShieldBuff
setSwordBuff
下一步,验证这两个method能不能用来hook
右键这两个方法,点击hook
出现hooked methods窗口,call count指的是被调用的次数
很容易想到,开红剑和红盾的时候应该会调用这两个method
我们训练场里做一次对应的动作
可以看到确实如此,那么就证明两个method可以用来hook
即,能hook的method的标准是能在对应时机被调用(call)或者一直被调用
注意事项:有的type里的method只有一个.ctor,虽然会一直调用,但这个不能hook,会直接卡死
至此,准备工作完成,我们找到了三个关键要素
type,method,field
上一节我们找到了三个参数
这里我们以修改红剑时间为例
Type:snow.player.ChargeAxe
Method:setSwordBuff
Field:_SwordBuffTimer
让我们先看看ref给出的sdk.hook的用法
sdk.hook(method_definition,pre_function,post_function,ignore_jmp)
O佬的解释:Hook 即为钩子函数,这个函数的用途是当游戏内某个特定方法被调用时,该hook函数被激活调用。而pre_function则是用于在函数真正被执行前我们可以做的一些预处理,而post_Function则是用来截获并传回返回值
注意:sdk.hook是不用放进re.on_frame的,它的运行次数取决于hook的method被调用的次数,也就是method不被调用的话,就没办法用这个method
我们要输入三个变量(ignore_jmp一般不用)
method_definition,pre_function,post_function
method_definition:我们使用sdk.find_type_definition先得到Type,再使用get_method拿到方法,具体如下
pre_function:在method被调用之前执行的函数,如果是修改某个field,一般用这个函数,这里我们也采用把修改方法写进这个函数
至于为什么args[2]是对象指针,因为是ref定义好的,在上面sdk.hook的用法里有写(反正大多数时候直接抄就是,不过有时候也可能是args[3],看ref报不报错了)
post_function:这个函数在method被调用后执行,用于替换method的返回值,比如斩味补正倍率,hook住method后用这个函数替换返回值,就可以修改伤害倍率了,在这里我们不需要,用一个输入什么参数就返回什么参数的函数替代
接着我们把参数填入sdk.hook
这样就完成了,把红剑的时间修改为了120s
分别定义再填入比较便于管理和美观,平时如果查看一些lua很多会是这样
和上面的hook都是一个东西
注意事项:这个只是最基本的hook框架,实际上最好添加一些判断条件,比如是否处于战斗场地,武器类型等等
修改后如下
可以看到用什么函数就在哪个函数里加条件就行
嗯?这里就结束了吗?标题的无限buff呢?
是的,setSwordBuff只在开红剑时被调用,只有开一次红剑才能达成所谓的无限buff(把时间设的超长就行)
有没有那种没有特定条件的还一直在被调用的通用method?
有的,那就是update
update是个很常用也很实用的method,基本上有这个都勾这个(不过也看情况,不是无脑勾)
可以看到update按帧运行,每一帧都会被调用,而且,貌似update是接收了所有的参数,因此可以用来修改所有field,我们把上面的method换成update,就可以每一帧都把红剑buff时间的field都设成120s,也就是锁了,这就是无限buff
Action一般不用找和搜,因为BHVT能直接显示node的action的详细参数
以修改箭斩的GP帧数为例
随便找一个箭斩的GPAction
首先得到了type的name
snow.player.fsm.PlayerFsm2ActionDamageReflex
method有三个
start,update,end
即action的开始,生效中,结束
因为动作action这类数据不用实时更新,这里method选择start就行
GP实际帧数是endframe减startframe,这里field改endframe就行
问题来了,snow.player.fsm.PlayerFsm2ActionDamageReflex这个action是很通用的,怎么确保只修改了箭斩GP的action
这里看下field里的参数
_Type的数值是2,对应Bow_JustEscape,所以这项是用来标志这个damagereflex是箭斩GP的类型
我们可以用这个做条件,用get_field拿到他的值作为判断条件
这样就只修改了箭斩的GP时间
修改condition之类的同理,用一些标识性的值或者其它的做条件都行
这里只是简单提下sdk.hook自带的一个特殊返回值
sdk.PreHookResult.SKIP_ORIGINAL
消耗弹药是通过一个叫consumeItem的方法执行
所以如果能让这个方法不被调用,就可以实现不消耗弹药
这里通过在pre_function里return一个sdk.PreHookResult.SKIP_ORIGINAL的返回值,来跳过被hook的方法的调用
例子如下
不过要注意的是,用这个办法跳过了method的调用,后面的post_function也依旧会生效
在上面无限弹药里我们知道,返回一个特殊返回值可以跳过原方法的调用
仅对于圆月收刀消失这个条件(碰撞消失是另外的)
它由snow.player.fsm.PlayerFsm2ActionLongSwordDestroyShell020这个action控制
这里小提一嘴对于圆月来说,它的关键字是SpacingShell和Shell020,前者是它本身,后者和它生成的追击shell有关,怎么合理的通过动作和关键字等搜索相关的method和field才是重点
因此想办法跳过这个action的调用就可以让圆月收刀不消失
BHVT里搜索这个action,选择它的start方法hook一下,开圆月再收刀可以看到被调用了,所以这里用start
这里sdk.hook只填了两个参数,method和pre_function,不对post_function有操作的话不填也没事
这里我只简单提一嘴
上面讲到,可以通过跳过action的调用达成圆月收刀,但是,对于Action这类Type,它本身就是可以进行开启和关闭操作的(BHVT里左边开关按钮)。
这种方法可以不用hook,当然用也行
不用hook的话,首先拿到action的object(用O佬的toolkits或者你可以抄一下拿到action的方法)
这一段是启用片手一个废案action,hitindex是39,启用它并修改对应的col可以多一个可用的伤害参数。
也可以这么写
action:call("set_Enabled", true)
这两个是等效的,一个用的是set_field,一个用的call,如果你看了目录里的【method和call】就会理解
所以,把这个当作业把
- 用hook,使用set_field或call关闭圆月收刀取消的action
- 不用hook,构造一个含所有圆月收刀取消ActionIndex的table,使用迭代器批量修改
一般对于计算返回值的method,比如这里的斩味伤害倍率,可以使用post_function截获它的返回值并进行修改,再将修改后的值传回去,这样就达成了修改目的,示例如下
这段的功能是检测是否在怪异任务内,如果是则将原本的斩味倍率乘2(相当于两倍伤害),不是的话返回原值
注意,这段判断masterplayer的时候,return后面少了个retval,具体请看注意事项
O佬的话:想拿一个Type,第一个想法不应该是钩子,而是去检查Singleton或Native Singletons ->框架所提供的有用函数,这两个分别能用ref提供的sdk.get_managed_singleton(name) 或sdk.get_native_singleton(name)直接拿到
这部分有空再补,手头暂时什么例子
method很多时候数量比field还要多,而它们本身就带了特定的功能
因此很多时候找一个合适的method会比找field效率更高
而call就是调用的意思
这里喔用一个简单的例子来说明
Type:snow.player.PlayerBase
我们找到它有关于斩味的两个method
从命名可以知道
get_SharpnessGauge是获取斩味的值
set_SharpnessGauge(System.Int32)是设置斩味的值(类型声明留着,跟着的value不要)
snow.player.PlayerBase不是singletons里的,记得用hook
那么在hook里,我们可以这样使用
local this = sdk.to_managed_object(arg[2])
local SharpnessGauge = this:call("get_SharpnessGauge")
(这里我们假设在field里有一个_SharpnessGauge)
那么这句就等同于
local SharpnessGauge = this:get_field("_SharpnessGauge")
意思就是,直接调用这个method,这个method就会完成它对应的功能,很多时候会比操作field简单,况且有时候field没有的method可能有
不过set_SharpnessGauge后面的括号说明了它需要传入一个Int32类型的参数
this:call("set_SharpnessGauge(System.Int32), 400")
这句是把斩味设置为400
差不多就这样,其实会个大概就差不多会自己找参数了
- 不要勾.ctor
- hook容易冲突和不生效,所以不要滥用,但是又不报错,可以把文件名改改,前面加个”!1”让文件排到最前面可以在一定程度上减轻这个问题,但也不能保证一定生效
- 多加判断条件,减少冲突
- 使用pre和post取决于自己,也可以在pre里拿到数据作为条件给post用
- 在post函数里,一般不能直接用第一句的写法,这样会返回空值,做判断条件时,函数接收的参数是什么就返回什么,比如一般接收参数命名是retval,这时应该使用return retval,像第二句(虽然有些函数本来的返回值是空,这时返回空不会出问题)。但为了避免这种问题,不如直接按if原本的用法写,像第三句
if not xxx then return end (x)
if not xxx then return retval end (√)
if xxx then 执行 end
因为比较笨比,所以花了挺久才算刚入门这个东西
在此对每个或多或少对我有过帮助的人表示感谢😘
Orcax,LingSamuel,南风焓,云雾敛,零酱,玄耀,鸦科鸟类,praydog,Bimmr
排名不分先后(想起谁写谁)🥰