0x00 前言
随着学级的提升,任务的数量和种类原来越多,深感时间越发不够,犹豫再三还是决定把杀时间却对自己没有什么提升的兴趣爱好逐一舍弃掉,首当其冲的就是占用较多碎片时间以及睡前时间的手游。
弃坑每个手游的时候,在微博上都会发下在下作为开服玩家的仓库和BOX之类的,为了在遥远的未来和别人谈论起这个游戏,能用这些截图证明自己不是云玩家,而是真实确实参与过的。
但仅是如此的话,我觉得还不够吧,不如以一个计算机专业学生的视角,从一些别人不常看到的角度来讲讲,顺便记录一下由于一时兴趣而耗费在这个游戏上的那段时光。
写给被舍弃掉的 BCR 手游,讲述我所了解的这些游戏的数据、战时内存以及对于防修改的一些建议。
(本来想发到52pojie,但太久没去登陆帐号没了 QvQ)
P.S. 本篇主要用于给数据挖掘与内存理解上已经足够熟练的Coder进行学习交流(以及留档),
本文仅提供分析,不给出定位及修改过程,并会刻意省略零基础玩家到内存分析中的gap知识点,
防止普通玩家基于本篇对游戏肆意修改破坏游戏环境。
0x01 数据分析
在本节我们简单分析一下这个游戏中角色的主要属性以及用途,分析往往需要建立在对数据的理解上。
上述两张图是一个角色的属性面板,是一个中规中矩的属性设计。
简单的描述一下其中几个数值:
- 物理防御力、魔法防御力:采用类似英雄联盟的计算方法:$Dmg = Dmg_{origin} \times \frac{Def}{100+Def}$
- 鹰角你看看呀!现成的公式他不香吗?!攻击减防御的减法是什么鬼啦!!!
- 技能值消耗降低:本游戏中的技能值(称作TP)满了之后可以释放大招(称作UB),释放大招后TP会被清空,但本项属性如果有值,则会留下该项值百分比的TP量。例如:技能值消耗降低为 10,那么放完UB之后会剩下 10% 的TP槽,该项不允许超过100。
- 生命值吸收:对于每次攻击、技能、UB,根据一定比例回复自身的生命值。目前看起来可能是百分比,该值允许超过100,即允许回复超过 100% 伤害的回复量。
0x02 内存分析
在得知有如此多的属性之后,我们会比较自然的想到,对于每个角色而言,这些属性都是一个示例的成员变量,
那么——这些数值应该会离的很近?这些数值的加密方式应该一样吧?这些数值是临时变量还是固定变量呢?
战时内存总览
首先上结论,在内存中,他们大概是这样一个分布的相对位置:
- 内存中,每个角色自成一块,5个出战角色所占用的内存之间并不相邻
- 数据分为两拨,前半段是角色这些属性的基础值,后半段是当前值。例如在战斗中释放了攻击力短暂上升的buff时,后半段的攻击力会增大,而前半段的不会
- 对于每个角色来说,不同属性参数之间的相对偏移量是固定的。例如魔法攻击力就固定在物理攻击力的右边三格(此处的”格”是通俗说法,严谨一些叫做 “4字节 DEC表示”)
- 对于每个角色来说,参数所处的内存地址是临时地址,即每次战斗开始时产生,战斗结束后收回。也是因为观测到这一点,我才可以断定数据是在本地进行计算,战斗结束后发回给服务器。这才敢于在每次战斗中肆意测试,然后记录下需要的数据后杀掉游戏进程不让它完成这场战斗(然后丢失一点体力,哈哈哈)
- 为了不被用于恶意开发,表格简单隐去了ox符号的定义图例、部分属性参数以及直接修改HP/TP的位置(提示:TP为单精度,上限1000.0)
战时内存应用
如上图所示,在进入战斗后,所有角色会从某个初始值开始(通常为 HP满 TP空,地下城之类会保留状态的特殊场合除外),随着战斗的进行会持续进行变化。
那么,有哪些是不变量、哪些是变量、哪些是可控变量,这是定位数据的基本:
- 右上角的时间是一个可控变量,因为我们可以通过按下暂停使得时间停住不动,时间通常不会是离散整型来跳的,不然会存在很多问题,我们可以直接通过单精度四舍五入的方式来获取时间的地址。
- 在任务跑进场内放出第一个技能之前,他们的属性就是没有被buff过的基础属性,而此时是可以暂停的。那这时则是在内存的海洋里搜索特定变量的最佳时机,那些数值较大且重复率较低的(例如物理攻击力或魔法攻击力)则可以将搜索结果控制在2-10个。
- 时间所处的位置是游离的,但属性之间是相邻的,通过定位单个属性即可找到整片内存。
0x03 发展历程及简要修改建议
艰辛的发展历程
那么,有些擅长使用CE在怪物猎人世界里横行霸道的小朋友就不高兴了:“我搜了,搜不到!大骗纸你骗棱!”,很好,这层薄如蝉翼的玻璃纸还是能防住一些人的,吾心甚慰……
常见的定位内存的方式为如下两种:
- 先给定一个值,将其进行小幅度增减变化后再搜变化后的值,常见于MHW改珠子、FGO改血量
- 对一个初始值未知的变量,将其增加后遍历所有增大了的值,再减小找谁变小了,常见于FF14改视野、MC改坐标
但 BCR 虽说是一个几乎裸奔的游戏,它还是想到了防止这两种初学者可以轻易修改的方案,那就是可逆映射:
小课堂:对于任意值 $x$,有函数 $f$,允许 $f^{-1}(f(x)) = x$,则我们称这个映射可逆
可惜的是 BCR 的程序员偷懒,选了一个 $f^{-1}$ 和 $f$ 等价的映射操作,映射操作中涉及的特征值又在内存里泛滥成灾生怕人看不见(因为初值为0的变量太多,特征值就等于是送的,这在近代战争的密码破译中也很常有),这就导致曾经竞技场出现的那一波遍地神仙的热潮。后来特征值减了1(烟雾弹),导致会用这操作的少了不少人,大概是研究的人决定圈地自萌不给下游发程序了。
“一个更新之后,介入了战斗日志查询功能,这样做出修改的人们会在战斗信息的记录中被发现……”
—— 真的吗?我希望是真的,但事实上,战斗信息的遍历消耗的资源太多,从公会战每人最多三刀都需要查一周可见一斑。而运营方也很难,一是不太好对搜内(原公司)的代码进行大改,原逻辑中,包会发送信息,但量实在太少,真正可以用来校验的东西太少,大多数情况还是靠几个有限的战斗记录槽,让玩家来举报。
可能的处理方案
特征值定期更改
目前的位计算映射是个非常赞的方案,特征值不知道是写死的还是变量,可以学隔壁FF14每次小版本更新必改 op_dict
映射,这样至少可以大幅降低非专业玩家的修改普及率(会改的多,但会找的人少,散布没多久就又要从根节点重新 n-hop jump)
增加校验值
战斗结束后可以发送一个校验值,这样可以以最低的字节数包含尽量多的信息量,例如:
- 当前队伍可能的最高伤害,以及实际造成的结算伤害
- 精确计算的话可能需要用DP多重背包,不推荐
- 粗略计算的话,计算最高可能的TP值以计算UB数量,以时间计算普通技能循环次数,不考虑打断等延后仅考虑全程满时间buff,向上取整
- 不从伤害考虑的话,可以偷偷加一个UB释放次数、攻击力变动情况的计数器或者flag,随着校验码加密后一起传回去。
希望这个游戏越做越好,本骑士君先告别兰德索尔啦,有缘再见!