(PS)賭博黙示録カイジ漢化筆記(五)(完)

五.文本導出和導入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參數,第一個參數指向選擇肢

 

 

(完)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章