[心得] RMX1的防盜機制: 人心終究是要回到公路的
本文極長,極生硬,強烈推薦讀好讀版:
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;
| ---- | ---- | ---- | ---- | ---- |
| BA5 | BA4 | ROM0 | ROM1 | 選擇 |
| ---- | ---- | ---- | ---- | ---- |
| Low | Low | Low | High | ROM0 |
| Low | High | Low | High | ROM0 |
| High | Low | High | Low | ROM1 |
| High | High | High | High | NONE |
| ---- | ---- | ---- | ---- | ---- |
https://hackmd.io/_uploads/rkZfXF8qR.png
特別需要注意的是,
如果是 $30 - $3F 這個記憶體位址區間,
BA5 和 BA4 會同時為 High,
那麼 ROM0-/OE 與 ROM1-/OE 就會同時為 High,
沒有任何一個被啟用;
這樣會發生什麼事呢?
以超任的設計來說,這個情況被稱為「OpenBus」:
如果沒有任何 ROM 被啟用,讀取的行為會失敗;
但由於讀取的過程中有個暫存器,
如果這次讀取沒有成功,
暫存器的數值就不會被改變,
因此他應該要拿到「前一次讀取」的資料。
回到記憶體映射的角度來看,
這個時候 $00 - $1F 會對應到 ROM0,
$20 - $2F 會對應到 ROM1,
$30 - $3F 什麼都沒有對應到,
就形成了 OpenBus 的情況。
near.sh 的討論中有提到,
開發版本的電路板為了開發方便,
會選擇使用可以重複燒錄的 EPROM;
他認為這應該會是個有四個插槽的電路板,
但對於 1.5 MB 的遊戲,
他需要插上三個 512KB 的 EPROM,
留下一個空的插槽。
但不管有沒有接上 EPROM,
位址就會對應到這個插槽,
因此不會發生 74LS00 把後 512KB 的內容重複一次的情況。
很可惜這邊我沒有證據他拍的電路板就是 X1 開發時使用的型號,
無法保證這點是否合理;
但 near 把「鏡射到 $30 - $3F」這個部分在模擬器中取消掉,
同時確保正確的 OpenBus 行為之後,
就能正確的模擬以跳線修正後的遊戲內容。
因此我認為這個說法是完全自洽的。
## 日文雜誌的接法不太一樣?
1994年4月出版的「バックアップ活用テクニック」 Part 36, page 107
有一篇丹治佐一寫的
「プロテクトバージョン!? ロックマンXの秘密」,
其中有提到這個電路,
但...他描述的連接方式和上面討論的有點相似,
卻有些微妙的不同。
特別是仔細看一下白色跳線的接法,
這根本不是第三根針腳。
https://hackmd.io/_uploads/rkCGHQL5A.png
仔細看一眼他的插圖,會發現...
這張電路板最左邊的 CIC 晶片的位置和前面我們看過的不同。
這張電路板其實不是 SHVC-2A0N-01,而是 SHVC-2A0N-11。
這邊有一張拍得更清楚的 SHVC-2A0N-11 照片,
可以看到他上面的線路確實很不一樣。
為了方便與正面對照,我有故意把背面照片左右相反,
但雜誌中是沒有這樣做的。
從正面可以看到黃圈圈起來的地方有個人工切斷的痕跡,
這件事情和 SHVC-2A0N-01 在背面的切斷如出一轍;
相同的,白色跳線其實是在讓那個切斷的地方重新接上。
白色跳線與電阻接上 74LS00 的部分,
數針腳可以看出這是 74LS00 的第六根針腳,
所以左邊電路圖接上 1K歐姆電阻的地方就是 6 號針腳;
回去對照雜誌上的電路圖,
確實他會是這樣連接的。
如果認真的去看每個針腳的定義,
確實都有符合雜誌下方的電路圖的接法。
也就是說,兩個討論的電路接法應該都是正確的;
他們的差異其實就是電路板的走線原本就有所差異,
而它們同樣的透過跳線/電阻/二極體的組合,
使 BA5 和 BA4 會同時為 High 的時候,
兩個 ROM 都不會啟用。
https://hackmd.io/_uploads/ByInnT8c0.png
## 透過 bsnes 手動調整記憶體映射
大部分模擬器的模擬目標是 ROM,
許多 ROM 確實也會在標頭區塊記錄一些卡帶的相關資訊,
例如超任的 ROM 有記錄這個檔案的大小、
這需要超任用什麼模式執行 (例如 LoROM 或 HiROM)、
這張卡帶有沒有 SRAM 提供存檔空間、
以及這張卡帶上有沒有副處理器 (例如洛克人玩家們熟悉的 Cx4)。
由 near 主導開發的模擬器 bsnes,
目標是盡可能提高超任模擬的準確性,
但很快的就發現 -
超任卡帶常常有些奇妙的設計,
像是特殊規格的電路板,
像是這次提到的奇妙的跳線。
非常明顯的,這些當然不會被記錄在 ROM 之中,
所以模擬器不太可能只從 ROM 提供的資訊就做到正確的模擬;
near 對此選擇的應對方式是,
他建立了超過一千兩百張卡帶的資料庫,
遇到每個 ROM 的時候:
(1) 先搜尋資料庫內有沒有記錄他的卡帶的相關資訊
(2) 如果沒有的話,看看 ROM 附近有沒有記錄資訊的相關檔案
(3) 上述都沒有的話,從 ROM 的標頭區塊去猜出來
這邊要特別提到的是 (2)。
bsnes 使用自行開發的 .bml 格式進行這些記錄,
是個輕量的標記式語言。
雖然如(1)所述,他資料庫內對於我們現在討論的初版洛克人X的 ROM 是有記錄的,
但我們還是姑且用 (2) 的格式來說明
SHVC-2A0N-01 與 初版洛克人X的 ROM 的差別,
應該會比較容易看懂。
一般的 SHVC-2A0N-01 的電路板,
上面不會有SRAM (那個2A0N的0表示沒有SRAM),
他的內容應該會是:
board: SHVC-2A0N-01
memory
type:ROM
content:Program
map
address:00-3f,80-bf:8000-ffff
mask:0x8000
map
address:40-7d,c0-ff:0000-ffff
mask:0x8000
這裡面記錄了電路板的名稱,
以及 ROM 對應到的記憶體映射。
但是對於初版洛克人X,
他有另外處理了跳線修正過後的電路版:(他在資料庫內的寫法不太一樣,
但這應該是等效的)
board: SHVC-2A0N-01#A
memory
type:ROM
content:Program
map
address:00-2f,80-af:8000-ffff
mask:0x8000
map
address:40-6f,c0-ef:0000-ffff
mask:0x8000
電路板名稱中的#A用來區分這是個有跳線修正後的版本,
而其中的記憶體映射也很明顯的刪去了
Bank $30 - $3F 與相關鏡像的映射,
用來表示跳線修正過後的結果。
但如果我們想要自行調整映射的部分,
或者甚至想要幫他加上原本不存在的SRAM,
這能夠做到嗎?
答案是...可以!
這邊選擇內嵌 bsnes v115 核心的 Bizhawk 2.9.1 進行實驗。
他沒有 bsnes 的資料庫,
不會受到 (1) 的影響;
同時他內建的 bsnes v115 核心能夠在讀取 ROM 的時候,
嘗試讀取同位置同檔名的 .bml 檔案,
試圖從裡面取得卡帶相關的資訊。
因此我們可以在這個 .bml 中遵照格式自己添加自己想要的映射、或是SRAM區塊。
舉例來說,
目前我們討論的初版洛克人X的ROM Rockman X 1.0(J).sfc 他沒有 SRAM,
但我們可以手動幫他加上去。
這需要在他旁邊擺上治作一個記錄卡帶資訊用的 Rockman X 1.0(J).bml:
game
sha256: 76f80cdf704a0e1daf1af5bbf564e427b425a5ee42329417de6f29219fe63e5f
label: ロックマンエックス
name: Rockman X
region: SHVC-RX
revision: SHVC-RX-1
board: SHVC-2A0N-01#B
memory
type: ROM
size: 0x180000
content: Program
memory
type: RAM
size: 0x2000
content: Save
name: save.ram
board: SHVC-2A0N-01#B
memory
type:ROM
content:Program
map
address:00-2f,80-af:8000-ffff
mask:0x8000
map
address:40-6f,c0-ef:0000-ffff
mask:0x8000
memory
type:RAM
content:Save
name: save.ram
map
address:70-7d,f0-ff:0000-ffff
我在 board 標上了 SHVC-2A0N-01#B 這樣的名字,
用來跟原本的做出區隔。
這樣寫下來的 .bml 就能夠讓 bsnes v115 核心
認定這裡應該要有一塊 SRAM,
而且也一併指定好他需要的記憶體映射。
拿這組配置下去遊玩,
就會觸發 TCRF 提到的三個計數器相關的機制。
## 防盜機制 - 不應存在的SRAM
理解了跳線修正如何改變了記憶體映射之後,
我們終於可以回來看看最前面提到過的TCRF 的敘述
https://tcrf.net/Mega_Man_X#Copy_Protection
其中有三個與計數器相關的部分:(1) 128次爆炸 (2) 128次掉落 (3) 128次受傷。
TASVideo 網站上 HHS寫的討論中,
https://tasvideos.org/Forum/Topics/558?CurrentPage=15&Highlight=384683#384683
他有把牽涉到的記憶體位址都有明確的寫下來,
其中這三條分別指向一個 SRAM 位址的檢查;
如果嘗試寫入那個地方後,
讀取同一個位置得到了剛才寫入的資料,
就相信這個位置能夠寫入,
是正版卡帶不應該存在的 SRAM,
因此觸發防盜機制。
這個作法很巧妙,
事實上後兩條計數器也確實的讓許多磁碟機玩家體驗到了異常困難的遊戲過程,
讓大家在討論 X1 的時候,
每個人各自提到的奇怪體驗常常不太一樣。
但很不巧的,
爆炸計數器相關的位址檢查有所瑕疵,
沒有注意到記憶體映射造成的問題,
因此讓製品版也會在預設的記憶體映射下觸發這個問題,
因此必須用跳線/電阻/二極體的組合去阻止錯誤的 $30-$3f 映射。
好,上面是我們已經知道的部分。
但我們有辦法驗證這個部分嗎?上一節我們提到,
bsnes 核心能夠透過 .bml 自訂電路板配置,
我們也同時描述了一組「加上 SRAM 的初版 X1 卡帶」的紀錄方式。
因此如果用 Bizhawk 2.9.1 + bsnes v115 核心啟動他,
就會發現遊戲的過程中有上述 TCRF 提到的現象。
但這個寫法不夠具體,
所以這邊利用 Bizhawk 監控記憶體的功能,
直接把對應的記憶體內容顯示出來。
觀察每個計數器的行為,
我們能夠得到以下觀察:
1. 爆炸計數器 (0x001F9D) -128 ~ 127 (2021年的文章提過)
- 每次發生一次爆炸就會+1,
Boss等大型敵人可能會爆炸很多次。
- 在爆炸計數器為負值時,會觸發以下效果
- 大補落地後 6 楨就會消失
- 衝刺豆砲會立起「序關旗標」(0x001F9B),
紀錄「序關尚未完成」,
以任何方式離開關卡之後就會回到公路
- 由於序關是否完成這點有紀錄在密碼表中,
所以這個狀態下產生的密碼會產生矛盾,
會被判定錯誤密碼。
- 所以只要全程使用特武就都不會觸發,
不要拿腳也不會觸發
- 拿了腳部又沒有特武的話,只好默念三遍,
「衝刺不攻擊,攻擊不衝刺」
- 「非衝刺狀態下的豆砲打中堅硬物件而反彈」會立刻離開關卡
- 如果這是個八大關卡,
同時 Boss 尚未打倒,
那麼會進入武器展示畫面
- 如果在武器展示畫面中,
看到 X 傳送下來的光束,就會判定 X 永久獲得這個武器。
八大頭目的生存狀態其實是看「持有武器」決定的,
所以拿到武器就算是打贏了 Boss。
- 只要全程使用特武就都不會觸發
- 在礦車上會無法起跳,所以拿波動拳的時候會相當麻煩
2. 落下計數器 (0x001F9E) -128 ~ 127
- 每次自由落體掉落時都會+1,
- 沒有 SRAM 的情況下起跳的時候會扣回來,所以很難看到。
- 到達 -1 之後不會變成 0,所以這個問題不會解除。
- 在落下計數器為負值時,會觸發以下效果:
- 接下來每次跳躍,
計數器偶數時跳躍高度會只剩一半,奇數時正常。
- 因此會一次高一次低,相當困擾。
- 好消息是因為他不會變回 0,
所以到那之後就都是正常跳躍高度。
- 接下來每次爬牆的時候都會扣一點血
- 老西城四一開始的高牆真的很高,爬上去會直接底血
- 閃電金剛關卡的中頭目 HP 大幅提升
- 無法搭乘機甲
- 我沒有檢查記憶體,不確定是不是跟開火有關,
但在這之後我每次接關都會回到關卡開頭。
- TCRF 的說法是「開火後會傳送回起點」,
我想這應該是指中繼點被取消。
https://hackmd.io/_uploads/S1zINjPcA.png
3. 受傷計數器 (0x001F9F) -128 ~ 127
- 每次受到傷害會 +1
- 似乎不只受傷會加,像是進入膠囊也會
- 鐵鷹的橫向龍捲會大量增加這個計數器
- 如果受傷計數器達到 -128 的瞬間,遊戲會開始自動隨機輸入
- 因為隨機輸入的部分包含暫停,所以會幾乎無法進行遊玩
- 所以能夠大幅累積這個數值的鐵鷹堪稱門神
## 防盜機制 - Bank $40 映射失敗
最前面提過的 TCRF 的敘述除了三組計數器相關的機制以外,
還有以下三條看起來與計數器無關的部分:
- 你可能會在開始關卡時失去所有升級(包括E罐)。
- 你在撿起強化道具或穿過Boss門時可能會被傳送回關卡起點。
- 當敵人掉落1up時,你可能需要重複初始關卡。
與之前相同的,
TASVideo 網站上 HHS 寫的討論中,
也有把這些部分牽涉到的記憶體位址都有明確的寫下來。
我沒有仔細檢查這些區塊的記憶體,
但我透過與前面描述很接近的方式,
手動在 .bml 中移除了 $40-$6F 的記憶體映射:
board: SHVC-2A0N-01#C
memory
type:ROM
content:Program
map
address:00-2f,80-af:8000-ffff
mask:0x8000
map
address:c0-ef:0000-ffff
mask:0x8000
透過這個方式,我取消了 $40-$6F 的記憶體映射。
打開遊戲之後輸入全裝甲+波動拳的密碼,
進入任意關卡,出現的確實是完全沒有裝甲的素身X。
這是最明顯的跡象;
除此之外,死亡後也會被送回關卡起點,沒有中繼點可以用。
後來也有被傳送回序關公路,
即使沒有腳部裝甲,根本沒有衝刺豆砲的可能,仍然是被送回去了。
雖然沒有逐項檢查,但我相信這個部分的幾條機制應該都有重現成功 -
除了E罐以外。是的,我手上有E罐,進入關卡並沒有讓他消失。
除此之外 - 波動拳密碼在這個場合,仍然打得出波動拳。
https://hackmd.io/_uploads/BJYrZovcC.png)
https://hackmd.io/_uploads/rydEZsP5R.png)
https://hackmd.io/_uploads/rkK3bsP9R.png)
## X1 的版本差異:防盜機制
洛克人X以 ROM 的標頭部分紀錄的版本資訊來分辨的話,
實際上總共有五個版本:1.0(J), 1.1(J), 1.0(U), 1.1(U), 1.0(E)。
雖然是回到公路這個明顯的現象最為有名,
但因為有跳線修正的 1.0(J) 通常也不會出現這個跡象,
因此目前比較實際的分辨版本的方式,
仍然是「集氣冰車打倒BOSS」─只有 1.0(J) 的冰車是不會碎裂的,
因此在逆襲的時候在空中用冰車打倒鐵鷹,
就有可能讓自己被移動的冰車給推下無底洞中。
其他的版本冰車都會碎裂,
因此不可能發生這樣的情況。
這時就有個有趣的問題─
如果我們直接給這些卡帶一塊 SRAM,
這些卡帶會觸發上述SRAM相關的的防盜機制、也就是三個計數器嗎?
如果我們拿掉這些卡帶 $40-$6F 的映射,
這些卡帶會讓我們在進入關卡的瞬間,就把裝備剝光嗎?
答案是...會!
實作的方式非常容易,
就是把上面的那個 .bml 的部分,
前面的 sha256 換成對應的 ROM 的 sha256 值即可。
底下的電路板相關資訊,
完全可以直接沿用。
因此可以推斷,
CAPCOM 在製作 1.1 以後的版本的時候,
確實有把前面的映射考慮進去,
因此不必再用跳線修正記憶體映射,
也能夠有正確的執行結果;
當然我無法藉此判斷他有沒有又在裡面裝上一條跳線,
但至少不會是用來改變映射。
## 其他模擬器的實行方式 - 直接修改ROM
看了上面這些透過 bsnes 核心去強迫他表現的防盜機制,
對於這麼慘絕人寰的設計,
對於這些嘔心瀝血試圖干擾盜版玩家的措施,
我相信在座的各位一定感到...
非常興奮,我在哪裡可以玩到這些有防盜機制的版本?
俗話說的好,洛克人玩家都是 M,或至少有一大堆都很 M,
看到這麼困難的機制,馬上就會有個問題─
「上面的有點複雜我看不懂,但這看起來很難,很有挑戰性,這我有辦法玩到嗎?」
可以。
只要你手上有一個洛克人X的ROM,不論版本,
但要確定他是 .sfc 格式
(而不是.smc,如果只有這個請自行尋找將 smc 轉換成 sfc 的方式),
直接找個 16 進位的編輯器(我個人推薦 HxD 或 Notepad++自帶的Hex Editor)
去更改一個 Byte,把 0x007FD8 的位置從 00 改成 08,
然後另存一個新的 ROM -
這個 ROM 就會有辦法在大部分 bsnes 以外的模擬器中表現出這個效果了。
(bsnes反而不能,得要真材實料的去寫 .bml。)
原理上這是更改 ROM 標頭紀錄的 SRAM 大小,
因此能夠簡單的觸發三條 SRAM 相關的計數器規則。
## 正面挑戰三個計數器!
自己打過個兩輪左右,
總之首先先下結論──
這遊戲確實能夠有系統的通關。
我目前有開計數器作為輔助,
但我覺得這熟悉之後可以不用開。
幾個比較需要注意的點大概是:
- 不能讓受傷計數器累積到128,相當於直接卡死
- 但只要沒有用到衝刺豆炮,序關旗標沒有立起,
那麼直接 Reset 超任會記得當前的密碼,幾乎不必擔心。
- 波動拳也能夠透過密碼繼承,
但是在輸入後確認的同時要按住 L+R+X+↓+START 這個按鍵組合。
- 附上八組可能的波動拳密碼,應該不管怎麼樣都是這八組。
如果不小心沒按好按鍵組合,沒繼承到波動拳,
那可以用這些對照調整一下密碼:
- 2653 3848 7587
- 2656 6418 3242
- 3673 2177 2487
- 3676 4627 5142
- 7431 3842 8523
- 7437 6412 1255
- 8441 2176 4423
- 8447 4626 6155
- 這遊戲最可怕的敵人應該是暴風鐵鷹,
他把人推飛的龍捲會大量增加受傷計數器,
我覺得這是不能迴避的。
- 目前覺得有機會解掉的方式只剩下,
貼上去波動拳一發帶走,
除此之外風險都太高了。
- 密碼不能繼承城內的進度,
所以城二中間開始就會面臨爬牆都會扣血的情況,需要小心控制。
打老西的時候特別明顯,不太有辦法正面打老西一階,
所以還是只好老樣子波動拳處理。
- 攻略的基本想法大概如下:
- 先參照2021年的上篇,利用爆炸計數器的效果,
直接透過豆炮反彈咒殺掉八大頭目
- 帶著特武去收集強化零件,
有需要就 Reset
- Reset 一次之後去收集波動拳
- 這中間很可能會遇到中繼點失效,
但問題不大,仍然能夠拿到波動拳
- Reset 一次之後進城,小心不要受傷超過 128 次
## 結語 - 人心終究是要回到公路的
2014年的12月,
我在 PTT Rockman 版發表了一篇
#1Ke19UXX (Rockman) [ptt.cc] [[心得] RockmanX初版回溯Bug研究+咒殺八大
https://www.ptt.cc/bbs/Rockman/M.1419776606.A.861.html)
那時我只是一時興起,想說這個回溯公路的現象時有耳聞,
想說「如果我們揪一群人找個愚人節一起來開個電視牆,
牆上每個人動不動就會被扔回公路去」,
感覺會是個不錯的節目。
當時我天真的以為這只是純運氣,
所以我應該要先實測一下節目有可能的長度──
也就是這大約需要多久才能夠打贏老西。
但為了保險起見,我好像應該也嘗試看看,
缺乏裝備的情況下能不能直接硬推進度的打下去,
如果可以的話 - 我猜我只要有身體裝備,
就能夠當個坦克人來應對大部分的情況。
然後我就赫然發現,我再也沒有被送回公路過了。
我當天第一次注意到,只要不拿腳就不會被送回公路 -
於是這個電視牆的意義就徹底的被我自己破壞掉了。
既然不拿腳就不會回到公路,
那麼牆上的玩家當然就會避免拿腳。
從那之後我就對初版洛克人X能夠引起的現象充滿興趣。
當時有看到的觀眾,有些人講出來他當初遇到這個現象,
有被送回公路去的,也有一些超過這個範圍的,像是什麼1up殺人事件之類的;
當時我實在是沒辦法重現出這些現象,
所以我非常懷疑實機到底會遇到什麼樣的情況,
卡帶會遇到什麼?磁碟機又會遇到什麼?
後來持續的有在注意這些相關的討論,
我慢慢地注意到有人宣稱這其實是一種防盜機制,
到後來看到了 near (當時還是 byuu) 的文章
討論了 bsnes 如何處理了這個部分 -
我真的看不懂,一點都沒辦法,我真沒想過要去看記憶體映射。
知道有這樣的事情,設法一點一點的多弄懂了一些,
非常緩慢,完全不知道何時有機會把這些串起來。
另一方面,我也持續的在推坑身邊的朋友來玩 1.0(J)。
在遊戲的選擇上,我自己特別喜歡
「操作上沒有這麼困難、但是有些出乎意料」的東西;
1.0(J)非常符合這個原則,我覺得這真的是滿有趣的。
非常感謝這些日子陪我一起玩 or 被我推坑跳進來玩的朋友們,
特別是告訴我「避免使用衝刺豆炮就比較不會被送回公路」的波紋 (ProwainK),
以及實際上在我面前打完一整輪,一次都沒有被送回公路的阿痕 (ds83171)。
我能夠確信這是衝刺豆炮造成的,並且獨立找出了爆炸計數器+序關旗標的位址,
完全倚賴上述這兩個結果。
再來,感謝 F6BFB5 這一路上的討論。
我看不懂日文,對於研究日版來說非常缺乏相關的情報:
有非常多日文相關的討論 / 上面的日文雜誌截圖 / 以及當年回收X1的全版廣告,
族繁不及備載的日文資料,全都是他告訴我的;
除此之外最重要的是上面模擬電路用的軟體,
也是他實際上拉好了一組電路出來,
我才知道有這樣的工具,愛不釋手,馬上解決了我困惑已久的許多問題。
另外特別感謝 XGOD 最近這兩個月,
XGOD 與我討論了非常多的 1.0(J) 相關研究內容,
讓我重新梳理了我所知道的所有的事情。
他的思維相當嚴格,時常準確地指出問題點,讓我發現我其實沒有完全弄懂,
所以摸摸鼻子回去多想一個晚上,找出能符合更多已知事實的解釋。
然後有時過個兩三天他又發現了一個矛盾之處,
所以我才會注意到前面想的這些不太正確...
但終究,在製作一開始提到影片之前,
我想我們確實的弄懂了很大部分的機制,
也盡可能消除了矛盾之處,達成一個自洽的說法。
這中間或許還有錯漏,畢竟我沒有非常細心,
我們兩個也都還是外行人,真沒辦法說很有把握這些都是對的。
如果有發現哪邊有明顯的問題,或是有看出來我們哪邊不懂,
而且知道這實際上應該怎麼運作的,還請不吝指教 -
如果真的能夠教會我那些我不懂的地方,
那真的是太好了,先行謝過。
行文至此,我們終於可以來問,
「如果我手上有一張跳線修正過的 1.0 卡帶,
我有辦法取消這個修正,讓他表現出未修正之前的效果嗎?」
這相當於我2014年想知道的問題。
從電路模擬的角度來看,我們模擬了跳線修正前/修正後的結果;
也因此,我馬上想到的就是 -
如果我把跳線 or 二極體拆掉,這應該要回到修正前的結果嗎?
是的。照這個模擬,直接剪掉二極體就會回到修正之前。
所以 XGOD 去剪了卡帶,確實的讓爆炸計數器生效,親手回到了公路。
在此重新感謝 XGOD,這個瞬間你確實的解答了我十年來的困惑。
最後感謝十年前的自己。
一直以來你都沒有放棄,持續的想著這個問題,
努力地學會了這些第一次看到的時候完全不懂的知識,直到問題被解決的這天。
辛苦你了,生日快樂。
是的,我選在今天把這整段故事總結下來,作為我自己38歲的生日禮物。
同時也感謝看到這裡的你。
這篇文章真的很長,非常艱澀,
不管中間跳過多少部分,幾乎所有的段落都仍然困難,
願意看到這真是辛苦你了。
## 給按 End 的人
(0) X1的防盜機制很精彩,
如果沒有耐心看這麼多字,可以直接看RMMH的這個影片
【電玩說書】你已成為正版的受害者 談洛克人X1離譜的防盜機制
https://www.youtube.com/watch?v=oL8X4Tb2Mb0
(1) X1日版初版的防盜機制有些問題,
會讓正版玩家莫名其妙被送回公路。
這有跳線修正,修正之後原則上不會發生 -
除非這個修正用的線路有另外出問題。
(2) 防盜機制有很多條,
「不應該存在的SRAM存在」與「不正確的記憶體映射」
分別會觸發更多不同的防盜機制,大幅妨礙玩家的遊玩過程。
(3) SRAM相關的懲罰機制意外的有趣,在這個條件下仍然可以有系統的破關。
(4) 「剪卡帶會變回有問題的初版卡帶」這個說法是正確的,
RMMH 有實際剪了錄下來。
但前提是這確實是 1.0 (J),
以及要剪斷的不是導線,而是旁邊的二極體。
以上,硬生生地趕上了...吧?
鴉片(Append), 2024.08.12
--
███◣ ◢██◣ ◢██◣ █ ◢█ ◣ ◢ ◢██◣ ◣ █
█ ██ █ ██ █ ██ █◢█◤ █◣◢█ █ ██ █◣ █
█ ██ █ ██ █ ██◤ ████ █ ██ ██◣█ @ ptt.cc
███◤ █ ██ █ ██◣ █◥◤█ ████ ████
█◥█◣ █ ██ █ ██ █◥█◣ █ █ █ ██ █◥██ 鴉片(Append)
█ ◥█ ◥██◤ ◥██◤ █ ◥█ █ █ █ ██ █ ◥█twitch.tv/append
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 59.127.47.181 (臺灣)
※ 文章網址: https://www.ptt.cc/bbs/C_Chat/M.1723478218.A.684.html
留言