五.文本导出和导入B

 

程序员要有一种体谅翻译人员的心情,一个好的编辑器肯定是必要的。Agemo的主页上有一个AgemoEditor就是一个还不错的软件。当然我觉得还是差一点,比如没有专有名词统一的工具,没有版本控制,不支持小组合作。

 

蓝山魔导导出的文本,由于没有找到文本指针,文字零落,分段也不明确,而且只能用一般的文本编辑器进行编辑。感觉还是很差的。当然转成AgemoEditor专用格式的话,也不是很困难,可问题是文本指针的问题。Agemo在使用说明中说了一句话:

支持的文本格式2 - 地址,长度,文字


格式见右侧

本人不用这种格式的,如果你找到了指针表,
可以方便修改每一句的长度,建议导出我那种格式。

E9DA,30,底いため、通常攻擊には弱い。{结束符FFFF}
E57A,14,
スキル:なし{结束符FFFF}
F766,46,
この世に怨念を持ったまま死だ者の灵で、
F766,13,
生前の{结束符FFFF}

 

就是粗体的那句,是的,如果作为程序员无法找出指针,将一堆垃圾扔给翻译,这种事情我是做不出来的。最后还是只能进行更为复杂的分析。

 

至此,需要以下几个工具:

1)  Agemops_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.dat004D4000开始的内容。显然,程序将此段内容从光盘读取到内存,然后读取内存,将文字显示出来。

 

接下来设置断点,选中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

 

00101676Course.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 logida,可以当做程序走了一遍。还有一个问题记录了所有的指令,包括一些小函数,每调用一次都会全部记录下来,不能像调试器那样步过。

 

我这次下的断点是这样的,重新启动游戏,对内存地址80100000-8010C000memread下断,第一次断在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,经过一段时间分析。按Ggo 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 log801eada5a1的值,2($v1)是寄存器相对寻址,pc中的写法是[v1+2]80100000v1的值,实际取值的地址是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指令比较v0v1,然后立刻对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参数,选择项的文本

004005

1参数,对应一个表达式指针

032

3参数,第一个参数指向str文件,播放视频

03c

1参数,指向图片文件,可能是加载图片

04F

2参数,文本显示坐标

047

无参数,等待按键

001

1参数,跳转到其他指令,注意可能形成循环

002

1参数,跳转到其他指令,表示选择题

003007

无参数,选择肢结束

00E

2参数,第一个参数指向选择肢

 

 

(完)

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