本文極長,極生硬,強烈推薦讀好讀版:
https://hackmd.io/@Append/ByM82wnKA
## 開始之前
總之這篇的故事有很大部分跟 RMMH 的近期影片有關。
【電玩說書】你已成為正版的受害者 談洛克人X1離譜的防盜機制
https://www.youtube.com/watch?v=oL8X4Tb2Mb0
這篇是我在這影片背後的研究過程中的一些心得,
以及對於我們想驗證的許多邏輯的補完。
如果還沒有看過的話,強烈推薦先至少看過一次。
## 緣起
之前寫過一篇「RockmanX 1.0 公路詛咒的機制與迴避」,
裡面提到了當年 RockmanX 初版卡帶的一個奇妙的防盜機制,
讓無數的玩家莫名其妙的被送回公路。
我自己從2014年左右開始關注這個現象,
後來經過 ProwainK 和 Ds83171 以及 F6BFB5 的協力研究,
完成了上述這篇文章,
對這個現象的背景、成因以及應對方式有了初步的理解,
至少我們知道了「只要不要衝豆就不會出事了」。
我當時在那篇文章底下寫了一段「下集預告」,提到預計會寫
The Cutting Room Floor (TCRF) 與 near.sh
對於防盜機制以及跳線修正的相關記載。
然後...就過了三年。
我隔了這麼久都沒有把續集寫下來。
理由非常簡單,就是,
就是我看不懂RRRRRRR ◢▆▅▄▃崩╰(〒皿〒)╯潰▃▄▅▇◣
其他機制那邊我真的沒什麼好辦法,
畢竟我沒有想到怎麼觸發那些機制的手段,他其實也沒有寫下來。
我當時能想到的大概是...
去買很多不同的磁碟機說不定有辦法撞到幾個?
但這有點太過亂槍打鳥了,
實在是沒有信心能辦到。
至於跳線修正到底做到了什麼...
這我當時就有看到有人在討論那些電路,
有畫出詳細的電路圖,
但...我看不懂,真沒辦法,
我一個讀化學系的真的沒看過這些,臣妾辦不到阿。
於是到了前陣子 XGOD 做了一片
【電玩說書】洛克人X2卡匣為什麼當年非常稀有/沒有盜版?
其中有拍到 X1 的卡帶中的跳線,
那時有討論了一下有沒有辦法把這段講成一個比較容易聽懂的故事...
不是,講故事之前,我得先看懂RRR ◢▆▅▄▃崩╰(〒皿〒)╯潰▃▄▅▇◣
於是就開始了漫長的奮鬥。
我有種回到大學期中考前的感覺,
大概就是那種,老師上課的筆記,同學們每個都說沒有看懂,
我們開個讀書會大家一起努力一下...好像看懂了,
然後跟同學解釋,赫然發現這不對,我沒有搞懂,
只好繼續看一下...
然後花了兩小時發現我前面少寫一步,
或者發現這邊老師筆記寫錯了一個字,
改掉之後整篇就沒有矛盾了。
這些日子大概就這樣的感覺,
XGOD 常常很準確的挑出我的邏輯中的矛盾之處,
於是我就摸摸鼻子繼續想辦法弄清楚...
到了現在,新的這片影片總算是完成了
"【電玩說書】你已成為正版的受害者 談洛克人X1離譜的防盜機制"
這裡面有針對 TCRF 提過的每個防盜機制盡,
每個都盡可能地找出觸發的方式,拍下實際的情況;
也有簡短的介紹了 near.sh 提到的跳線修正的機制,
並且最後透過理解之後,剪掉跳線上二極體的部分,
成功的在實機上還原了公路的詛咒這個防盜機制。
但...作為一個影片,篇幅有限,
實在無法把所有的過程都寫上來,
就算是目前這個篇幅,30分鐘的YT影片已經非常漫長了。
實際上內容也確實偏難,
底下的留言幾乎也都是「雖然我看不懂,但看起來好厲害」。
不過還是有很多朋友很有勇氣,
比較想看到實際上中間我們怎麼驗證這些的過程,
所以在這邊另外再寫一篇,
紀錄一下我們說服自己的過程。
這篇文章接下來會討論以下幾件事情:
- 先回顧一下我們在這過程中找到的既有討論。
實際上我們有非常多的判斷都是直接從前人的敘述中得知的,
然後我們設法去重現這些。
- 這之中大部分的機制都牽涉到記憶體映射,
所以我們需要描述超任主機怎麼樣透過記憶體位址讀取到ROM中的內容。
- 實際上 7400LS 這張晶片到底做了什麼,
以及跳線怎麼修正他。
- 這裡面實際上有那些防盜機制,
我能夠用模擬器的環境有系統地重現嗎?
- 如果我觸發了這些的防盜機制,
這遊戲有辦法有系統的破關嗎?
## 文獻回顧
在寫上一篇的時候,當時我們就已經知道:
- TCRF 除了我們遇到的「爆炸計數器」以外,有提到更多的防盜機制,
例如「落下計數器」和「受傷計數器」。
每個都只各用一句帶過,很可惜沒有辦法看到實際上的效果。
- near.sh 有在文章中寫出 Capcom 在製品版中透過跳線修正了卡帶,
這很可能是因為開發版的記憶體映射方式和製品版不同,
因此模擬器需要手動修正映射才能避免這些機制的觸發。
### TCRF 記載的防盜機制
首先先翻譯一下 [TCRF 的敘述]:
https://tcrf.net/Mega_Man_X#Copy_Protection
- 在128次爆炸後,
能反彈子彈的敵人可能會在反彈時殺死你。
強化道具會迅速消失,
並且在衝刺時射擊會導致你需要重複初始關卡。
- 這其實就是我們已經知道的公路詛咒。
豆炮反彈會離開關卡,衝刺豆炮會立旗導致離開關卡後回到公路。
- 在掉落128次後,
你接下來的127次跳躍會在正常跳躍和小跳之間交替。
你還會在爬牆時受傷,並且無法使用騎乘裝甲。
開火會將你傳送回關卡起點。
- 雖然上一篇沒有提到,但這個我之前是有注意到的。
當然我並不清楚規則,但我有在Sigma Stage 4的牆上,
用 Joytokey 設定「連發→」來讓下滑變得更慢,
赫然發現我的HP會被扣下去。
- 當你第128次受到傷害時,遊戲會開始添加隨機輸入,
並且你的蓄力X炮射擊會被鎖定。
- 你可能會在開始關卡時失去所有升級(包括E罐)。
- 這個也是很有名的,很老的模擬器 ZSNES 1.2 會友這個現象,
不管什麼裝備都留不下來。
- 你在撿起強化道具或穿過Boss門時可能會被傳送回關卡起點。
- 當敵人掉落1up時,你可能需要重複初始關卡。
- 雖然這個也滿有名的,
但我有點難想像為什麼大家能判定出來是1up造成的...
TCRF記錄的這些現象,我們寫上一篇的時候就有注意到。
14年之後我很長時間都有在玩1.0J,
應該滿早就有看到TCRF寫下來的這些,
但當時只知道第一條爆炸計數器,
除此之外我都沒有弄懂,
但偶爾還是會聽到有人聊天的時候提到,
當年曾經遇到過類似的狀況。
很可惜大家都是遙遠的模糊記憶,
沒有人能記得細節,
所以也不知道如何考據。
不過直到最近我突然注意到,
TCRF 其實是有寫下 Citation 的。
- Source: HHS, TASVideos
https://tasvideos.org/Forum/Topics/558?CurrentPage=15&Highlight=384683
- Source: Original TCRF research
https://jul.rustedlogic.net/thread.php?pid=433506
TASVideo 的 HHS 寫的超詳細,
把所有牽涉到的記憶體位址都有明確的寫下來。
我想這多半是有直接看懂程式碼,
才能寫得這麼準確;
實際上也因為有這些位址,
我們才有手段去直接切入這些問題,
設法重現每一個效果。
另一篇更早一點的 TCRF 的研究中有寫下來一些現象,
而且還有發現這些現象的程式碼中有額外的自我檢查,
如果想把這些程式碼移除掉,
仍然會觸發同樣的防盜機制。
### near.sh 描述的跳線修正
當時我還在研究的時候,
有看到 Near (更多人可能更熟悉 byuu)
有在他的個人網站 near.sh 裡面寫下來一些模擬器開發的文章,
其中有提到 X1 卡帶中的跳線。
在 Near 過世之後,
目前這些文章有被備份到BSNES的網站 https://bsnes.org/articles
其中提到了:
- 洛克人X的卡帶中沒有存檔用的SRAM,
但大部分的盜版裝置 or 磁碟機都會有;
程式碼中會對SRAM區塊進行寫入檢查,
很可能就能判斷出這是不是盜版,
並且施加防盜機制作為懲罰。
- 這段寫入檢查的程式碼有些問題,
在製品版中遇到了不對的「記憶體映射」,
導致正版卡帶也會被誤判成盜版,
因而觸發防盜機制。
- 洛克人X的1.0版踩用 SHVC-2A0N-01 的電路板,
但他透過一條跳線更改了「記憶體映射」,
導致他的記憶體映射和其他同樣電路板的行為很不一樣。
- 一般模擬器在這種情況難以應對,
但BSNES在資料庫中紀錄了這件事,
每次遇到這個ROM的時候就針對性的作出正確的映射。
這裡提到的「記憶體映射」會需要更多一點點的基本介紹,
我們後面會對此做比較詳細的討論。
### 其他相關文獻
接下來附上幾張照片。
這來自於 Reddit 上的一個討論,
https://www.reddit.com/r/snes/comments/b3bsmp/bought_this_import_rockman_what_are_these_wires/eiztnu6/
他拍攝了電路板的照片,
同時標示了電路相連的部分,
以及將他們整理成電路圖,
分析上面外加的跳線與二極體的部份的實際效果。
我會在這之後的過程,
照著這個思路走過一次,
描述「74LS00實際上會做出什麼樣的記憶體映射」,
以及加上這個跳線修正之後的結果。
https://i.imgur.com/E64ie1g.png
https://i.imgur.com/XMIILq2.png
https://i.imgur.com/KKbwiwJ.png
除此之外,
日文雜誌「バックアップ活用テクニック」中有一頁提到了這個電路,
同樣的提出了解釋。
內容的接法稍微有點不同,
後面我們會一併去理解這中間的差異。
https://hackmd.io/_uploads/rkCGHQL5A.png
## 超任的「記憶體位址」與「映射」(Mapping)
超任的CPU要跟其他元件進行互動的時候,
會使用24位元的整數來形成一組「位址」;
前面提到的「記憶體映射」指的就是「把位址對應到特定的元件」的規則。
為了弄清楚這點,先介紹一下超任的位址格式。
超任使用的24位元位址,通常我們會用16進位來表示他。
這樣可以把他表示成 000000 ~ FFFFFF 之間的某個整數。
在大家的習慣中,我們把這六位數的最前面兩位的
$00 ~ $FF 叫做 Bank (習慣上會標記$表示Bank),
後面的四位 0000 ~ FFFF 叫做 Page (分頁)。
理論上,這每個位址都可以對應到一個 Byte 的資料;
所以 000000 ~ FFFFFF 這麼多組位址
實際上可以對應到 16,777,216 個位元組,
也就是 16MB 的資料。
但這裡面有很大部分要分給不同元件,
而且超任卡帶內容通常不怎麼大,
目前最大的應該是 6MB 的時空幻境 (Tales of Phantasia)
與星海遊俠 (Star Ocean)。
洛克人X比起來不大,只有1.5MB,
總位址量遠遠超過這個大小,
要指向所有的內容其實並不需要完整的24個位元 -
如果是要涵蓋這個大小,其實只需要 21 個位元就能達成。
從卡帶的角度來考慮,
實際上卡帶的針腳的定義我這邊引用
The SNES Cartridge, Briefly Explained
一文的圖片以及介紹:
https://mousebitelabs.com/2019/05/18/custom-pcb-explanation/
https://hackmd.io/_uploads/BJMVh4TKC.png
從照片中可以看到,
連接主機的針腳上其實是有編號的;
電路板背面(主機正面)的針腳編號是5~27,
電路板正面(主機背面)的編號是36-58,
兩面各有23個針腳,表格中有標示出每個針腳的功能,
中間的編號我就不太清楚會跑去哪,
但似乎還有另外一種電路板會有更多針腳。
考慮到每個針腳都能夠對應到 0 與 1 (也就是電位低與高),
他們表示的就是位元,
所以多個針腳就能表示出一個更大的整數。
- VCC, GND: 供電用
- VCC提供高電位,
原則上電路直接連上 VCC 的部份,
電位會是 +5V
- GND提供低電位,
原則上電路直接連上 GND 的部份,
電位會是 0V
- A0-A15, BA0-BA7: 記憶體位址,共24根
- A0-A15 這十六個針腳對應到的就是上面提到的分頁,
總共可以對應 0000-FFFF。
- 如果你偷看一下後面 X1 卡帶的照片,
他的40什麼都沒接上!
- BA0-BA7 這八個針腳對應到的就是上面提到的 Bank,
總共可以對應 00-FF。
- 同樣的如果你偷看一下後面 X1 卡帶的照片,
他的47/48什麼都沒接上!
- X1卡帶上總共只有 21 個針腳有接上電路
- 21位元能夠表示的位址數量的理論上限是2MB,
比 X1 ROM 的 1.5MB 大一些
- D0-D7: 資料讀取/寫入用針腳,共八根 (1 byte)
- /CART: Off (也就是電位為Low)時才能夠讀取卡帶內容
- /RD: 讀取開關
- /WR: 寫入開關
除此之外另外附上兩個只有ROM晶片的針腳才會標註的:
- /CE是 Chip Enable,啟用這個ROM晶片
- /OE是 Output Enable,接受這個晶片輸出的資料
- 實際上接下來我們遇到的都會是 /OE(overbar),
表示**低電位時才啟用**
接下來看一下電路板的晶片部分,
可以看到這張 SHVC-2A0N-01 的電路板上有兩個 ROM 晶片,
分別是 KM23C4001B (上) 與 KM23C8001B (下) ;
前者可以容納 512KB,後者可以容納 1MB。
這兩張晶片都是由 Samsung Electronics 製造的,
現在直接搜尋編號仍然能夠找到他們的 Datasheet,
上面有列出每個針腳的用途。
先偷偷計算一下,512KB = $2^{19}$ Bytes,
也就是說要描述 512KB 的資料需要 19 個針腳;
相對的,1MB 需要多用到一個位元,
所以可以用 20 個針腳完成。
在針腳的示意圖中,
1MB 的 KM23C8001B 的記憶體位址針腳編號從 A0 -> A19,總共20根;
相對的,512KB 的 KM23C4001B 少了 A19 這根針腳,
因此只用到 19 根。
這邊需要特別注意的是,
晶片的針腳命名,與電路板的針腳命名並不需要保證相同;
所以之後我們會在需要的時候特別標記,
這個命名是晶片的針腳、還是電路板的針腳。
https://hackmd.io/_uploads/BJ0C6gUcC.png
但從卡帶電路板的針腳來看,
電路板針腳 40 (A15) 其實什麼都沒有接上;
1MB 的 KM23C8001B 需要的 20 個針腳,
對應到電路板上應該會是 A0-A14 (15根) + BA0-BA4 (5根)。
很可惜我沒有辦法看到晶片底下的電路怎麼經過,
這部分他們怎麼連接我沒有證據,
但根據超任的 Memory Map 規則,
A0-A14這15個針腳對應到的應該會是位址的後面四位,
也就是「分頁」;
分頁能夠對應到的位址是 0x0000 - 0xFFFF,
這需要16個位元才能表示;
如果只有15個針腳,
實際上是不能對應到全部的。
實際上超任的 LoROM 模式也因此不會用到這之中全部的位址,
每個分頁都只對應到 0x8000 - 0xFFFF;
這相當於在這16個位元中的第一個位元固定都是1,
後面15個位元由針腳決定。
相對的,
Bank 的部份對應到的位址應該是 $00 - $FF,需要八個位元來表示;
晶片上的與此有關的針腳是 A15-A19,
這五根對應到電路板中的 BA0-BA4 這 5 根針腳。
但很明顯的,5 根針腳只能對應到 $00 - $1F 這 32 種可能,
考慮前面分頁是32768個位址,這樣剛好能夠表示1MB;
剩餘還沒有用到的三個針腳中,
電路板上我們能夠看到 47(BA6) / 48(BA7)這兩個針腳並沒有接上晶片,
最後剩下的 BA5 在這裡決定要控制這兩張晶片的哪一張,
因此這樣就能對應到 $00 - $3F 這些可能。
但實際上 KM23C4001B 在 Bank 的部份只有用到 BA0-BA3 個針腳,
BA4 原本就沒有接上他。
也因此總對應其實應該是 $00 - $3F;
其中 $00 - $1F 對應到 1MB 的 KM23C8001B,
$20 - $2F 對應到 512KB 的 KM23C4001B。
好,那麼現在就有個奇妙的問題 -
BA6 與 BA7 沒有接上,如果我們給他不同的位元,
會影響其他位元的運作嗎?
如果他真的沒有接上其他元件,
那不管他是哪個位元應該都不會影響。
也因此,
$00 (00000000)、$40 (01000000)、
$80 (10000000) 與 $C0 (11000000)
應該都對應到一樣的 Bank。
這件事情就是「記憶體鏡像」(Memory Mirroring),
因為 BA6 與 BA7 在這邊沒有接上元件,
自然的讓這四組位址對應到了同樣的區塊。
以下附上超任的 LoROM 模式的記憶體映射示意圖。
https://hackmd.io/_uploads/B12-7GL90.png
> 這裡其實有個還沒看懂的問題:
> - 根據上面這個邏輯,
> 電路板上同樣沒接上的 A15 應該會有同樣的效果,
> 讓所有分頁的部分 0x8000-0xFFFF 也鏡射到 0x0000-0x7FFF?
> - 前面引用的文章中有提到 A15 會接上一個 decoder,
> 最後應該要對應到 我無法在電路板上看出這件事情
> - 實際上 bsnes 最後給我 SHVC-2A0N-01 的記憶體映射裡面,
> $40-$7F 的部份對應到的分頁 0x8000-0xFFFF
> 也是有鏡射到 0x0000-0x7FFF
> - 也就是說,這個問題很可能應該要改問
> 「SFC如何決定一些位址不要映射到 ROM 的資料,
> 而是對應到其他元件」?
> - 這會同時回答為何超任能夠將 Bank $7E-$7F 對應到 RAM,
> 而不是 ROM 的鏡像。
## 記憶體控制晶片: 74LS00
SHVC-2A0N-01 上面使用的記憶體控制晶片是 74LS00。
這也能夠直接 Google 查到 Datasheet,
實際上他很單純,就是四個 NAND 閘。
他有十四個針腳,其中 VCC 和 GND 分別對應到 +5V 與 0V,
其他12根分別對應到這四個 NAND 閘,
每個閘各兩有兩個輸入與一個輸出。
https://hackmd.io/_uploads/BJY7GmUqC.png
在 SHVC-2A0N-01 這張電路板上,
我無法判斷出這些針腳之間在晶片底下有什麼樣的連接,
但根據 Reddit 上面的討論中,
有在上面這些照片上加上線路的連接方式。
看著下面這張照片,
可以注意到醒目的黑色的導線,
他一端接上了 KM23C4001B 的 31 號針腳(/OE),
另一端通過一個電阻之後接上了74LS00 的 3 號針腳。
但如果仔細看這個 3 號針腳,
在電路板上其實本來就有一條電路
直接走向 KM23C4001B 的 31 號針腳 -
但是在中間有個被切斷的痕跡。
為了避免看不清楚,
這邊附上另一張不同出處的照片,
並且用黃圈表示被切斷的位置。
假設跳線、電阻與二極體都是後來追加的部分,
那麼推測原本應該沒有這些,
也沒有那個切斷的痕跡 - 也就是說那邊應該要能形成通路。
https://i.imgur.com/XMIILq2.png
https://i.imgur.com/glUbH1M.png
在這個情況下,
我們能夠透過線上模擬工具重建這個情況。
前面有提到 /CART 為 Low 的時候才能讀取卡帶內容,
在這個情況下,
BA5 為 Low 的時候,
會讓 ROM0 的 /OE 為 Low (注意 /OE 是在 Low 啟用),
ROM1 的 /OE 為 High;
這個時候超任會讀取到的就是 ROM0 的內容。
相對的,如果 BA5 是 High,
ROM0 的/OE 為 High,ROM1 的/OE 為 Low,
讀取到的就是 ROM1 的內容。
但在這個時候,
由於 ROM1 只有 512KB,
BA4 實際上並沒有接上他,
所以不管 BA4 實質上是 Low 還是 High,
他都只會認得 BA0-BA3 指向的 Bank;
從記憶體映射的角度來看,
他做到的就是把 $20 - $2F 鏡射到 $30 - $3F
(以及考慮其他mapping後,
$60 - $6F 鏡射到 $70 - $7F,
$A0 - $AF 鏡射到 $B0 - $BF,
$E0 - $EF 鏡射到 $F0 - $FF)。
這樣看的話,
74LS00 預設的這個接法,
就會讓512KB 的 ROM1 重複表現兩次,
實質上表現得像是 1MB;
原本超過 1.5MB 到 2MB 之間的 512KB 位址,
實際上卻對應到了後面 512KB 的資料。
對於一個1.5MB的遊戲來說,
如果兩張 ROM 分別是 1MB 和 512KB,
那「鏡射後面的512KB」
就是 SHVC-2A0N-01 這張電路板上的 74LS00 預設的行為。
https://hackmd.io/_uploads/rkWDVSI5R.png
## 跳線修正:為什麼要外接一條醜醜的電線?
前面提到這張卡帶背面的跳線修正。
以前面這張照片為例,這有一個黑色導線,
一端接上了 KM23C4001B 的 31 號針腳(/OE),
另一端通過一個電阻之後接上了 74LS00 的 3 號針腳;
另外有一個二極體,
陰極的一端(有個黃色環標記)接上了 31 號針腳,
而陽極接上 30 號針腳
(BA4,但對 ROM1 來說原本應該是 NC,也就是"沒有作用")。
除此之外,有一個原本線路切斷的痕跡。
https://i.imgur.com/XMIILq2.png
考慮有可能的各種情況,
我們能夠重新製作出電路的模擬。
以下直接列出 BA5 與 BA4 分別為 Low 或是 High 的所有可能,
列出實際上他各自會讀取哪個 ROM;
|