五.文本导出和导入B
程序员要有一种体谅翻译人员的心情,一个好的编辑器肯定是必要的。Agemo的主页上有一个AgemoEditor就是一个还不错的软件。当然我觉得还是差一点,比如没有专有名词统一的工具,没有版本控制,不支持小组合作。
蓝山魔导导出的文本,由于没有找到文本指针,文字零落,分段也不明确,而且只能用一般的文本编辑器进行编辑。感觉还是很差的。当然转成AgemoEditor专用格式的话,也不是很困难,可问题是文本指针的问题。Agemo在使用说明中说了一句话:
支持的文本格式2 - 地址,长度,文字
本人不用这种格式的,如果你找到了指针表, |
E9DA,30,底いため、通常攻擊には弱い。{结束符FFFF} |
就是粗体的那句,是的,如果作为程序员无法找出指针,将一堆垃圾扔给翻译,这种事情我是做不出来的。最后还是只能进行更为复杂的分析。
至此,需要以下几个工具:
1) Agemo的ps_debugger,到Agemo的主页去下载。看名字就知道,用来调程序的。
2) IDA,反汇编的工具,用来静态分析的。到看雪(pediy)去下载。
Ps_debugger,如何设置不再说明,可以参考附带说明文档。该程序不能直接加载镜像,所以要先用虚拟光驱加载,然后再“RUN CD”。游戏进入第一段文本画面后,点击“Pause CPU”按钮暂停游戏。点击“Dump”按钮将内存Dump下来,Dump下来的内容保存在dump目录下,其中vram.bin是显存,这个目录下还有另外一个软件vram.exe,就是Agemo主页上提到的ps显存查看器,这里没有使用说明,但是主页单独下载的压缩包里面有。
打开后就可以看到这个画面:
这个软件的作用我现在还不是很了解,所以不再多介绍。
ram.bin是内存,才是重要的部分,用winhex之类的软件打开,可以发现从00100000开始,其内容就是Course.dat从004D4000开始的内容。显然,程序将此段内容从光盘读取到内存,然后读取内存,将文字显示出来。
接下来设置断点,选中Break下的MemRead,自动填充为80000000-80000000,前面为什么要带80请参考Agemo首页的PS资料。将这个始末地址修改成80100000-8010C000,但要注意修改后并不会立刻生效,要先取消MemRead,在选中MemRead,下方会提示“wait for pause on read mem from 80100000 to 8010C000”,此时说明已经生效。
点击“Resume CPU”继续,程序提示“CPU Break, mem read at 00101676, 1 bytes”
00101676在Course.dat中就是004d5676,对应的是“雨粒”的“粒”,也就是将要显示的字。不去管它,继续点“Resume CPU”
CPU Break, mem read at 00101677, 1 bytes
CPU Break, mem read at 00101678, 1 bytes
CPU Break, mem read at 00101679, 1 bytes
CPU Break, mem read at 0010167A, 1 bytes
CPU Break, mem read at 00100048, 2 bytes
CPU Break, mem read at 0010167B, 1 bytes
注意,顺序读取到167A后,也就是00,突然跳转到了0048,然后又跳转到了167B。这说明了几点:
1) 指针表中并没有指明文本的长度,文本还是靠00来表明句子结束的。
2) 48很可能就是指向167B指针。
48的数据是00 2B,注意程序读取了2 bytes。这一段数据部分的长度是164A,而164A + 6 + 2B = 167B,就是指向167B的指针。不停的“Resume”,可以看到:
CPU Break, mem read at 0010004A, 2 bytes (新的一句话)
CPU Break, mem read at 00101694, 1 bytes
CPU Break, mem read at 00101695, 1 bytes
CPU Break, mem read at 00101696, 1 bytes
CPU Break, mem read at 00101697, 1 bytes
CPU Break, mem read at 00101698, 1 bytes
CPU Break, mem read at 00101699, 1 bytes
CPU Break, mem read at 0010169A, 1 bytes
CPU Break, mem read at 0010169B, 1 bytes
CPU Break, mem read at 0010169C, 1 bytes
CPU Break, mem read at 0010169D, 1 bytes
CPU Break, mem read at 0010169E, 1 bytes
CPU Break, mem read at 0010169F, 1 bytes
CPU Break, mem read at 001016A0, 1 bytes
CPU Break, mem read at 001016A1, 1 bytes
CPU Break, mem read at 001016A2, 1 bytes
CPU Break, mem read at 001016A3, 1 bytes
CPU Break, mem read at 001016A4, 1 bytes
CPU Break, mem read at 001016A5, 1 bytes
CPU Break, mem read at 001016A6, 1 bytes
CPU Break, mem read at 001016A7, 1 bytes
CPU Break, mem read at 001016A8, 1 bytes
CPU Break, mem read at 001016A9, 1 bytes
CPU Break, mem read at 001016AA, 1 bytes
CPU Break, mem read at 0010004C, 2 bytes
CPU Break, mem read at 0010004C, 2 bytes
CPU Break, mem read at 0010004E, 2 bytes(到这里结束了)
出现了分页结束的符号,现在程序等待按键输入。按键后,又立刻断下,继续点“resume”:
CPU Break, mem read at 00100050, 2 bytes
CPU Break, mem read at 00100050, 2 bytes
CPU Break, mem read at 00100052, 2 bytes
CPU Break, mem read at 00100054, 2 bytes
CPU Break, mem read at 00100056, 2 bytes
CPU Break, mem read at 00100058, 2 bytes
CPU Break, mem read at 00100058, 2 bytes
CPU Break, mem read at 0010005A, 2 bytes
CPU Break, mem read at 0010005C, 2 bytes
CPU Break, mem read at 0010005E, 2 bytes
CPU Break, mem read at 001016AB, 1 bytes
CPU Break, mem read at 001016AC, 1 bytes
CPU Break, mem read at 001016AD, 1 bytes
一路从50读取到了5e,然后跳转到16AB,初步分析认为4F 20跳过12个字节,之后每2个字节就是一个指针,指向一句文本,一直到47 00 00 00为止,47 00 00 00是分页标记。
这些分析还是比较简单,只要有一些耐心,还能看出来32 30 00 00后面跟随的指针是指向“XXXXXX.MOV”之类的字符串的。但总的来说还是比较粗,下面考虑用IDA进行静态分析。
Ps_dugger虽然不像ollydbg或者SoftICE之类的软件那么功能众多,如何利用就要自己的想象力了。
Ps_debugger有一个asm log功能,这个功能有一个问题,就是不能进入死循环,否则记录下来的文件会大的惊人。一般来说超过1M,得到的记录文件基本就无用了。死循环一般来说到了等待输入的时候就会产生,如果显示文字的时候可以按键快进或者有时间控制,一般都会进入死循环。记录下来的文件会提示用到的寄存器的值,所以看起来还是挺方便的,对照asm log和ida,可以当做程序走了一遍。还有一个问题记录了所有的指令,包括一些小函数,每调用一次都会全部记录下来,不能像调试器那样步过。
我这次下的断点是这样的,重新启动游戏,对内存地址80100000-8010C000的memread下断,第一次断在80100002,点击asm log,开始记录,不停地点resume,到读取到80100014停止。因为再点的话就开始播放视频进入死循环了。得到的记录在程序目录下的asm.log中,先复制一份备份,这个文件在程序重启时会清空的。
内容大致如下:
80116e18 : LHU 801eada5 (a1), 0002 (80100000 (v1)) [80100002]
80116e1c : LHU 80100000 (v1), 0004 (80100000 (v1)) [80100004]
80116e20 : ADDU 80100006 (v0), 80100006 (v0), 0000164a (a0),
80116e24 : LH 0000164a (a0), 0538 (801a6e2c (gp)) [801a7364]
80116e28 : SH 00000000 (r0), 003c (801a6e2c (gp)) [801a6e68]
80116e2c : SW 80101650 (v0), 0038 (801a6e2c (gp)) [801a6e64]
。。。。。。
8011687c : SLL 00002000 (v0), 00000003 (a1), 01 (1),
80116880 : ADDU 00000006 (v0), 00000006 (v0), 80100010 (v1),
80116884 : LHU 80100016 (v0), fffe (80100016 (v0)) [80100014]
打开ida,新建一个Consoles → .psx Sony PlayStation Excutable项目,载入Slps_027.49,经过一段时间分析。按G(go to),输入第一行指令的地址80116e18,可以看到如下代码:
(函数名和注释是我加的,加注释点;即可)
loc_80116E04: # CODE XREF: ReadCourseHeader:loc_80116DE0j
lw $v1, 0x24($gp) # 文本内存地址->v1
# 这里是80100000
nop
addiu $v0, $v1, 6 # 数据开始部分->v0
sw $v0, 0x2C($gp) # 保存到gp+2c
lhu $a0, 0($v1) # 代码段长度->a0
lhu $a1, 2($v1) # 文本段长度->a1
lhu $v1, 4($v1) # 0x02->v1
addu $v0, $a0 # v0+a0->v0,文本部分指针
lh $a0, 0x538($gp)
sh $0, 0x3C($gp)
sw $v0, 0x38($gp) # 保存到gp+38
addu $v0, $a1 # v0+a1->v0,文本结束指针
sw $v0, 0x40($gp) # 保存到gp+40
addu $v0, $v1 # v0+2->v0,数据真正结束
sw $v0, 0x44($gp) # 保存到gp+44
bnez $a0, loc_80116E4C
lui $v0, 0x801A # 0x801a0000->v0
sh $0, 0x30($gp) # 0->gp+30
红色部分的指令就是80116e18的指令。
lhu $a1, 2($v1) # 文本段长度->a1
80116e18 : LHU 801eada5 (a1), 0002 (80100000 (v1))