口袋妖怪綠寶石——數(shù)據(jù)提取與代碼分析(2-基于名稱列表的詳情信息提取)
說在前面:
? ? 在上期專欄,我們利用字符集,可以對ROM中的文本列表進行提取,這些文本列表通常是一類游戲?qū)ο蟮拿Q,例如精靈名稱、道具名稱、招式名稱等,因此也可以稱作名稱列表。以名稱列表為基礎(chǔ),利用Excel表格的自動填充功能,上面提到的3個名稱列表又可以對應(yīng)到精靈代碼、道具代碼和招式(技能)代碼。
????精靈除了種族名稱之外,還有許多其他的信息,例如種族值、特性、獲得努力值、屬性、蛋組等等;道具除了名稱之外,還有出售價格、使用效果、背包分類等信息。這些都可以稱為詳情信息,本期專欄的目標(biāo)是提取名稱列表對應(yīng)的詳情信息,填充成為一個更大的表格。

詳情信息在哪兒找
????上期專欄提到,如果想要找到名稱列表,可以通過搜索一個已知名稱的編碼字節(jié)序列來定位。以精靈名稱列表為例,怎樣找到精靈的詳情信息呢?
????我們需要從精靈名稱列表對應(yīng)的符號“gSpeciesNames”入手,利用VS Code提供的查找功能,找到精靈詳情信息對應(yīng)的符號是什么。
????在VS Code中打開原版綠寶石的源代碼工程,選擇“編輯——在文件中查找”,或者使用Ctrl+Shift+F快捷鍵,在搜索欄里填入gSpeciesNames,VS Code可以找到這個字符串出現(xiàn)在了哪些文件中:

????現(xiàn)在可以用更專業(yè)一點的說法了,其實gSpeciesNames是一個常量的名字,這是符號表里面的一類符號,其他的符號還有函數(shù)名、變量名等等。之所以說是常量,是因為查找結(jié)果的第一條恰好就是gSpeciesNames的聲明,它使用了const進行修飾,所以是常量,這是C語言的編程語法。
????鼠標(biāo)單擊任意一個查找結(jié)果,VS Code就可以自動打開這個查找結(jié)果對應(yīng)的文件,比如打開第一條查找結(jié)果:

????這里可以看到打開的文件是data.h,里面的第127行是常量gSpeciesNames的聲明。聲明只說明了這個常量的類型,是一個二維數(shù)組,要知道它的內(nèi)容還需要找到它的定義。VS Code作為代碼編輯器,找到任何一個名字的定義都是非常方便的,只需要在gSpeciesNames上點擊右鍵,就可以看到“轉(zhuǎn)到定義”的選項:

????單擊“轉(zhuǎn)到定義”后,就可以看到VS Code打開了一個新的文件species_names.h:

????這個文件恰好就是我們在上一期專欄提取出來的精靈名稱列表。
????按照C語言的語法,在定義gSpeciesNames這個數(shù)組時,還定義了一系列的枚舉類型常量,也就是以SPECIES_開頭的那些符號。由于C語言的數(shù)組下標(biāo)從0開始,所以上面的定義相當(dāng)于:
????可以理解為,這些SPECIES_開頭的常量就是精靈代碼:它表示一個數(shù)字,這個數(shù)字對應(yīng)到一個精靈的種族名稱。在源代碼項目中,會用這些常量的名稱來代替純粹的數(shù)字(這是為了程序的可讀性),比如說之后程序中的某處需要用到“妙蛙種子”的精靈代碼,就會用SPECIES_BULBASAUR這個常量代替,而不是直接寫一個數(shù)字1。
????有了精靈代碼,定義精靈詳情的時候一定會用到這些常量,于是“文件查找”功能再次上線,這次查找SPECIES_BULBASAUR:

????這里出現(xiàn)了28個查找結(jié)果,其實每一個都可以點進去看看,它所在的文件名大致描述了這個常量用在這里的原因。把幾個可能有用的查找結(jié)果列舉如下:
species_info.h文件,精靈詳情信息
evolution.h文件,精靈進化信息
tmhm_learnsets.h文件,精靈可以學(xué)習(xí)的技能機器
????以species_info.h文件為例,查找SPECIES_BULBASAUR結(jié)果是這樣的:

????可以看到,妙蛙種子這只精靈的詳情詳細(xì)地列舉在文件中,并且往下依次是第二只、第三只……精靈的詳情信息,這正是我們要找的精靈詳情信息!
????這里對我們價值最大的信息是:精靈詳情信息對應(yīng)的常量名稱是gSpeciesInfo,它的類型是struct SpeciesInfo,這是C語言中的結(jié)構(gòu)體。這兩個信息對于我們在ROM中提取數(shù)據(jù)是至關(guān)重要的:常量名稱可以通過符號表來找到地址;類型可以讓我們知道一只精靈的詳情信息需要多少個字節(jié)來描述。一個一個來:
????在符號表(pokeemerald.sym.txt)文件中查找gSpeciesInfo,可以看到它的地址:

