五.文本导出和导入B
程序员要有一种体谅翻译人员的心情,一个好的编辑器肯定是必要的。Agemo的主页上有一个AgemoEditor就是一个还不错的软件。当然我觉得还是差一点,比如没有专有名词统一的工具,没有版本控制,不支持小组合作。
蓝山魔导导出的文本,由于没有找到文本指针,文字零落,分段也不明确,而且只能用一般的文本编辑器进行编辑。感觉还是很差的。当然转成AgemoEditor专用格式的话,也不是很困难,可问题是文本指针的问题。Agemo在使用说明中说了一句话:
支持的文本格式2 – 地址,长度,文字
本人不用这种格式的,如果你找到了指针表, |
E9DA,30,底いため、通常攻擊には弱い。{ |
就是粗体的那句,是的,如果作为程序员无法找出指针,将一堆垃圾扔给翻译,这种事情我是做不出来的。最后还是只能进行更为复杂的分析。
至此,需要以下几个工具:
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)) [80100002]
可以看到$a1是寄存器寻址,asm log中801eada5是a1的值,2($v1)是寄存器相对寻址,pc中的写法是[v1+2],80100000是v1的值,实际取值的地址是80100000+2=80100002,也就是方括号中的值。这种东西多比较比较就明白了。不再说明。
现在先回到上面的第一行代码:
lw $v1, 0x24($gp) # 文本内存地址->v1
# 这里是80100000
gp这个寄存器的说明是全局指针(Global Pointer),我看下来似乎是指向一个全局变量表,每一个gp+n都是一个全局变量。这里的0x24($gp),(以后都记作gp+24,不再说明)保存的是对话脚本的起始地址。
gp+24 |
数据起始地址,80100000 |
gp+2c |
指令部分开始地址,80100006 |
gp+30 |
当前读取位置 |
gp+38 |
文本开始位置 |
gp+40 |
终结符开始位置 |
gp+44 |
数据结束 |
这段代码显然只是读取头部数据,所以取名叫做ReadCourseHeader,下面看看读取数据的部分。
找到jr ra指令,ra是返回地址,ps没有把返回地址放在堆栈中,而是特别使用了一个寄存器。
80116e8c : SH 00000000 (r0), 70c4 (801a0000 (at)) [801a70c4]
80116e90 : JR 801162c8 (ra),
80116e94 : ADDIU 801fff60 (sp), 801fff60 (sp), 0018 (24),
801162c8 : LUI 801a75c0 (v0), 8017 (32791),
注意蓝色部分,agemo的文章中多次提到过了,跳转语句有一个指令的延迟,所以先执行蓝色部分的代码(应该是在平衡堆栈),再返回到上级函数。
按G,来到801162c8,这个函数比较复杂,但是根据asm log一路走下去,不要管其他分支,其中有一个函数的调用:
8011641c : JAL 801166ec, 801162c8 (ra),
80116420 : SH 0000014d (v0), 0488 (801a75c0 (s0)) [801a7a48]
这个函数应该和ra没有关系,ida的指令和asm log中不同,可能该指令隐藏着修改ra寄存器。这样跳转到了801166ec。
HandlerCmd:
var_8= -8
lhu $a0, 0x30($gp) # 当前读取位置->a0
lw $a2, 0x2C($gp) # 数据开始指针->a2
addiu $sp, -0x18
sw $ra, 0x18+var_8($sp)
addu $a2, $a0 # a2 + a0(gp+30) -> a2
addiu $v1, $a2, 4 # a2 + 4 -> v1
sh $a0, 0x34($gp) # a0 -> gp+34
addiu $a0, 4 # a0 + 4 -> a0
sw $v1, 0x1C($gp) # v1 -> gp+1c
lui $v1, 0x8017
lhu $v0, 0($a2) # 读数据
lhu $a1, 0($a2) # 再读
andi $v0, 0xFFF # 低12位
srl $a1, 12 # 高4位
sh $v0, 0x18($gp)# 保存低12位
sll $v0, $a1, 1 # 高4位*2
addu $a0, $v0 # a0 + v0 -> a0
lhu $v0, 0x18($gp) # 读回低12位
li $v1, 0x8016F1D0 # <suspicious> # v1赋值
sh $a0, 0x30($gp) # a0 -> gp+30
lhu $a0, 2($a2)
sll $v0, 2 # 低12位左移2位
addu $v0, $v1 # 加上v1
lw $v0, 0($v0) # 读取v0
nop
jalr $v0 # 跳转
nop
sll $v0, 16
lw $ra, 0x18+var_8($sp)
sra $v0, 16
jr $ra
addiu $sp, 0x18
# End of function HandlerCmd
这个函数值得研究一番,主要功能是读取2bytes数据,如
01 00 4F 20 00 00 C0 00 18 00 4A 70 00 00 00 00
0C 00 17 00 17 00 18 00 2B 00 44 00 47 00 00 00
如4F 20这段数据,实际是204F,它的高4位,也就是2,代表后面有2个参数,因为每一个参数是2bytes,所以读取位置增加了参数*2;其他12位是一个函数表的入口,该函数表指针是0x8016F1D0,每一个函数地址是4bytes,所以这个数乘4后加到该指针上。最后跳转到这个函数上。
Ida中静态分析的话,下面就不知道往哪里去了,但是看asm log可以找到这句:
80116748 : LW 8016f248 (v0), 0000 (8016f248 (v0)) [8016f248]
8011674c : NOP
80116750 : JALR 8012d884 (v0), 80116424 (ra),
80116754 : NOP
8012d884 : ADDIU 801fff60 (sp), 801fff60 (sp), ffe8 (65512),
8012d888 : SW 801a75c0 (s0), 0010 (801fff48 (sp)) [801fff58]
它跳转到了8012d884,到ida中继续分析,这个函数对应的指令是1E,由于还不清楚到底是干什么的,只能命名为Func01E。
下面部分代码:
jal ReadParamA1ToV0
li $a1, 1 # 先$a1赋值,再跳转到函数ReadParamA1ToV0
move $a0, $v0 # 指令后一个数据->v0->a0
sll $v0, $a0, 16
sra $v1, $v0, 16 # 只要低16位
li $v0, 0x64
bne $v1, $v0, loc_8012D8D8
li $v0, 0x65
sb $s0, byte_801A71BF # case:0x64
j loc_8012D990
move $v0, $0
—————————————————————–
loc_8012D8D8: # CODE XREF: Func001E+3Cj
bne $v1, $v0, loc_8012D8F0
li $v0, 0x50
sb $0, byte_801A71BF # case:0x65
j loc_8012D990
move $v0, $0
ReadParamA1ToV0这个函数名是我取的,含义是这个函数读取指令后的参数部分,参数放到a1中,返回值放到v0中。这段代码主要要注意的部分是switch的结构,bne指令比较v0和v1,然后立刻对v0重新赋值,再跳转或者继续执行下去。(注:一般来说参数按照a0,a1的顺序放置,返回值总是放在v0,当时不知道这个)
再往下分析代码也没有分析出什么重要的东西,所以不再说明了。主要这里了解了脚本部分的结构,现在看
01 00 4F 20 00 00 C0 00 18 00 4A 70 00 00 00 00
0C 00 17 00 17 00 18 00 2B 00 44 00 47 00 00 00
就可以明白204F有两个参数,调用04F这个函数(实际功能是对话的定位,两个参数是坐标),704A是一个变参数的函数,04A这个函数的功能是显示对话,对话从第二个参数开始,第一个参数含义不明。0047是一个没有参数的函数,功能是显示换页标记,接受按键信息换页。
目前了解的函数有:
04A |
不定参数,第一个不明,总是0(?),其他都是文本指针 |
04C |
2参数,选择项的文本 |
004,005 |
1参数,对应一个表达式指针 |
032 |
3参数,第一个参数指向str文件,播放视频 |
03c |
1参数,指向图片文件,可能是加载图片 |
04F |
2参数,文本显示坐标 |
047 |
无参数,等待按键 |
001 |
1参数,跳转到其他指令,注意可能形成循环 |
002 |
1参数,跳转到其他指令,表示选择题 |
003,007 |
无参数,选择肢结束 |
00E |
2参数,第一个参数指向选择肢 |
(完)
今天的文章(PS)賭博黙示録カイジ汉化笔记(五)(完)分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/28628.html