最近有朋友分享了安卓上的playstation模拟器,我又开始玩起ps上的怪物农场2.这是一个怪物养成类游戏,玩家要养育,训练怪物,并参加比赛.
游戏制作公司是Tecmo,游戏中怪物的模型贴图等非常优秀,合体系统也是我见过最好的(从某种角度上看,完爆真女神转生).
该游戏有个特色系统是,可以通过ps的换盘机制,从光盘生成不同的怪物,甚至生成稀有的怪物.因此玩家需要收集各种各样的光盘(模拟器可以使用光盘镜像)来生成怪物.
虽然我10年前就开始玩这个游戏,断断续续玩了很久,还是有很多稀有的怪物收集不到.因此早就想通过破解来收集所有的怪物.
那么就开始填这个10年前的坑吧.
这里记录下修改的过程.
首先在网上瞎找,找到no$psx模拟器\调试器,试跑了下,感觉不太好用.
之后换了PSX,感觉似乎好些,运行和调试都没问题,但是完全不知道要怎么入手.
汇编是MIPS的,还不熟悉,内存也没法搜索.试着随意调试了下,暂时不知道要如何找到生成怪物的那个函数.
于是继续在网上寻找资料,找到了slowbeef汉化Policenauts的日志.
slowbeef做的第一步是找到游戏中的文本,修改文本,并在游戏界面中可以观察到变化.文本通常是破解的切入点之一.
所以这也是我要做的第一步,找到游戏中的某个文本,修改它.
游戏开始时,会让玩家输入自己的名字,就决定先在内存中找到自己的名字好了.
只能输8位,所以我只能输入unigauld啦.
选择确定后,后续的对话中出现了我的名字.
slowbeef在他的文章中介绍了一种方法,叫Statesave hacking,就是利用模拟器的Quick Save(快照)功能来分析内存.
这种方式基本上等同于内存Dump.模拟器在保存快照时,会把当前的内存数据保存到文件.在读取快照时,会从文件恢复内存数据.处理器当前的状态应该也有保存(我的猜测).
我们使用Quick Save 1保存,在模拟器的saves目录下生成quicksave_SLPS_019.06_1.psv这个文件.
使用这种方法的好处是,可以修改快照内存,之后直接使用Quick Load,可以直接使改动的内容生效.
OK,既然"unigauld"这样的字符串被显示在了屏幕上,那在内存中肯定也能找到.
使用二进制编辑器010Editor,搜索"unigauld",结果什么都搜不到.将编码调成日文,搜索"さん",也是一样无果.
试着搜索字符串,查找出一些字符串,但是显示在对话中的字符串根本找不到.
slowbeef的文章中介绍了一种方法,叫relative search,即不直接搜索"unigauld",而是根据相邻字符间的差距来查找.
正常ASCII的a是0x61,b是0x62,c是0x63,但是很可能游戏中对字符串编码做了处理,例如存储时,每个字符都增加0x10,这样abc就变成了0x71,0x72,0x73.直接使用ASCII码查找自然没办法找到.
恩,所以需要写个脚本来做relative search.虽然c++比较熟,但是人生苦短,我还是决定用python.简单撸了个脚本,开始搜索,结果还是搜索不到.
难不成使用了其他方式对字符串进行加密,连字符间的相对顺序都变了?不死心继续搜索,无意中发现了下面这个字符表,和取名字时的那个很像.
这里右边使用的是shift-jis编码.仔细观察发现,u是95 82,n是8E 82.试着使用这里的编码搜索,还是无果.
感觉暂时是没法找到了,肯定是经过什么奇怪的加密处理了.于是洗澡去了,洗完突然灵光一现,想到可以试试用relative search以2个字节为单位搜索.
于是改改脚本,很土但是能用.
def relative_search(filepath,word):
f = open(filepath,"rb")
f.seek(0,2)
size = f.tell()
print(ord('n')-ord('u'))
for i in range(size-20):
f.seek(i,0)
s = f.read(18)
if ord(s[0])-ord(s[2]) == ord('u')-ord('n') \
and ord(s[2])-ord(s[4]) == ord('n')-ord('i') \
and ord(s[4])-ord(s[6]) == ord('i')-ord('g') \
and ord(s[6])-ord(s[8]) == ord('g')-ord('a') \
and ord(s[8])-ord(s[10]) == ord('a')-ord('u') \
and ord(s[10])-ord(s[12]) == ord('u')-ord('l') \
and ord(s[12])-ord(s[14]) == ord('l')-ord('d') :
print(s[0],s[2],s[4],s[8],s[10],s[12],s[14],s[16],i)
if s[0] < s[1]:
print("ok")
else:
print("false")
relative_search("quicksave_SLPS_019.06_1.psv","uni")
input()
果然找到了3处地方.
其中485936(0x76A30)对应了如下数据.
619019(0x9720B)对应了如下数据.
2094970(0x1FF77A)对应了如下数据.
看起来前两个字符串到FF 00就结束了,也就是只有"unigauld".而最后一个字符串比较长,看不出到哪结束,应该是界面上对话使用的字符串.
试着将第一个字节修改成2F,保存文件.
使用模拟器的Quick load 1加载快照文件.发现改动生效了,u变成了v.
接下来试着改改unigauld后面的字符,这里是"さん".
改完还是保存,使用Quick load 1加载快照.发现"さん"变成了”しが”.
到这里,第一步算是完成了.
后续可以做的事情,
尝试找到读取CDROM的相关函数,如果知道读取了什么数据,那就可以伪造这部分数据,进而控制生成的怪物.
尝试找到生成怪物的函数及怪物ID的参数,这样就能控制最后生成的怪物了.
弄清这里的字符串编码规则,可以考虑写脚本来扫出这些字符串.