SFC中文化經驗談(四)—反組譯/概念篇— - 模擬器

Charlie avatar
By Charlie
at 2012-11-24T16:34

Table of Contents



【雜言】

我有點後悔寫這系列文章了....比我預想的還難寫(抱頭

先說明一下,因為我不可能從計算機概論或寫編譯器談起,
所以這系列的經驗談是假定讀者有資工背景或具基本程式能力。
反組譯這邊則最好有修過組合語言或系統程式這類課程,具備基本概念即可。
一般來說,資工大三~大四學生應該就具備充足知識了.....。

之前三篇中提到的工具我並沒有解釋如何使用,因為那是自己花時間摸一下就會用的。
但這篇的反組譯模擬器我會稍微提一下用法,因為這邊有些"經驗上"的用法。


【正文】


~PART I. Rom、Ram、VRam~


跟中文化有關係的SFC記憶體是Rom、Ram跟VRam

Rom應該都知道了,就是包含程式與資料的遊戲"軟體"部分
(實際是存放在卡匣硬體上,這點就跟存放在光碟中的軟體一樣)。

Ram則是用來放置遊戲記錄與暫存資料的地方,
SFC會用7E:0000~7F:FFFF這個特殊SFC位址來代表Ram裡的資料位置。
所以SFC的金手指碼"基本上"都是7E、7F開頭,就是為了處理Ram裡的數值。
我們也隨時可以用snes9x v1.43.ep9r8將遊戲目前Ram的內容dump出來。
http://0rz.tw/cT4l6 (第一個紅框應該在Dump Ram,不是Dump Palette,我畫錯了)

VRam則是存放顯示在螢幕上的內容
根據遊戲設計不同,最多會有4個圖層與一些sprites....。
(如果是用Snes9X的玩家,在遊戲中按數字鍵1~5就能看出圖層來)

VRam在每個圖層上要顯示的東西都會分成兩部分:
(1)8x8為單位的圖塊(tile)
依模式不同每個點有可能是2bits~4bits(4色~16色)顯示。
基本上這些就像一塊塊可重複使用的拼圖。
(2)每個位置要使用的圖塊編號
螢幕上(嚴格講是某圖層或sprites)由左到右、由上到下依序使用的圖塊編號。
相當於是指明螢幕上(嚴格講是某圖層或sprites)每個地方要用什麼圖塊。
(以前做的示意圖 http://0rz.tw/NgBGU )

就結果而言,要看到螢幕上有什麼字就代表VRam裡有該文字的圖塊,
並在要顯示的位置給圖塊編號(不是字庫代碼)。
這些圖塊即便是每個點以2bits(4色)顯示的8x8最小文字圖,
也要用到16 bytes的連續資料來顯示。因此遊戲時常會用DMA的方式,
直接把未壓縮的連續文字圖資料一口氣在Rom、Ram、VRam間搬運,提升處理速度。
(DMA的寫法很重要,以後會用實例解釋,現在先有個印象就好)


以流程來說,要完成文字的顯示工作,我"看過還有印象的"主要有4種方式:

(1)Rom --(解壓、挑字)--> Ram --(整個字串)--> VRam

這可能使用在"使用字庫較大,並顯示名詞文本"的情況下。
因為名詞長度一般了不起8個字,但因為字庫太大,
所以先在Rom裡依使用順序挑出字圖,複製到Ram裡成為字串圖,
再把Ram裡整個字串圖複製到VRam顯示出來
若是有壓縮的字圖就要先解壓,再一個一個byte放進Ram裡,
若是未壓縮的字圖就DMA直接整個字圖傳進Ram裡,效率相對較高。
有時劇情文字也會用這種方式處理。

(2)Rom --(解壓、挑字)--> Ram --(挑字)--> VRam

這可能使用在"使用字庫較大,並顯示劇情文本"的情況下。
在Ram裡排成字串圖會發生的問題就是...有些字會重複被使用。
因為Ram裡還要放一堆其他圖像、數據,所以其實沒太多空間浪費。
如果一段對話很長,這時可以把會用到的字圖挑"一份"到Ram裡,
再根據劇情代碼從這個"Ram裡的小字庫"挑字圖到VRam顯示出來
有時非字庫的文字圖也會用這種方式處理,例如皇騎1的地圖名稱。

(3)Rom --(挑字)--> VRam

這相當於是上面的簡化版,直接從Rom裡依序挑字到VRam。
但可能因為速度慢(從Rom挑越多字越慢)或因為文字圖必須未經壓縮,
感覺不常被使用在劇情文字。可能用在指令名稱等顯示在固定位置的內容。

(4)Rom --(解壓、整個字庫)--> Ram --(挑字)--> VRam

這可能使用在字庫夠小的情況,特別是遊戲只有使用平、片假名,沒幾個漢字時,
可以輕鬆地把整個字庫全塞到Ram裡。之後只要看文本代碼用到哪些字,
再依序拷貝到VRam就行,對遊戲而言可說是最方便的了.....

.....然後做中文化的就淚目了T____T

如果看到某些SFC漢化遊戲改不出"只有平、片假名的小字庫",那可能卡這邊了。
(或是代碼數量不足等其他原因.....也可能只是單純反組譯不熟 XD)
因為這部份我不會提出實例(解釋不完),所以我把我在皇騎1的處理概念解釋一下:

<i>想辦法拆成對應不同條件的小字庫:

例如皇騎1戰鬥時可以切換成有動畫或無動畫,攻擊名稱會因而有所差別。
所以我可以把所有攻擊名稱會用到的中文字,重新分成兩個不同小字庫,
各別對應有動畫跟無動畫兩種情形。
這樣只要在原架構下,寫段函式去判斷這時是有動畫或無動畫,
再依情況載入不同的小字庫就行。

缺點是....拆出來的小字庫都要小於原字庫,且可能發生代碼重複使用的問題,
也就是人名、道具名等需要統一代碼的地方不適合使用
但如果只是指令名或是人名、道具名不多的情況就還蠻好用的。

<ii>簡單說.....就是想辦法硬幹成(1):

例如皇騎1的狀態畫面有可能顯示上千種不同人名、道具名、職業名....
字庫最少要700個中文字,而且也沒額外條件好拆字庫時.....就只好吐血吧。

先在Ram裡找一塊沒被使用的空間,寫個函式把當下用到的中文字圖
依序寫到前述的空間,另外要同時計算目前寫了幾個字圖到Ram裡。
寫完後再把所有的字圖依序複製成為VRam裡特定位置的圖塊,
並將原本顯示日文的位置所對應的編號,固定成特定位置開始的連續編號。

這樣不管字庫多大,理論上都能"動態地"從Rom裡挑字圖到Ram,
再從Ram複製字串到"固定的"VRam位置。
光是要讓皇騎1狀態畫面不受限地顯示中文,我就多加了10幾個函式。
因為這邊其實還同時併發8x8顯示與代碼數量不足這兩個問題。
不然我通常將一個地方改中文化,只需要加1~3個函式,這裡真的改太大....



~Part II. 反組譯log檔~


所謂的反組譯,簡單來說就是把0與1機械碼實際運作的過程(機器看得懂,人看不懂),
改用較高階、精簡的指令集與16進位數字表現出來(也就是組合語言)。
相對於程式碼是給人看、機械碼是給電腦看,組合語言就介於中間。
因為每行都相當於是機器硬體上的一個簡單動作,所以會比高階程式語言瑣碎得多。

我們先來看看怎麼用snes9x v1.43.ep9r8將遊戲中的運作過程dump出來

載入Rom檔後,按下右邊debuger介面中的Run鍵,開始遊戲。
根據需求上的不同,有兩種方式決定何時開始dump:

(1)不知道資料在Rom裡位址的情況
例如,當我想把ロシュフォル教会改成中文,但卻找不到這些字在Rom的哪裡時,
可以在文字出現前的畫面,先去右邊debuger介面勾選Loggin下的cpu欄,
再把滑鼠移到遊戲視窗,立即按鍵顯示出目標"ロシュフォル教会"。
再馬上取消掉debuger介面中cpu欄的勾選,就會dump出勾選其間的組語代碼,
(上述動作越快越好,因為一秒就會dump出幾十MB的組語代碼.....)
並存在與Rom同一資料夾下的"Rom檔名0000.log"。
http://0rz.tw/hgpPL

在這種情況下要知道確切的相關程式開始位置比較麻煩,
如果知道現在顯示文字的代碼,可以用這代碼在log檔中搜尋。
如果不知道可以搜尋何時有DMA的動作,或是觀察VRam中的變化,
再反推相關程式的開始位置(以後會給實例說明)。

(2)知道資料在Rom裡位址的情況:
按下介面中的Breakpoints鍵,貼上位址並勾選3個中斷時機,
當遊戲對該位址資料進行讀寫時就會自動中斷停止,
這時按下Step Over鍵就會開始一行一行執行指令。
http://0rz.tw/yLJAr

配合(1)用UltraEdit搜尋這幾行指令在log檔的位置,
就知道相關程式從何處開始,不用在幾十MB的組語代碼中大海撈針。


這時我們終於得到log檔,甚至知道程式大概從哪裡開始。
把(1)中例子的log檔打開來看,即使在我緩慢的準系統上只跑了一秒,
就產生了20MB的龐大log資料,每個欄位代表的意義如下:
(省略後面不重要的部分,"無視"那欄的暫存器我沒遇過要改的情況,故...無視)

SFC位址 機器碼 組語代碼 A暫存 X暫存 Y暫存 無視 bank
======== ======== ===================== ====== ====== ====== ====== =====
$00/BC7F E8 INX A:FF01 X:001C Y:5555 D:0000 DB:00
$00/BC80 E8 INX A:FF01 X:001D Y:5555 D:0000 DB:00
$00/BC81 E0 28 00 CPX #$0028 A:FF01 X:001E Y:5555 D:0000 DB:00
$00/BC84 90 E2 BCC $E2 [$BC68] A:FF01 X:001E Y:5555 D:0000 DB:00
$00/BC68 DA PHX A:FF01 X:001E Y:5555 D:0000 DB:00
$00/BC69 20 8C BC JSR $BC8C [$00:BC8C] A:FF01 X:001E Y:5555 D:0000 DB:00

$00/BC8C 86 1C STX $1C [$00:001C] A:FF01 X:001E Y:5555 D:0000 DB:00
$00/BC8E A9 01 LDA #$01 A:FF01 X:001E Y:5555 D:0000 DB:00
$00/BC90 8D 5E 16 STA $165E [$00:165E] A:FF01 X:001E Y:5555 D:0000 DB:00
.....(略)


SFC位址: 目前的指令位址$oo/xxxx,oo是第oo個bank,xxxx是該bank裡的位址

機器碼: 實際執行的二進位指令碼,只是在此以16進位表示比較容易看。
一個基本的SFC指令會包含1~4個bytes,第1個byte一定是指令本身的代號。
第2~4個byte(如果有的話)都是資料內容。

組語代碼:即使機器碼用16進位表示也只是"變短",很難記住這機器碼對應什麼指令
所以需要用組合語言的方式來簡單表示指令與資料內容
我隨便拿上面例子來解釋,例如....

‧INX 表示X暫存器的值加1。

‧CPX #$0028 表示把X暫存器的值跟數字0x0028做比較
(#$表示資料就是"數字",只有$的話表示資料是"位址內的值")。

‧BCC $E2 表示前面比較結果是"小於"的話,移動0xe2的距離。
因為讀了2個bytes,所以現在程式指標位址是0x00bc84 + 2 = 0x00bc86。
移動距離是0xe2-0x100(0xe2是負數)=向前移0x1e,
也就是移到0x00bc86-0x1e=0x00bc68的位址。
如果前面比較結果是"大於等於"的話,就略過繼續處理0x00bc86的指令。
[$BC68]是反組譯器提供的額外資訊,表示這邊程式指標跳到0x00bc68。

‧PHX 表示把X值放到堆疊(stack)中。

‧JSR $BC8C 表示跳到同一個bank中,位址0xbc8c的位址。
後面[ ]的內容一樣是反組譯器提供的額外資訊....

‧STX $1C 表示把X暫存器的內容存到同bank中,位址0x001c的位址。

‧LDA #$01 表示把數值0x01載入A暫存器中。

‧STA $165E 表示把A暫存器中的內容存到同bank中,位址0x165e的位址。

熟悉的話,只要看到組語代碼就能知道每行在幹什麼。
不熟悉的話可以參考我第一篇提到的網頁,裡面有所有指令代碼的解釋。

A暫存: SFC主要有3個2 bytes暫存器:A、X跟Y。
A暫存器是功能較強的,可以做加減、AND、OR、shift、比大小等工作。
大部分數學運算會在A暫存器裡完成

X、Y暫存:這兩個暫存器比較算輔助性質,只能做"加1"等少數極基本功能。
經常用來輔助A暫存器,完成一些較複雜的計算或資料搬運功能

bank值: 之前有說過程式執行時不太會切換bank,如此可以提升程式執行效率。
JSR $BC8C 這例子可以看出來,即使實際要跳過去的位址是0x00bc8c,
最前面00這個bank值卻被省略了,連帶也有效節省了指令長度 。


===========================================================================
....先寫到這邊吧,下週再給實際的中文化例子。
看看如何解讀log檔,並且加上新的函式來突破原本程式的限制。

中間有些概念如果有錯的話,也隨時歡迎指正。

--

All Comments

Hardy avatar
By Hardy
at 2012-11-28T13:52
先謝再推
Tristan Cohan avatar
By Tristan Cohan
at 2012-12-02T05:33
後面關於反組譯的說明 真是受教了
Queena avatar
By Queena
at 2012-12-03T06:56
喔喔,越來越艱深專業了,感謝分享!!
Agnes avatar
By Agnes
at 2012-12-04T22:40
太神了 感謝留下記錄給後人參考
Bethany avatar
By Bethany
at 2012-12-06T06:40
太神了,看不懂還是要推…身為OB的愛好者非常感謝您
James avatar
By James
at 2012-12-06T22:32
專業文推~
Ula avatar
By Ula
at 2012-12-11T05:41
推專業
Franklin avatar
By Franklin
at 2012-12-15T06:40
推~
Charlie avatar
By Charlie
at 2012-12-17T23:13
不推不行
Selena avatar
By Selena
at 2012-12-21T12:39
神文推!
Michael avatar
By Michael
at 2012-12-21T20:19
大推~
David avatar
By David
at 2012-12-23T05:00
專業好文推,長知識!

復活邪神3繁體漢化版 incubus大簡易版

Ivy avatar
By Ivy
at 2012-11-24T08:50
首先要感謝Rsaga3漢化組 以及incubus大熱心提供的簡易版 謝謝你們 讓小弟再一次圓夢 之前玩的時候 主角都選卡塔麗娜 具有腕力高 速度快的優勢 但是卡在天生的事件限制 莫妮卡公主要過完一推事件才能收到 後來索性(所幸?)重玩 直接選了莫妮卡 才發現公主才是我的最愛 (卡塔麗娜對不起 andgt ...

問一款老舊的FC遊戲

Mason avatar
By Mason
at 2012-11-23T23:59
小時候曾經有一片遊戲 玩了許久一直都破不了 而且不知道名稱 現在有模擬器 想說破了它完成以前破不了的遺憾 記得遊戲內容大至上是這樣 主角一個人(好像不能2P) 攻擊敵人好像是射小刀 遊戲方式有點像聖劍傳說系列 主角我記得是走一個很大的迷宮 怪物我記得有球和蝙蝠之類的 好像是ARPG??不是那種會進入戰鬥畫面 ...

一款電腦遊戲

Zanna avatar
By Zanna
at 2012-11-23T21:13
因為這是小時候看表哥玩的 這款跟炎龍騎士團玩法一樣是戰略也要練等,只是有個地方讓我印象深刻........ 那就是升級的時候,各項數值是1~9點隨機亂跳的,玩家只能看運氣看按到多少........ 運氣好的話各項都可以按到接近9點,但是不好的話就是各項都是接近1點...... 想請問各位大大這遊戲的名 ...

益智方塊類遊戲

Ivy avatar
By Ivy
at 2012-11-23T20:49
俄羅斯方塊除了1988年版本外,另外推薦1990年的BLOXEED。 這個遊戲是本家系列作的第三部作品,不但改良了操作性、加入對戰模式、提升了難 度,還增加了有趣的攻擊道具系統。玩家可以利用這些附著在方塊裡的道具來快速減 少未清除的方塊,甚至能同時得到龐大的獎勵分數,成就感十足。 遊戲難度其實還滿高的,單 ...

你覺得最難的遊戲?

Victoria avatar
By Victoria
at 2012-11-23T14:23
http://www.tudou.com/programs/view/pSmwhRXKulg/ 課長挑戰與遊戲訪談 難得的中文字幕(殘體+土豆網 不喜誤點) 建議看一下 遊戲廣告(  ̄ c ̄)y▂ξ http://www.youtube.com/watch?v=uyFymzSj0yQ 以完整的遊戲來看 ...