究极绿宝石5.3——科普向,什么是金手指(四)
说在前面:
上一期的专栏,作者介绍了映射表这个概念。从抽象的层面上来说,电子游戏中的一切都是数字;从具体的层面上来说,变量地址和变量取值的映射表是将游戏中的数字与实体联系起来的桥梁。变量地址的映射表只有一张,而变量取值的映射表有很多。想要理解金手指的原理,需要从这些映射表入手。
本期专栏还是讨论“变量与实体的映射表”这个话题,使用VBA模拟器中“查找金手指”的功能,难度要比上一期更大。

查找金手指例3——队伍首位精灵的个体值在哪里?
玩精灵宝可梦系列游戏有一段时间的玩家都会知道,游戏中有精灵个体值的设定。每个精灵都有自己的个体值,从第三世代开始,分为HP、攻击、防御、速度、特攻、特防,共六项,每项的取值是0~31。如果单项个体值是31(满单项个体值),玩家们通常就称此为V(道理是这样的:将十进制数31转换为三十二进制,三十二进制中,0~9仍然表示十进制的0~9,然后从字母A开始表示10,B表示11,……直到V表示31),例如“速度V”就是速度的个体值是31。有几项个体值是满的,就称为“几V”,例如六项个体值全满就称为“6V”,少了一项就称为“5V”。很多玩家热衷于孵蛋,其中有一个目的就是生出6V的精灵。
在有些特定玩法下,也可能需要刻意拉低某项个体值,比如玩戏法空间的空间队,希望速度的个体值是0,这时候可能就需要一个“0速5V”的精灵;比如特攻手希望受到“欺诈”这个技能的伤害越小越好,就想让攻击的个体值是0。
在究极绿宝石5中,紫堇市的街头有个人会给你看队伍首位精灵的个体值;打败茵郁市飞行道馆门口的真嗣后,他会给主角一个能力值查看器,能够查看所有精灵的个体值。我们现在的问题是:队伍首位精灵的个体值,它映射到的变量地址是多少?
掌握了第三期专栏中介绍方法的读者,可能会自己尝试一下,先用能力值查看器或者紫堇市街头的那个人看一下自己精灵的某项个体值,然后在“查找金手指”的页面中输入这个个体值的数字,进行第一次查找。接下来把队伍首位精灵换成另一个个体值不同的,然后再次在“查找金手指”内查找,看看是不是搜索的范围缩小了。对本段内容还不熟悉的读者可以回顾一下第三期专栏。
很遗憾,这个方法对于搜索个体值的变量地址并不适用,原因就出在“搜索某项个体值”这个操作上。精灵共有六项个体值,事实上,这六个数字是放在一起的,单独查找哪一个都找不到,因为其中的某个数字对应的变量取值既不是2位十六进制数,也不是4位和8位的。对于“这六个数字放在一起”这句话究竟在数学意义上怎么理解,神奇宝贝百科给出了答案:
个体值由2位十六进制数(00~1F,即十进制下的0~31)表示,占用5位字段。……
在第三世代中,所有的个体值一起存储在一段32位(4字节)字段中,第一位决定了宝可梦的特性,第二位决定宝可梦是否为蛋的状态(0为否,1为是),接下来的三十位依次决定宝可梦的特防、特攻、速度、防御、攻击、HP的个体值。如果一只宝可梦只有一种特性,那么其对应字段首位就只会是0。
这段话是神奇宝贝百科(https://wiki.52poke.com/wiki/)中,搜索“个体值”词条所出现的页面上的原文,信息量非常之大。之前不了解计算机知识的读者,就算看到这段话,估计也会略过。但在我们看来,这简直是官方的大福利啊!官方把个体值的变量取值是怎么组织的详详细细地告诉玩家,那我们就按照官方的说明,把六项个体值“组装”成一个变量取值。
按照官方的说法,个体值“占用5位字段”,这句话的意思是说,单个个体值占用5个二进制位,原因非常自然,要表示0~31共32个取值,5位二进制数刚好够用()。再结合下面这句:“所有的个体值一起存储在一段32位(4字节)字段中”,32位二进制数就是8位十六进制数,于是我们知道了个体值的变量取值的长度,和上一期专栏中的金币数是一样的。最后,这句话“接下来的三十位依次决定宝可梦的特防、特攻、速度、防御、攻击、HP的个体值”,更加自然,因为5*6=30,六项个体值,每项占5个二进制位,一共占30位。
俗话说得好,“一图胜千言”,把引用自神奇宝贝百科的话总结成一张图,如下所示:

这个数字来自于下图的比比鸟,是作者临时测试随便抓的一只精灵:


究极绿宝石5虽然整合了第一到第八世代的精灵和剧情,但本源还是第三世代的绿宝石,因此组织方式按照第三世代的来,让我们依次分析这个32位二进制数(9dee7a69(16))是怎么组成的:
最高位是1:按官方说法,“第一位决定了宝可梦的特性……如果一只宝可梦只有一种特性,那么其对应字段首位就只会是0”,这句话的意思其实说的是最高位决定了精灵是哪一个普通特性。比比鸟有两个普通特性:锐利目光和蹒跚,分别对应到0和1。图中的比比鸟是蹒跚特性,因此最高位是1。
次高位是0:按官方说法,“第二位决定宝可梦是否为蛋的状态(0为否,1为是)”,比比鸟现在当然不是蛋的状态,所以是0。
下一个连续的5位:特防个体值,从能力值查看器上可以看到,比比鸟的特防个体值是14,用win10程序员模式的计算器(这个计算器在第二期、第三期专栏都用了好多次了,不会还有读者不知道吧?)可以换算成二进制的01110(2),正好和图片中的对应上。
再下一个连续的5位,以及之后的个体值就交给读者们自己验证了。组装好之后,我们就可以在“查找金手指”的页面进行搜索:

有两点需要注意。“数据尺寸”这里要选32位,因为要查找的变量取值是32位二进制数。另一个是“有/无正负之分”这里,之前的用法都是用的“不分正负”,这里用十六进制会方便一些,当然也可以先用计算器把9dee7a69转换为十进制,再选择“不分正负”。
搜索结果如下:

有4个搜索结果,范围已经不是很大了,接下来我们需要“追踪式变量取值查找”,对首位精灵的个体值做一下修改,然后继续查找。修改首位精灵的个体值有个最简单的办法,就是把首位精灵换成别的精灵。作者再随便抓一只飞天螳螂,“查找个体值”的界面果然发生了变化:



新抓了飞天螳螂后,再次查找,也不需要点击“查找”按钮了,发现只有一个地址处的取值发生了变化,也不需要我们再根据飞天螳螂的个体值计算那个32位二进制数了,因为目标已经找到:精灵个体值映射到的变量地址是02024534。

查找金手指例4——背包中的道具在哪里?
上一期的专栏中,作者给出了一个队伍首位精灵携带道具映射到的变量地址:0202450e。在这个例子中,我们要学会举一反三,利用映射表的通用性,找到和道具相关的另一个实体——背包中的道具——的变量地址。
映射表的通用性,指的是同一类游戏实体的变量取值的映射表,在不同的环境下保持不变。举例来说,队伍首位精灵的种族、野生战斗遇到的精灵种族、队伍第二位精灵的种族,用的是同一张精灵种族的映射表;精灵携带的道具、背包中存放的道具、电脑中存放的道具,用的是同一张道具的映射表;玩家的名称、精灵的名称,用的是同一张汉字的映射表……
在这里,“队伍首位”“野生战斗”“背包中的”“电脑中的”“玩家的”“精灵的”描述的都是实体所在的环境,真正的实体是“精灵类型”“道具”和“汉字”。由于环境的不同,这些实体的变量地址也会不同,但它们对应的映射表是同一张。例如02b0在精灵种族的映射表中代表三首恶龙,那么02b0无论是在首位精灵的精灵种族,还是野生战斗遇到的精灵种族,还是队伍第二位精灵的种族,代表的都是三首恶龙。
这就给我们提供了“举一反三”的机会。在上一期的“自己动手来尝试”环节中,如果有尝试过的读者,可能手里已经有一些道具映射到变量取值的记录了。我们可以利用几个道具映射到的变量取值,来找到“背包中的道具”的变量地址。
下图为作者的一个存档,打开背包,找到道具,一直拉到最上面:

目前显示,背包中携带的第一个道具是心之鳞片。通过上期的“自己动手来尝试”,找到了心之鳞片映射到的变量取值是006f,我们打开“查找金手指”,“数据尺寸”选“8位”,“有/无正负之分”选择“十六进制”,在下方输入框内输入006f,点击“开始按钮”,再点击“查找”按钮(这一系列操作,经过了之前的例子,读者们应该很熟悉了)。

看起来结果很多啊。没关系,我们还可以用“追踪式变量取值查找”。回到游戏,用Select键把“红色碎片”这个道具移到第一个道具的位置。如果不知道自己VBA模拟器里面Select键是哪个,可以到“选项——手柄——设置——1...”里面去设置,在“选择”那一栏对应的按键就是Select键,作者用的是键盘上的退格键(Backspace),如下图:

在背包中,光标移到“红色碎片”,然后按Select键,左边会提示“移动红色碎片去哪”。

然后再按一次“上”键,再按一次Select键退出选择,就把红色碎片移到第一个位置去了:

红色碎片映射到的变量取值是0030,我们回到“查找金手指”界面,输入0030,再次点击查找,这次只剩下一个结果了:

因此,背包中第一个道具映射到的变量地址就是0203d030。
先别着急庆祝,做到真正的“举一反三”还差一步。对第二期专栏还有印象的读者,应该没有忘记“变量地址查看器”这个东西,它还是我们验证找到的变量地址是否正确的两个验证手段之一(另一个是看“查找金手指”里的“旧值”和“新值”)。现在我们把“变量地址查看器”打开(工具——反汇编),输入0203d030,然后点击“转到”按钮:

变量地址0203d030处,变量取值确实是0030,对应着红色碎片。没错,看来我们找到的变量地址是正确的。我们继续向下看,地址0203d032处,取值是0009,也就是十进制的9,这个9是什么含义呢?
现在可以到上面再看一眼主角的背包,红色碎片的数量恰好是9,这会不会是巧合呢?我们继续向下看。
下一个地址,0203d034处,取值是006f,这个数字刚才出现过,是心之鳞片映射到的变量取值,而心之鳞片恰好是背包里的第二个道具。
再下一个地址,0204d036处,取值是0022,这是十进制的34,背包里心之鳞片的数量恰好是34。再往下可以一一验证,依次是第三个道具的变量取值、第三个道具的数量、第四个道具的变量取值、第四个道具的数量……
这个事实可以给我们什么启发呢?
第一个启发是:为什么背包里面,道具的变量取值和数量在地址上是紧挨着放的?这其实是为了编写程序的时候便于管理,把相关的、相似的变量放在一起,找起来的时候会更方便。因此在查找其他变量地址的过程中,我们也可以顺着地址向上或向下看看,会不会有新发现。
第二个启发是:第三期专栏的“自己动手来尝试”中,为了获得每个道具映射到的变量取值,我们需要不断地在游戏中替换首位精灵的携带物品,测试一百个道具,这个操作就需要重复一百次,十分繁琐。而在这里,我们只需要利用“变量地址查看器”,就可以一次性获取背包中所有道具映射到的变量取值。这在操作上是不是大大简化了?
希望读者们能将这种“举一反三”应用到其他地方去,让做事的时候事半功倍。

查找金手指的方法总结
第三期和本期专栏举了很多“查找金手指”这一功能的例子,可以做个总结了。
查找“修改变量取值”这一类的金手指,其本质是找到游戏内实体到数字的映射表,具体来说是找到从游戏实体到变量地址和变量取值的映射表。“查找金手指”这个功能,对不同的游戏实体操作起来,方法大同小异。
第一类:变量取值是纯粹的数字。第三期专栏的例1——查找金币数,和例2——查找队伍首位精灵等级,就都属于这一类。除此之外、精灵刚被捕捉时的等级、玩家训练师卡片上的ID、精灵面板上的数值(如当前HP、最大HP、特攻特防值等等)、对战开拓区的对战点数等,也都属于这一类型,它们的特点是不需要变量取值的映射表,因为游戏实体本身就是数字。
第二类:变量取值对应着不是直接由数字表示的游戏实体。第三期专栏给出的查找精灵类型的例子,和本期的例4——查找背包中的道具,就属于这一类。除此之外,精灵携带的道具、精灵被捕捉的地点、玩家名称/精灵名称中出现的汉字/数字/英文字符/特殊字符、精灵被捕捉用的精灵球、精灵性格、精灵技能等,也都属于这一类型。它们的特点是每一类实体都会有一张映射表,但是同类的映射表是通用的。
第三类:变量取值是由联系紧密的一系列数字在二进制下拼装成的。本期给出的精灵个体值的例子,就属于这一类。除此之外,精灵的努力值、精灵缎带数、对战开拓区的印记情况等,也都属于这一类型。它们的特点是变量取值经过了程序员的设计,通常是在二进制位上把表示每个实体的数值拼接在一起,而不是每个实体的数值用一个单独的变量来表示。
用一个流程图的方式来描述“查找金手指”的功能如何使用:

流程图中,红色的线代表变量地址的输入或输出,蓝色的线代表变量取值的输入或输出。这是一个循环的过程,起点可以是“本身就是数字”的游戏实体,也可以是“网络上金手指代码的搜索结果”。在经过验证之后,新得到的变量地址或者变量取值可能有助于发现新的映射表。

自己动手来尝试
仿照“精灵个体值”的查找方式,尝试查找“队伍首位精灵努力值”映射到的变量地址,并明确6项努力值组成变量取值的组织方式。可以参考的资料在专栏的例子中都出现过,比如神奇宝贝百科、网上的金手指代码等等。

用了两期专栏,算是把“变量与实体的映射表”这个概念在结合例子的情况下讲了个大概。在手里有了一份基本完整的映射表之后,使用金手指也就是水到渠成的事了。不过金手指真的管用吗?回顾第一期专栏结尾处提出的三个问题,我们还有最后一个问题没有回答——修改变量的副作用,留到下期专栏介绍。
还能坚持读到这里的读者们,谢谢大家的支持!