????gSpeciesInfo地址從083203cc開始,長度是0x2d10個字節(jié)。
????在species_info.h文件中,右鍵點擊struct?SpeciesInfo跳轉(zhuǎn)到它的定義:

????這個結(jié)構(gòu)體內(nèi),每個成員變量的左側(cè)都有注釋,解釋了這些變量的相對地址。例如變量friendship位于相對地址0x12處,這個信息對于我們填寫精靈詳情信息的表格非常重要。
????這里還有一點解釋一下:以evYield_開頭的變量,它們的含義是擊敗這只精靈后會獲得多少努力值(神奇寶貝百科上把努力值也稱作基礎(chǔ)點數(shù)),可以看到前4個這樣的變量相對地址都是0x0A,這是因為這些變量并沒有占據(jù)一整個字節(jié)的空間,而是只占了2個二進制位,4個變量一共占據(jù)8位,從而利用整個字節(jié)的空間。變量名稱后面的“:2”就是指一個變量占2個二進制位的意思。
????同樣地,0x19處的一個字節(jié)也分為兩個變量,低7位(第0位到第6位)是bodyColor變量,最高位(第7位)是noFlip變量。
????觀察0x0B位置的讀者可能有疑惑:一個字節(jié)有8個二進制位,0x0A和0x19處的變量都已經(jīng)把所有二進制位充分利用了,但是0x0B處只用了4位(evYield_SpAttack和evYield_SpDefense),剩下的4位為什么沒有對應(yīng)的變量呢?
????如果沒有的話,那就說明剩下的4位沒有實際意義,這4位不包含任何信息。
????這個結(jié)構(gòu)體的定義告訴我們:一只精靈的詳情需要0x1A個字節(jié)(最后一個變量在0x19處,且占用1個字節(jié),0x19+1=0x1A),也就是26個字節(jié)。

真是26個字節(jié)?數(shù)據(jù)對齊引發(fā)的問題
????剛才僅僅是說明:SpeciesInfo這個結(jié)構(gòu)體定義的單個變量至少占用26個字節(jié),但它實際占用的字節(jié)數(shù)不一定是26。為了說明這一點,有一個C語言程序的傳統(tǒng)需要介紹。
????先說一個結(jié)論:ROM中的變量或者常量往往會對齊到以4為倍數(shù)的地址。這是因為原本ROM運行在GBA游戲機上,這個游戲機模擬的是32位操作系統(tǒng),32位就是4個字節(jié),對于每次操作以4個字節(jié)為單位的數(shù)據(jù)是最方便快捷的。同樣的道理,如果數(shù)據(jù)放在ROM中的地址是4的倍數(shù),中央處理器(CPU)在取數(shù)據(jù)時也會更快捷。這個道理在后面的專欄進行匯編代碼分析時還會再做說明!
????gSpeciesInfo的起始地址083203cc就是4的倍數(shù),但是剛才分析的結(jié)構(gòu)體大小26不是4的倍數(shù)。比26大(需要保證能放得下結(jié)構(gòu)體的所有內(nèi)容)、同時又是4的倍數(shù)的數(shù)字,最小是28(“最小”是為了節(jié)約空間)。gSpeciesInfo里的第二組數(shù)據(jù)(編號為1的精靈對應(yīng)的詳情信息)需要對齊到083203cc+0x1C這個地址,這就是數(shù)據(jù)對齊。
????還有一個方法驗證SpeciesInfo結(jié)構(gòu)體究竟是占用26個字節(jié)還是28個字節(jié)。上期專欄提取出的精靈列表,序號從0x0000到0x019B,共有0x19C行數(shù)據(jù),而gSpeciesInfo長度0x2d10個字節(jié),0x2d10和0x19C相除正好是0x1C(十進制的28)。

利用“多行編輯”高效編輯字節(jié)序列
????現(xiàn)在可以從HxD中將ROM的字節(jié)序列復(fù)制出來了:

????將這部分?jǐn)?shù)據(jù)復(fù)制到VS Code中,接下來是給數(shù)據(jù)分段。字節(jié)序列的格式是2個數(shù)字或英文字母,后面跟著一個空格,然后重復(fù)同樣的格式。我們需要把每28個字節(jié)放在一行內(nèi),正則表達(dá)式的查找功能非常適合這個操作。
????在Ctrl+F彈出的查找對話框內(nèi)輸入這個正則表達(dá)式:
????這個正則表達(dá)式含義是2個數(shù)字或英文字母、后面跟一個空格,這個模式重復(fù)28次。按組合鍵Alt+Enter進入多行編輯模式,就可以在每28個字節(jié)后面輸入一個換行符,實現(xiàn)數(shù)據(jù)分段:

