超級任天堂程式設計/SNES 記憶體對映
SNES 卡帶主要有兩種型別,SNES 社群將它們稱為 LoROM 和 HiROM 卡帶。兩者都有不同的記憶體對映。在解釋 LoROM 和 HiROM 之前,我們應該在這裡定義一些關鍵詞。
$ 或 0x 字首: 以下數字為十六進位制。地址通常顯示為十六進位制值。
Bank: 64 千位元組 (65536 或 $10000 位元組),基本上是 CPU 理解的 3 位元組地址的最高有效位元組。地址 $AABBCC 的 bank 為 $AA (170)。使用三個位元組的地址空間,SNES 可以定址高達 16 兆位元組 (2^24 或 1<<24 或 16777216 位元組 == 16384 千位元組 == 16 兆位元組)。請注意,僅僅因為 SNES 可以定址 16 兆位元組並不意味著它也有 16 MB 的 RAM(這裡稱為 WRAM) - 我們將在後面詳細解釋。記住,因此 SNES 有 $100 或 256 個 bank(從 $00 開始,到 $FF 結束)。
Page: 256 位元組 ($0100 位元組)。每當機器需要執行對映任務時,都會使用頁面(例如,確保地址 $AABBCC 和地址 $DDBBCC 指向完全相同的資料,如果機器應該這樣工作的話)。頁面也用於普通的 PC(x86/x86-64 架構),因此並非 SNES 獨有。頁面是機器的最小可對映單元。你會注意到下面的表格中沒有條目,其中某個記憶體範圍沒有以 $FF 結尾 - 這是因為 $FF 是一個頁面的最後一個地址,在此位元組之後一個新頁面開始。SNES 嚴重依賴於對映 - 例如,如果沒有對映,它將無法將 $00 – $3F 的所有 bank 的兩個 WRAM 頁面直接放在 bank 的開頭。一個 bank 包含 256 ($100) 個頁面,因此 SNES 有 $100 (bank) * $100 (page) == $10000 (65536) 個頁面可用。
LoROM 和 HiROM 都可以儲存高達 4 兆位元組 (32 兆位) 的 ROM 資料。ExLoROM、ExHiROM 和一些擴充套件晶片(如 SA-1 和 SDD-1)據說可以儲存高達 8 兆位元組 (64 兆位)。
在 SNES 標頭中的位元組 $15(有關更多詳細資訊,請參見下文),儲存了 ROM 組成位元組。
| 值 | 位掩碼 | 定義 | 示例 ROM | 示例 ROM 大小 |
|---|---|---|---|---|
| $20 | %0010 0000 | LoROM | 最終幻想 4 | 1048576 位元組 / 1 MB |
| $21 | %0010 0001 | HiROM | 最終幻想 5 | 2097152 位元組 / 2 MB |
| $23 | %0010 0011 | SA-1 ROM | 超級馬里奧 RPG | 4194304 位元組 / 4 MB |
| $30 | %0011 0000 | LoROM + FastROM | 終極 VII | 1572864 位元組 / 1.5 MB |
| $31 | %0011 0001 | HiROM + FastROM | 最終幻想 6 | 3145728 位元組 / 3 MB |
| $32 | %0011 0010 | SDD-1 ROM | 星之海洋 | 6291456 位元組 / 6 MB |
| $35 | %0011 0101 | ExHiROM | 幻想傳說 | 6291456 位元組 / 6 MB |
要使用的位掩碼是 001A0BCD,基本值為 $20
- A == 0 表示 SlowROM (+ $0),A == 1 表示 FastROM (+ $10)。
- B == 1 表示 ExHiROM (+ $4)
- D == 0 表示 LoROM (+ $0),D == 1 表示 HiROM (+ $1),在擴充套件 ROM 的情況下與 B 和 C 一起使用。
請記住,有些人有時使用“模式 20”來指代 LoROM 對映模型,使用“模式 21”來指代 HiROM,儘管這在技術上是錯誤的。如表所示,有兩個 LoROM 和兩個 HiROM 對映,它們的標記位元組與名稱暗示的不同。
ExLoROM 是一個非官方的對映,因此它沒有自己的型別值。要檢測它,通常會檢查遊戲是否為普通的 LoROM,然後檢查 ROM 檔案大小。
請注意,像 SuperFX 這樣的擴充套件晶片有自己的記憶體對映,這裡沒有涵蓋。特別是 SA-1 和 SDD-1 是 MMC 晶片,這意味著它們可以進行 bank 切換,以更改為給定 bank 訪問的 ROM 部分。
這是 LoROM 記憶體對映
| Bank | 偏移量 | 定義 | ROM 地址 | 映象 |
|---|---|---|---|---|
| $00–$3F | $0000-$1FFF | LowRAM,從 bank $7E 映象 | (沒有 ROM 對映) | $7E (WRAM 的前兩個頁面) |
| $2000–$20FF | 未使用 | (沒有 ROM 對映) | $80-$BF | |
| $2100–$21FF | PPU1、APU、硬體暫存器 | (沒有 ROM 對映) | $80-$BF | |
| $2200–$2FFF | 未使用 | (沒有 ROM 對映) | $80-$BF | |
| $3000–$3FFF | DSP、SuperFX、硬體暫存器(有關於此頁面的文件嗎?我找不到任何來源) | (沒有 ROM 對映) | $80-$BF | |
| $4000–$40FF | 舊式手柄暫存器 | (沒有 ROM 對映) | $80-$BF | |
| $4100–$41FF | 未使用 | (沒有 ROM 對映) | $80-$BF | |
| $4200–$44FF | DMA、PPU2、硬體暫存器 | (沒有 ROM 對映) | $80-$BF | |
| $4500–$5FFF | 未使用 | (沒有 ROM 對映) | $80-$BF | |
| $6000–$7FFF | 保留(增強晶片記憶體) | (沒有 ROM 對映) | $80-$BF | |
| $8000-$FFFF | LoROM 部分(程式記憶體) | $00: $000000 - $007FFF
$01: $008000 - $00FFFF
$02: $010000 - $017FFF
...
$3D: $1E8000 - $1EFFFF
$3E: $1F0000 - $1F7FFF
$3F: $1F8000 - $1FFFFF
|
$80-$BF | |
| $40–$6F | $0000-$7FFF | 如果晶片不是 MAD-1,可以對映為更高 bank ($8000 - $FFFF)。否則該區域未使用。 | 未對映或$40: $200000 - $207FFF
$41: $208000 - $20FFFF
$42: $210000 - $217FFF
...
$6D: $368000 - $36FFFF
$6E: $370000 - $377FFF
$6F: $378000 - $37FFFF
|
$C0-$EF |
| $8000-$FFFF | LoROM 部分(程式記憶體) | $40: $200000 - $207FFF
$41: $208000 - $20FFFF
$42: $210000 - $217FFF
...
$6D: $368000 - $36FFFF
$6E: $370000 - $377FFF
$6F: $378000 - $37FFFF
|
$C0-$EF | |
| $70–$7D | $0000-$7FFF | 卡帶 SRAM - 最大 448 千位元組 | (沒有 ROM 對映) | $F0-$FD |
| $8000-$FFFF | LoROM 部分(程式記憶體) | $70: $380000 - $387FFF
$71: $388000 - $38FFFF
$72: $390000 - $397FFF
...
$7B: $3D8000 - $3DFFFF
$7C: $3E0000 - $3E7FFF
$7D: $3E8000 - $3EFFFF
|
$F0-$FD | |
| $7E | $0000-$1FFF | LowRAM (WRAM) | (沒有 ROM 對映) | $00–$3F (WRAM 的前兩個頁面) |
| $2000–$7FFF | HighRAM (WRAM) | (沒有 ROM 對映) | (沒有對映) | |
| $8000-$FFFF | 擴充套件 RAM (WRAM) | (沒有 ROM 對映) | (沒有對映) | |
| $7F | $0000-$FFFF | 擴充套件 RAM (WRAM) | (沒有 ROM 對映) | (沒有對映) |
| $80-$BF | $0000-$FFFF | 映象 $00–$3F | (參見 bank $00–$3F) | $00–$3F |
| $C0-$EF | $0000-$FFFF | 映象 $40–$6F | (參見 bank $40–$6F) | $40–$6F |
| $F0-$FD | $0000-$FFFF | 映象 $70–$7D | (參見 bank $70–$7D) | $70–$7D |
| $FE-$FF | $0000-$7FFF | 卡帶 SRAM - 64 千位元組(總計 512 KB) | (沒有 ROM 對映) | (沒有對映) |
| $8000-$FFFF | LoROM 部分(程式記憶體) | $7E: $3F0000 - $3F7FFF
$7F: $3F8000 - $3FFFFF
|
(沒有對映) (請記住這些 bank) (在 <$7F 範圍內被 WRAM 覆蓋了) |
在 LoROM 模式下,ROM 始終對映在每個 bank 的上半部分,因此每個塊 32 千位元組。bank $00 – $7D(地址:$8000 - $FFFF)儲存連續資料,以及 bank $80 - $FF。卡帶上的 SRAM 連續且重複對映 - 8 千位元組的 SRAM 對映在 $0000 - $1FFF、$2000 – $3FFF、$4000 – $5FFF 等處。由於 SNES 的 WRAM 對映在 bank $7E - $7F,因此這些 bank 不會對映到最後一個 SRAM/ROM 塊。必須透過 bank $80 - $FF 訪問此記憶體。在 LoROM 和 HiROM 模式下,都沒有其他方法可以訪問此記憶體。
LoROM 的建立是為了確保系統 bank ($00 – $3F) 的更高頁面 (>7) 實際上被使用。這是透過僅將整個 ROM 載入到更高頁面以及 32 千位元組塊中來實現的。32 KB * $80 bank == 4 兆位元組。
這是 HiROM 記憶體對映
| Bank | 偏移量 | 定義 | ROM 地址 | 映象 |
|---|---|---|---|---|
| $00–$1F | $0000-$1FFF | LowRAM,從 bank $7E 映象 | (沒有 ROM 對映) | $7E (WRAM 的前兩個頁面) |
| $2000–$20FF | 未使用 | (沒有 ROM 對映) | $80–$9F | |
| $2100–$21FF | PPU1、APU、硬體暫存器 | (沒有 ROM 對映) | $80–$9F | |
| $2200–$2FFF | 未使用 | (沒有 ROM 對映) | $80–$9F | |
| $3000–$3FFF | DSP、SuperFX、硬體暫存器(有關於此頁面的文件嗎?我找不到任何來源) | (沒有 ROM 對映) | $80–$9F | |
| $4000–$40FF | 舊式手柄暫存器 | (沒有 ROM 對映) | $80–$9F | |
| $4100–$41FF | 未使用 | (沒有 ROM 對映) | $80–$9F | |
| $4200–$44FF | DMA、PPU2、硬體暫存器 | (沒有 ROM 對映) | $80–$9F | |
| $4500–$5FFF | 未使用 | (沒有 ROM 對映) | $80–$9F | |
| $6000–$7FFF | 保留 | (沒有 ROM 對映) | $80–$9F | |
| $8000-$FFFF | HiROM 部分(程式記憶體) | $00: $008000 - $00FFFF
$01: $018000 - $01FFFF
$02: $028000 - $02FFFF
...
$1D: $1D8000 - $1DFFFF
$1E: $1E8000 - $1EFFFF
$1F: $1F8000 - $1FFFFF
|
$80–$9F | |
| $20–$3F | $0000-$1FFF | LowRAM,從 bank $7E 映象 | (沒有 ROM 對映) | $7E (WRAM 的前兩個頁面) |
| $2000–$20FF | 未使用 | (沒有 ROM 對映) | $A0-$BF | |
| $2100–$21FF | PPU1、APU、硬體暫存器 | (沒有 ROM 對映) | $A0-$BF | |
| $2200–$2FFF | 未使用 | (沒有 ROM 對映) | $A0-$BF | |
| $3000–$3FFF | DSP、SuperFX、硬體暫存器(有關於此頁面的文件嗎?我找不到任何來源) | (沒有 ROM 對映) | $A0-$BF | |
| $4000–$40FF | 舊式手柄暫存器 | (沒有 ROM 對映) | $A0-$BF | |
| $4100–$41FF | 未使用 | (沒有 ROM 對映) | $A0-$BF | |
| $4200–$44FF | DMA、PPU2、硬體暫存器 | (沒有 ROM 對映) | $A0-$BF | |
| $4500–$5FFF | 未使用 | (沒有 ROM 對映) | $A0-$BF | |
| $6000–$7FFF | 卡帶 SRAM - 8 千位元組(總計 256 KB) | (沒有 ROM 對映) | $A0-$BF | |
| $8000-$FFFF | HiROM 部分(程式記憶體) | $20: $208000 - $20FFFF
$21: $218000 - $21FFFF
$22: $228000 - $22FFFF
...
$3D: $3D8000 - $3DFFFF
$3E: $3E8000 - $3EFFFF
$3F: $3F8000 - $3FFFFF
|
$A0-$BF | |
| $40–$7D | $0000-$FFFF | HiROM 部分(程式記憶體) | $00: $000000 - $00FFFF
$01: $010000 - $01FFFF
$02: $020000 - $02FFFF
...
$3B: $3B0000 - $3BFFFF
$3C: $3C0000 - $3CFFFF
$3D: $3D0000 - $3DFFFF
|
$C0-$FD |
| $7E | $0000-$1FFF | LowRAM (WRAM) | (沒有 ROM 對映) | $00–$3F (WRAM 的前兩個頁面) |
| $2000–$7FFF | HighRAM (WRAM) | (沒有 ROM 對映) | (沒有對映) | |
| $8000-$FFFF | 擴充套件 RAM (WRAM) | (沒有 ROM 對映) | (沒有對映) | |
| $7F | $0000-$FFFF | 擴充套件 RAM (WRAM) | (沒有 ROM 對映) | (沒有對映) |
| $80–$9F | $0000-$FFFF | 映象 $00–$1F | (參見 bank $00–$1F) | $00–$1F |
| $A0-$BF | $0000-$FFFF | 映象 $20–$3F | (參見 bank $20–$3F) | $20–$3F |
| $C0-$FD | $0000-$FFFF | 映象 $40–$7D | (參見 bank $40–$7D) | $40–$7D |
| $FE-$FF | $0000-$FFFF | HiROM 部分(程式記憶體) | $3E: $3E0000 - $3EFFFF
$3F: $3F0000 - $3FFFFF
|
(沒有對映) (請記住這些 bank) (在 <$7F 範圍內被 WRAM 覆蓋了) |
HiROM 的理解稍微複雜一些。與 LoROM 不同,它不使用 $80 (128) 個區域來對映 ROM 到 SNES 的地址空間,而只使用 $40 (64) 個區域。同樣與 LoROM 不同,這些區域被充分利用,即每個塊為 64 KB。64 KB * $40 個區域 == 4 MB。區域 $40 - $7D (地址:$0000 - $FFFF) 以及區域 $C0 - $FF 包含連續資料。注意,HiROM 還為區域 $00 - $3F 和 $80 - $BF 建立了對映。由於這些是系統區域,它們的低頁 (<8) 已被對映 - 但高頁是空閒的,因此 ROM 的許多部分被映象四次到 SNES 的地址空間。卡帶上的 SRAM 被對映到區域 $20 - $3F,以 8 KB 塊的形式。由於只有 32 個區域為此保留,因此理論上 HiROM 中可訪問的 SRAM 量低於 LoROM (256 KB 對 512 KB)。由於 SNES 的 WRAM 被對映到區域 $7E - $7F,因此這些區域不會對映到最後一個 ROM 塊。必須透過 $80 - $FF 區域訪問此記憶體。
區域 $80 - $FF 也可以用於更快的記憶體訪問。許多記憶體區域 <$80 以 2.68 MHz (200 ns) 的速度訪問。如果地址 $420D (硬體暫存器) 的值為 1,則訪問記憶體 >$80 以 3.58 MHz (120 ns) 的速度進行。
LoROM 基本上意味著地址線 A15 被卡帶忽略,因此卡帶不會區分任何區域中的 $0000-$7FFF 和 $8000-$FFFF。較小的 ROM 使用此模型來防止在區域 $00–$3F 中浪費空間。
LoROM 和 HiROM 被設計用來將 4 MB 儲存在 ROM 中,並將其對映到 SNES 的 24 位地址空間(如果忘記了,它最多可以定址 16 MB)。通常,對於許多遊戲來說,這已經足夠了,特別是考慮到 SNES 最初並不打算只使用控制檯來渲染許多後來的遊戲。這也是 SNES 為 SuperFX 晶片保留一些地址空間的原因,這些晶片可以使遊戲節省 CPU - 它並沒有打算讓 SNES 在沒有增強晶片的幫助下玩像《夢幻模擬戰》、《最終幻想 6》或《星之卡比》這樣的遊戲。
除了 SNES 設計上的硬體問題之外,程式設計師也面臨著巨大的空間限制。《最終幻想 6》的英文劇本 - 確切地說,是早期草稿 - 被削減了 75%,因為否則它會耗盡用於日版遊戲的 24 兆位 (3 MB) 卡帶。將 ROM 從 24 MBits 擴充套件到 32 MBits 不會造成太大的問題,但超過 4 MB 的任何內容都會超出 LoROM/HiROM 的範圍。
SNES 的一個巨大優勢在於它沒有定義如何將某些地址塊對映到卡帶的 ROM 中。卡帶負責將它需要使用的地址正確對映到它的 ROM 中。對於想要程式設計模擬器的人來說,這種行為會帶來一些問題,因為他們必須程式設計並模擬 SNES 硬體以及卡帶。請記住,卡帶與 SNES 一樣,包含它自己的處理器和微晶片,這些處理器和微晶片可能會表現出不同的行為。例如,一個常見的問題:在卡帶中使用 MAD-1 地址解碼晶片,它將 $40–7D:8000-FFFF ROM 區域的內容對映到區域的低半部分(因此 $400005 和 $408005 之間沒有區別 - 請參閱 LoROM 表以瞭解確切的地址)。真正的 SNES 不必關心這一點,模擬器必須關心。
因此,使用一種格式,允許使用高達 8 MB 的 ROM 空間而無需使用擴充套件 MMC 晶片,不會造成明確的硬體問題,因為卡帶上的地址解碼晶片需要處理這個問題。兩種新的格式被引入:ExLoROM(如果好奇,字首“Ex”代表“擴充套件”),以及 ExHiROM。只有兩款遊戲實際上從未在北美正式釋出,使用了 ExHiROM,分別是《夢幻模擬戰》和《大怪獸物語 2》。另一方面,ExLoROM 沒有用於任何商業遊戲,被認為是一種非官方的對映,因此模擬器對它的支援有時已知存在不足或不一致。但是,包含新遊戲內容的 ROM 補丁通常會擴充套件原始 ROM 的大小,並且可能會使用這兩種格式中的一種。事實上,《時空之輪》的“DoctorL”和“KajarLab”的重新翻譯都將遊戲從 4 MB 擴充套件到 6 MB。
關於 ExLoROM 和 ExHiROM 的記憶體佈局,有很多誤導性或難以理解的解釋和示例,原因有兩個。
- 駭客通常不會遇到這些格式。
- 無法理解為什麼有人需要如此多的記憶體。*
首先讓我們介紹一下 ExLoROM 的記憶體對映 - 我們不會詳細介紹系統區域低半部分的對映,因為這些已經在之前介紹過了,並且不會改變擴充套件 ROM 格式。
| Bank | 偏移量 | ROM 地址 | 映象 |
|---|---|---|---|
| $00–$3F | $8000-$FFFF | $00: $400000 - $407FFF
$01: $408000 - $40FFFF
$02: $410000 - $417FFF
...
$3D: $5E8000 - $5EFFFF
$3E: $5F0000 - $5F7FFF
$3F: $5F8000 - $5FFFFF
|
$80-$BF |
| $40–$6F | $8000-$FFFF | $40: $600000 - $607FFF
$41: $608000 - $60FFFF
$42: $610000 - $617FFF
...
$6D: $768000 - $76FFFF
$6E: $770000 - $777FFF
$6F: $778000 - $77FFFF
|
(沒有對映) |
| $70–$7D | $8000-$FFFF | $70: $780000 - $787FFF
$71: $788000 - $78FFFF
$72: $790000 - $797FFF
...
$7B: $7D8000 - $7DFFFF
$7C: $7E0000 - $7E7FFF
$7D: $7E8000 - $7EFFFF
|
$F0-$FD |
| $80-$BF | $8000-$FFFF | (參見 bank $00–$3F) | $00–$3F |
| $C0-$EF | $8000-$FFFF | $C0: $200000 - $207FFF
$C1: $208000 - $20FFFF
$C2: $210000 - $217FFF
...
$ED: $368000 - $36FFFF
$EE: $370000 - $377FFF
$EF: $378000 - $37FFFF
|
(沒有對映) |
| $F0-$FD | $8000-$FFFF | $F0: $380000 - $387FFF
$F1: $388000 - $38FFFF
$F2: $390000 - $397FFF
...
$FB: $3D8000 - $3DFFFF
$FC: $3E0000 - $3E7FFF
$FD: $3E8000 - $3EFFFF
|
$70–$7D |
| $FE-$FF | $8000-$FFFF | $FE: $3F0000 - $3F7FFF
$FF: $3F8000 - $3FFFFF
|
(沒有對映) |
注意:這些資訊是錯誤的,因為我不得不意識到。從偏移量 $200000 開始的 ROM 毫無意義。此外,由於它仍然是 LoROM 格式,因此第一個 ROM 區域必須載入到 $00:8000-FFFF。但是,模板將很快被修正,並且全部刪除它沒有任何意義。
(ExHiROM 的解釋和表格即將推出)。
(*第二個原因實際上是一種保護機制。如果駭客遇到有人想要將 ROM 擴充套件到 32 兆位限制之外,駭客會自動假設這個人是一個新手,對他在做什麼沒有太多概念,而且在很多情況下,他是對的。但是,這並不能證明關於這些主題的文件缺乏。
在區域 0(卡帶的第一個區域)的末尾,儲存了 64 ($40) 位元組的卡帶資訊。這些資訊對於執行儲存在卡帶上的 ROM 至關重要,因為它包含 ROM 的內部名稱、中斷向量(ROM 中機器程式碼的地址,以 16 位格式)、版本等。ROM 的這部分也稱為 SNES 標頭(不要與我們很快會談到的 SMC 標頭混淆)。
ROM 的第一個區域的末尾取決於它使用的記憶體對映。在 LoROM 的情況下,第一頁的末尾位於 $7FFF,對於 HiROM,第一頁的末尾位於 $FFFF。因此,您基本上必須檢查 ROM 的這兩個位置,以檢視資訊是否符合標準標頭格式,也就是說,它們是否足夠合理。
一些卡帶附帶所謂的 SMC 標頭。SMC 標頭通常由複製裝置在 ROM 的開頭寫入,並佔用 $200 (512) 位元組的額外空間。補丁通常依賴於您的 ROM 是“帶標頭的”還是“無標頭的”,因為這會改變 ROM 中的偏移量。應用到錯誤型別 ROM 的補丁很容易破壞卡帶的程式程式碼。
建立 SMC 標頭“相對”簡單,刪除它更簡單。通常,SMC 標頭包含 ROM 的大小除以 8 KB 單位 &$FF (按位與 255) 在位元組 0 中,大小 >> 8 (按位右移一個完整的位元組) 在位元組 1 中,以及卡帶型別在位元組 2 中。剩下的其他位元組 (509) 被設定為零。要建立標頭,您只需在 ROM 的開頭新增一個 512 位元組的塊,其中包含這些資訊。要刪除標頭,您只需在“忘記”前 512 位元組的情況下將 ROM 儲存到您的 HDD/SSD 上。
我們為什麼會在應該處理 SNES 標頭時關注 SMC 標頭 - 實際上,有兩個原因。
1. 在將 ROM 對映到記憶體時,必須考慮 SMC 標頭是否存在。帶標頭的 ROM 中的區域不會從偏移量 $0 開始,而是從 $200 (512,標頭的大小) 開始,因此區域的末尾也會相應地移動。
2. 如果 ROM 中沒有 SMC 標頭,或者您在解析 ROM 時不信任這些資訊,您唯一確定您要載入的卡帶型別的方法是檢查已經提到的位置的格式正確的標頭。但是,如果您信任這些資訊(您不應該),這是一種確定您是處理 LoROM 還是 HiROM 的方法。
在嘗試載入這些資訊之前,您必須確定 SMC 的大小,無論它是否存在。ROM 大小模 1024 (ROM 大小 % $400) 會給出 SMC 標頭的大小。如果為 0,則沒有標頭。如果不是 512,則標頭格式錯誤。
ROM 中的以下偏移量是無標頭 ROM 的偏移量。您必須將 SMC 標頭的大小新增到 ROM 中的所有地址,才能正確讀取它。x 是當前 ROM 區域的最後一頁(如果卡帶使用 LoROM 對映,則為 $7,如果使用 HiROM,則為 $F)。
| 偏移量 | 名稱 | 描述 |
|---|---|---|
| $xFC0 | 遊戲標題 | 21 位元組,通常為大寫 ASCII。 |
| $xFD5 | 對映模式 | 001ABBBB; A==1 表示 FastROM ($10)。如果 BBBB 是對映模式。 |
| $xFD6 | ROM 型別 | 表示卡帶包含擴充套件晶片、SRAM、電池等。 |
| $xFD7 | ROM 大小 | ROM 的大小,儲存為 。如果您執行 $400<<(ROM 大小),您將獲得以位元組為單位的總大小。 |
| $xFD8 | SRAM 大小 | SRAM 的大小,或稱為卡帶 RAM。儲存為 。通常用於儲存遊戲進度。要再次獲取大小,請使用 $400<<(RAM 大小)。 |
| $xFD9 | 開發人員 ID | 如果使用標題版本 3,則為 $33 |
| $xFDB | 版本號 | |
| $xFDC | 校驗和補碼 | |
| $xFDE | 校驗和 |
在此資訊之後,中斷向量表開始。中斷是硬體直接傳送給 CPU 的訊號,如果中斷未被遮蔽,則需要處理該訊號。中斷向量指定處理給定型別中斷的程式碼的地址。這些向量的大小均為 16 位,應位於 ROM 的第一個區塊中。這是因為 DBR(資料區塊暫存器)和 PBR(程式區塊暫存器)在每次更改模擬模式或執行中斷時都會被設定為 0。
向量的大小均為 2 位元組,並以小端格式儲存(如果讀者不知道這一點 - CPU 是小端 CPU,這意味著 MSB 儲存在更高的位置。大端意味著 MSB 儲存在更低的位置)。程式執行在模擬模式下從復位向量 ($xFFC-D) 開始。
| 中斷名稱 | ROM 中的偏移量 | 描述 |
|---|---|---|
| COP | $xFE4-5 | 軟體中斷。由 COP 指令觸發。類似於 BRK。 |
| BRK | $xFE6-7 | 軟體中斷。由 BRK 指令觸發。類似於 COP。 |
| ABORT | $xFE8-9 | 在 SNES 中未使用。 |
| NMI | $xFEA-B | 不可遮蔽中斷。在垂直重新整理(vblank)開始時呼叫。 |
| IRQ | $xFEE-F | 中斷請求。可以設定為在水平重新整理週期的特定位置呼叫。 |
| 中斷名稱 | ROM 中的偏移量 | 描述 |
|---|---|---|
| COP | $xFF4-5 | 軟體中斷。由 COP 指令觸發。 |
| ABORT | $xFF8-9 | 在 SNES 中未使用。 |
| NMI | $xFFA-B | 不可遮蔽中斷。在垂直重新整理(vblank)開始時呼叫。 |
| RES | $xFFC-D | 復位向量,執行從此向量開始。 |
| IRQ/BRK | $xFFE-F | 中斷請求。可以設定為在水平重新整理週期的特定位置呼叫。也是由 BRK 指令觸發的軟體中斷。 |
本節介紹 LoROM 和 HiROM 的初始解析方法。本文將要分析的遊戲是 *最終幻想 4*(LoROM)和 *最終幻想 6*(HiROM),它們都是無標題的。
首先,我們檢視 ROM 中的地址 $7FC0 - LoROM 區塊的最後一個位元組是 $7FFF,標題佔用高達 64/$40 位元組。因此,如果它是 LoROM(注意,我們還不知道),標題的第一個位元組位於 $7FC0。
00007FC0 46 49 4E 41 4C 20 46 41 4E 54 41 53 59 20 49 49 |FINAL FANTASY II|
00007FD0 20 20 20 20 20 20 02 0A 03 01 C3 00 0F 7A F0 85 | .......z..|
00007FE0 FF FF FF FF FF FF FF FF FF FF 00 02 FF FF 04 02 |................|
00007FF0 FF FF FF FF FF FF FF FF FF FF FF FF 00 80 FF FF |................|
看起來不錯。在前兩行,寫入了 ROM 的名稱(由於 *最終幻想 4* 在北美髮布時被稱為 *最終幻想 2*,所以這實際上是有道理的)。前 21 個位元組只是 ASCII 字元(也是在 ROM 中看到明文的難得機會,因為對話通常以與 ASCII 不同的格式儲存)。此字串之後的下一個位元組可能會被誤認為是另一個空格字元,但實際上 $D5 處的位元組表明它是一個 LoROM 卡帶(現在我們有兩個證明該卡帶是 LoROM:首先是標題的位置,其次是指示卡帶使用 LoROM 的位元組。順便說一句,如果這兩個不匹配,則 ROM 被認為是損壞的)。
人們可能會在那裡進行其他檢查(例如,ROM 的大小與標題中儲存的大小,國家和許可證程式碼,SRAM 大小...),但就目前而言,只有另外兩個位元組對啟動 ROM 至關重要:復位中斷向量在模擬模式下的位置(在 ROM 被對映到 SNES 地址空間的那一刻發出)。該向量以小端格式儲存在 $7FFC-D 中(見上文),這意味著向量按以下方式計算
emulation_reset_vector = rom_content[0x7FFC] | (rom_content[0x7FFD] << 8);
在這個例子中,向量是 $8000 - 啟動程式碼位於第一個區塊的開頭(是的,第一個區塊。請記住第一個區塊是如何儲存在 $8000 - $FFFF 中的,而 $0000 - $7FFF 包含系統資訊?)。
讓我們看一下那裡的程式碼
十六進位制
00000000 78 18 FB C2 10 E2 20 9C 0D 42 9C 0B 42 9B 0B 42 |x..... ..B..B..B|
00000010 A9 8D 8D 00 21 A9 00 8D 00 42 A9 00 EB A9 00 48 |....!....B.....H|
反彙編(使用我正在開發的程式,所以不要問我在哪裡找到它)
$00:8000: 78 ; SEI, disables interrupts. This is a crucial point in the execution of the ROM, so the CPU must not be interrupted.
$00:8001: 18 ; CLC, clears the carry flag. This makes sense in the next instruction.
$00:8002: FB ; XCE, switches into native mode if the carry flag is clear.
$00:8003: C210 ; REP #$10, sets the X and Y registers into 16 bit mode.
$00:8005: E220 ; SEP #$20, sets the accumulator into 8 bit mode. This is all possible with the CPU still being in native mode.
$00:8007: 9C0D42 ; STZ $420D, sets the memory access speed at 200ns when accessing banks >$7F.
$00:800A: 9C0B42 ; STZ $420B, disables all DMA processes that might have been started.
$00:800D: 9C0C42 ; STZ $420C, disables all HDMA processes that might have been started.
$00:8010: A98F ; LDA #$8F, bitmask 10001111 ...
$00:8012: 8D0021 ; STA $2100, ... disables rendering and sets brightness to max.
$00:8015: A900 ; LDA #$00, sets NMI and V/H count to zero and disables the joypad.
$00:8017: 8D0042 ; STA $4200, OK, actually this is done here ...
$00:801A: A900 ; LDA #$00, stuff that is of no interest here ...
$00:801C: EB ; XBA
$00:801D: A900 ; LDA #$00
$00:801F: 48 ; PHA
您會看到這似乎足以成為完全有效的啟動程式碼。
再次,讓我們檢視偏移量 $7FC0
00007FC0 CD 85 CB C2 20 A5 D0 0A AA BF 02 E6 CC 85 C9 A5 |.... ...........|
00007FD0 D0 CF 00 E6 CC 90 05 7B E2 20 E6 CB 7B E2 20 A9 |.......{. ..{. .|
00007FE0 01 8D 68 05 60 9C 67 05 AD B9 1E 29 40 D0 05 AD |..h.`.g....)@...|
00007FF0 45 07 D0 04 9C 45 07 60 A9 64 8D 67 05 A9 CE 85 |E....E.`.d.g....|
這似乎有點不對勁。也許它使用 HiROM?
0000FFC0 46 49 4E 41 4C 20 46 41 4E 54 41 53 59 20 33 20 |FINAL FANTASY 3 |
0000FFD0 20 20 20 20 20 31 02 0C 03 01 33 00 CD a0 32 5F | 1....3...2_|
0000FFE0 FF FF FF FF FF FF FF FF FF FF 10 FF FF FF 14 FF |................|
0000FFF0 FF FF FF FF FF FF FF FF FF FF FF FF 00 FF FF FF |................|
是的,它確實如此。復位向量位於 $FF00。再次使用十六進位制表示
0000FF00 78 18 FB 5C 19 00 C0 FF FF FF FF FF FF FF FF FF |x..\............|
反彙編
$00:FF00: 78 ; SEI, same start-up code ...
$00:FF01: 18 ; CLC, ... as before ...
$00:FF02: FB ; XCE, ... isn't it?
$00:FF03: 5C1900C0; JML $C00019, this is a bit different. The CPU preforms a long jump to a different location
簡而言之,此程式碼告訴正在執行的 CPU $C0:0019 是下一個要執行的指令。由於這是 HiROM 記憶體對映,這意味著我們必須檢視區塊 $C0 被對映到哪裡。參考以前的 HiROM 對映表,區塊 $C0 - $FD 是 $40 – $7D 的直接對映。事實上,這些區塊連續地包含了所有 ROM 內容 - 由於 $C0 <=> $40 且 $40 是開頭,因此要執行的程式碼基本上位於 ROM 的開頭,偏移量 $19
00000000 20 79 68 6B 03 00 08 08 4C 76 C1 4C 3F 4A EA EA | yhk....Lv.L?J..|
00000010 EA EA EA EA EA 20 24 BB 6B 78 18 FB E2 20 C2 10 |..... $.kx... ..|
00000020 A2 FF 15 9A A2 00 00 DA 2B 7B 48 AB A9 01 8D 0D |........+{H.....|
反彙編
$C0:0019: 78 ; SEI, same code as before?
$C0:001A: 18 ; CLC, basically it IS the same code. This leads to the conclusion that the programmers wanted to make sure there are no interrupts enabled.
$C0:001B: FB ; XCE
$C0:001C: E220 ; SEP #$20, sets the accumulator into 8 bit mode.
$C0:001E: C210 ; REP #$10, sets the X and Y registers into 16 bit mode.
$C0:0020: A2FF15 ; LDX #$15FF, now THIS is interesting. The next instruction sets the stack. By default, the stack in
; emulation mode is set to $100 and grows downwards ...
$C0:0023: 9A ; TXS, ... and this is not changed with entering native mode. This instruction sets the stack to $15FF.
$C0:0024: A20000 ; LDX #$0000, the next three instructions set the X register to 0, push that value on the new stack, and pull it to the
; Direct Page register immediately.
$C0:0027: DA ; PHX, the reason why they have to do this is because the accumulator is 8 bit and the direct page register is 16 bits.
$C0:0028: 2B ; PLD
$C0:0029: 7B ; TDC, and now they set A to that value.
$C0:002A: 48 ; PHA, now this makes sense. The DBR register can be directly set only via pulling the value from stack ...
$C0:002B: AB ; PLB, ... which is done here. So all relative accesses should be directed to bank $00.
$C0:002C: A901 ; LDA #$01
$C0:002E: 8D0D42 ; STA $420D, and again the hardware registers are set ...