????通過調(diào)整光標(biāo)的位置,可以把光標(biāo)定位到每一行的開頭,對每行的編輯等價于對所有行的編輯。接下來需要按照SpeciesInfo結(jié)構(gòu)體的定義,把字節(jié)進行分組歸類,組與組之間用制表符隔開,這在多行編輯的情況下處理起來非常方便。
????從baseHP到expYield,前10個字節(jié)都是單字節(jié)的變量(u8類型就是8位2進制,也就是1個字節(jié)),在編輯時直接刪除空格換成制表符即可。
????0x0A和0x0B都是和努力值相關(guān)的變量,需要把它們合并在一起。在合并時,由于多字節(jié)變量的取值是按照小端序排列的,需要把字節(jié)的順序反過來再拼接到一起,這就需要在多行編輯的時候能夠進行“選中特定的一段數(shù)據(jù)”這種操作,并且這個操作是在所有行同時進行的。

????按住Shift鍵,然后通過方向鍵控制光標(biāo),就可以在多行編輯的情況下選中特定的一段數(shù)據(jù),例如上圖選中了0x0B對應(yīng)的一列,我們需要把它剪切,并粘貼到到0x0A那一列的前面。操作后的效果如下:

????可以用同樣的方式繼續(xù)處理后面的變量。雖然按照每行28個字節(jié)來劃分,但只有前26個字節(jié)是有意義的,因此可以放心地刪掉最后2個字節(jié)。
????最后處理結(jié)果如下圖,這是可以直接復(fù)制到Excel表格中的數(shù)據(jù)格式:

????復(fù)制到Excel表格中,添加合適的列頭,和之前的精靈名稱列表結(jié)合到一起,一個精靈詳情表就創(chuàng)建出來了:


利用“詳情表”探索未知數(shù)據(jù)
????在源代碼項目的指導(dǎo)下,我們很清楚字節(jié)序列中每個字節(jié)代表的含義。但是當(dāng)分析沒有源代碼的數(shù)據(jù)時,可能有些字節(jié)的含義事先我們并不知道。此時就需要利用Excel的“篩選”功能,推測未知字節(jié)可能代表的含義。
????以上面的“精靈詳情表”為例,比如說第J列的“屬性1”我們并不知道是什么含義,但是在每只精靈的數(shù)據(jù)都已知的情況下,我們可以通過對具有相同“屬性1”的精靈分類,來推測它可能是什么含義。
????選中整個表格,使用Excel的“數(shù)據(jù)——篩選”功能,在第一行的每個單元格右邊都會出現(xiàn)一個下拉箭頭,點擊“屬性1”所在的列,可以看到有一個按照取值分類的下拉列表:

????這里我們把其他所有的對勾都取消(可以簡單地通過把“全選”對勾取消來實現(xiàn)),然后只勾選01,看看剩下的都是什么精靈:

????這里面的精靈有:猴怪、火爆猴、腕力、豪力、飛腿郎、快拳郎、無畏小子、怪力、幕下力士、鐵掌力士、瑪莎那、恰雷姆。這些精靈有什么共同的特點呢?
????基于對口袋妖怪綠寶石這個游戲的了解,熟悉的玩家應(yīng)該能立刻想到這些精靈都是格斗系的精靈?!皩傩?”這個標(biāo)簽就算是未知的,通過剛才的分析也能大致猜到它的含義。
????這種通過提取數(shù)據(jù)、數(shù)據(jù)篩選的分析方式在探索改版綠寶石ROM時能起到很大的作用!

一些常見的名稱列表和詳情列表常量名稱
????這里列舉一些口袋妖怪綠寶石ROM中常見的名稱列表和詳情列表的常量名稱。結(jié)合符號表和這兩期專欄介紹的數(shù)據(jù)提取操作,可以制作出許多張Excel詳情表格:
????注:這里的“特性”舉個例子比較好理解,例如“妙蛙種子”的普通特性是“茂盛”。

????這兩期專欄主要介紹利用文本信息來提取ROM數(shù)據(jù)的方法,提取的數(shù)據(jù)都遵循列表格式,可以方便地復(fù)制到Excel表格中進行處理。但是ROM中還有其他長度不定的數(shù)據(jù),也不遵循列表的格式,例如地圖數(shù)據(jù)、對話文本、腳本等等。這些數(shù)據(jù)的提取方法將會留到接下來的專欄進行分析。
????繼續(xù)感謝眾位讀者的支持!