對于@八百里的烈焰SMB1改版的修復過程

(文章由SKY2008_233 UID:402759619原創(chuàng))
黃色字體為21.9.21晚補充內容。
深藍色字體為21.11.27晚最終補充內容。
首先送上ROM:
鏈接:https://pan.baidu.com/s/110ukUojcZIwq7SwyRYYUrg
提取碼:hack
通關視頻:

作者原專欄:

下面則是我的修改過程
材料:FCEUX(UP用的2.2.0.2776)
建議沒有基礎的先看后面的修改
(可能需要一定的匯編基礎,可見下面視頻)

有不懂的點可以和UP主私信!周末一定會回!

FCEUX修改工具使用教程:
直接修改字節(jié)碼來該指令需要查詢6502匯編表,比較麻煩,為了提高效率,F(xiàn)CEUX自帶調試+寫入程序的功能??梢詮闹鹘缑嬲{試-調試器進入界面。

界面中,左側是顯示文件指令反匯編后的內容,右側顯示了程序實時運行的各個數(shù)值,可以添加斷點和書簽。改指令時,可以輸入右上的“搜至”按鈕右側地址,然后按下按鈕,便可定位到指令處。要對指令進行修改,可點擊指令左側的小格(圖中涂紅),會彈出來行內匯編器。

在上方方框中輸入指令,然后按下回車,點擊提交,即可完成更改。然后再調試-十六進制編輯器中點擊文件-保存ROM,即可存入文件。

一、轉移刺的程序
原來的改版中,4-1城堡音樂及其詭異。據(jù)作者自述,這段音樂是因為刺的程序在這里,自$FBA4-$FBF1,總計78字節(jié),是按照MMM的程序修改而來。(下面黃色字解釋原因)改版中沒有水關,則可以用水關音樂來替代這段程序的位置。

觀察這段程序,由于把地址寫全的指令只有JMP和JSR,BEQ等B開頭的跳轉指令(如BEQ $FBBB),而所使用的的JMP、JSR除了$JMP $FBD0都是跳轉到正常程序的位置($8000-$F90C)(JSR $2026后面會講),則可以直接將整段程序復制到水關音樂的位置,改變$FBD0的值即可。
BEQ等B字開頭的指令屬于分支跳轉,類似于高級語言中的if語句,因為這里跳轉是指“跳轉到此指令后的第幾個字節(jié)”,所以可以在移動位置后不用修改。
那么問題來了,如何確定水關音樂的位置?這里就需要講一下了,在游戲內,BGM的觸發(fā)器地址為$FB,輸入01,02,04,08分別是地上,水下,地下,城堡,而音樂數(shù)據(jù)會通過LDY $F7/$F8/$F9;JMP($F5),Y讀取,則可以得出,$F5所寫的地址就是音樂數(shù)據(jù)地址。我們只需要直接對$FB寫入02,看$F5、$F6的值就行了。
將02寫入$FB我們發(fā)現(xiàn),$F5和$F6依次為52 FD。FC中,地址是要倒著讀的,也就是說,實際指向的地址是$FD52,這就是我們要找的地址。但這還沒有完,不要急復制代碼!
我們需要確保水關音樂長度足夠78個字節(jié)。如剛才所說,$F7,$F8,$F9作為音樂的偏移值,它們的值加上$FD52不會超過音樂范圍。78用16進制表示是4E,也就是說,$F7-$F9中有任何一個數(shù)值在$4E及以上,空間就是絕對夠的。
顯然,音樂剛開始,$F8就是7X,$F9甚至是$AX,遠遠超過$4E,所以空間是完全夠的。這一步雖然現(xiàn)在很簡單,但是在其他實例中就不一定了,需要檢查。
也許對于很多人,城堡音樂是循環(huán)且周期短的,占地空間可能不是很大;但是只要一仔細聽,你會發(fā)現(xiàn)這是快節(jié)奏的曲子,按樂譜儲存會有很多音符疊加,導致最終音樂數(shù)據(jù)很大。
現(xiàn)在,我們將$FBA4后的程序整個復制到$FD52,將$FBD0改成$FD7E??梢粤藛??還沒有。我們需要將程序的入口也進行更改!打開FCEUX自帶的十六進制編輯器,打開編輯-查找,對程序的幾個入口進行搜索。我們要改哪幾個入口呢?這就需要一定的讀程序能力了。
一般情況下,我們以RTS來切分程序,因為RTS是程序的結束處,再往后就是另外一段程序了。通過這個方法,我們可以確定原來的入口至少有$FBA4,$FBB6,$FBBB,$FBC0,$FBD0。分別查找這幾個入口(地址全要倒著寫,比如$FBD0就是D0 FB),看前面是不是4C或是20(即JMP和JSR);如果不是,可以往前翻,看能不能在120字節(jié)內找到JSR $8E04(20 04 8E);如果還找不到,那就說明這不是跳轉地址,接著往下找。查找要這樣重復幾次直到ROM到$8010后或內存的$FFFA前。
以下是查找后結果:
$DFA9:4C A4 FB????$F95D:FB A4(舍)
$DFAC:4C B6 FB
$DE05:4C C0 FB
此外都不是跳轉位置。
所以,我們將這三處指令分別改成JMP $FD52;JMP $FD64和JMP $FD6E,測試一下實際效果。我們只需要看人撞刺能不能正常去世,如果可以,就說明修改成功了!
當然,修改時出BUG是很容易的,如果你改后出現(xiàn)了下圖效果:

請檢查你的地址是不是哪里輸錯了,以及是否保存ROM。特別是程序里的那個啊JMP $FBD0一定要記得改?。ㄖ拔揖褪前?FD7E寫成$FE7E就崩潰了然后找了很久問題)
可能你會問:$FE7E怎么會崩潰呢?其實$FCxx往后一片基本全是音樂,而這本來就不是代碼,指針移動到這里很可能會引起不可預知的錯誤。比如往后走的$FE96是字節(jié)22——JAM!因為除了#$A2,所有以十六進制2結尾的指令碼都是JAM,指針一碰就死循環(huán),然后絲毫不動,游戲卡死。JAM的出現(xiàn)率高達12/256(4.69%,不要小看),加上一些異常的JSR,JMP,這種數(shù)據(jù)區(qū)而非代碼區(qū)可以說是很高概率會崩潰。
別忘了修改初衷,讓城堡關音樂回歸正常!而我們只要從原版里復制$FBA4-$FBA1的數(shù)據(jù)到這里的$FBA4,就可以了。別忘了保存ROM!
為什么要提到這個JSR $2026呢?2026所在的區(qū)域是顯存區(qū),這一部分是不可能塞代碼的,所以在程序里出現(xiàn)這個,要么是修改數(shù)據(jù)殘留,要么是特殊數(shù)值。在這里,因為是按照MMM修改的,就有了差異,MMM在這里是3個FF,即不會用到的代碼,所以是修改殘留,就不需要管它。

二、設置刺猬速度
1-1關尾有三只刺猬,但是有可能它們都會跑走,也有可能一個光速刺猬飛過來把你鯊了。這其實是因為敵人槽的數(shù)據(jù)殘留X軸速度。正??鞓吩苼G下來的刺猬速度應該是F8/08,而在敵人槽空時,它們X速度都是00,即靜止。這一次,我們找到存這些數(shù)據(jù)的地方需要一個文件,Memory map,內存表。下載地址:https://pan.baidu.com/s/1gdxkBJl 文件名就叫MemoryMap.txt
通過查閱,我們可以發(fā)現(xiàn),$16-$1A存的是敵人ID,而$58-$5C存的是X軸速度。走到關卡加載出兩個敵人的地方,我們能看到$16,$17為00 1D,即綠烏龜和火棍,$58在F8和08來回切換,$59則一直在快速變化,即火棍速度有特殊值。到了關卡后部分,我們發(fā)下$59定下來了,加載刺猬后發(fā)現(xiàn),$16-$1A全填充了12,即刺猬,一會$1A又成了11,即快樂云,$5C被重新設置。這時,我們點擊調試器斷點區(qū)的“添加”按鈕,按下圖設置數(shù)值。


先將這兩斷點雙擊,關閉斷點,然后重置,開始游戲,打開兩個斷點。
跑了一段路程后,游戲暫停,調試器窗口彈出,在暫停兩次并繼續(xù)后,程序對斷點$CABF和$CAC5成了每一幀執(zhí)行。



然后,關閉斷點,重置游戲,手動設置$075B的值為02(重置后游戲會從第2頁開始讀?。?,開始游戲,開啟斷點,往前跑,游戲再一次暫停了。


注意到了嗎?$58沒有初始設置,就被直接進入每幀設置程序了,也就是說,$58留的還是殘留X軸速度。那么這是在哪里產(chǎn)生的呢?
重新檢測加載烏龜設置$16時的程序,發(fā)現(xiàn)了一個之前提過的東西:JSR $8E04!

為什么要強調這個程序呢?你仔細閱讀可以發(fā)現(xiàn),其實它的作用就是跳轉到這個JSR后面程序的地址,這里相當于是“跳到C282起第Y個地址(從0開始數(shù))”。這里的Y是$16的值,即敵人ID,所以我們知道,敵人程序就是在這里跳轉的!
按照我們讀出來的邏輯,我們找到直接加載刺猬的地址,即C282后72個字節(jié)指向的地址(一個地址有2個字節(jié)),即C2A6指向的地址C7A0。
通過幾次調試,我們發(fā)現(xiàn)這個$C7A0并不直接指向那個每幀的程序,還要再過幾次跳轉才到。既然每幀的程序對先前的X軸速度做處理,那么我們在其前寫一段代碼,先改變X軸速度即可。
那哪里有這個空間呢?因為這個改版用到的地圖數(shù)據(jù)很少,只有1-1~4-1四個關卡,我們可以把用不到的地圖數(shù)據(jù)寫一段代碼。
這就需要一些關于關卡的常識了——每一關都有一個獨立的空間號,存儲在$0750。通過檢測,我們發(fā)現(xiàn),整個游戲區(qū)間,$0750只有25,26,C2,C0,60這幾個可能值。添加$0750寫入斷點,我們可以得到,關卡指針在$E7-$EA,可以手動鎖定$0750為20(為什么是20?看帖子吧,其實在一定范圍內都可以,但千萬別搞個66,75什么太大的數(shù)值),然后查看$E7的指向地址,為$A46D,我們在這里寫一段程序:
LDA #$00;
STA $58,X;
JMP $C7A0;
我來解釋一下這段程序。意思是,令 X軸速度為00,然后跳至刺猬程序。X保留的是此時是幾號敵人槽,因此所有刺猬的X軸速度都會為0!
最后,我們把剛才$C2A6從A0 C7改成6D A4即可,保存ROM!
此時,1-1的三只刺猬都永遠是不動的了!
此時,你可能注意到,可能有的刺猬朝向不同。如果我們要把朝向統(tǒng)一,滿足強迫癥該怎么做呢?
朝向是另外存儲的,在內存46-4A,1向左,2向右。我們只要在JMP $C7A0前多寫一段:
LDA #$01
STA $46,X
這里就是將這個朝向鎖死成左了。
不過有一個特性,就是快樂云會出來在4號敵人槽。即$1A值會改成11。同上,我們添加$1A斷點,對$1A值改成11的指令全改成NOP無操作,這樣就不會有快樂云了!

不要忘記保存!
其實這種取消快樂云的方法有點治標不治本,因為很可能敵人的其它參數(shù)會被順道修改,可能就看著一只刺猬突然緩慢移動什么的...這里提供一種原作者私信我的方法:
在地圖后面加一個“停止持續(xù)的對象”,快樂云來了也會筍尖走掉。

三、小型修改:只顯示大關號
應該知道的知識:大關數(shù)值存儲在$075F,挺多人都知道的。
其實要改的根源是在顯示上,那我們的斷點就要寫在PPU上。打開命名表(調試-命名表),可以看到這樣的畫面:

我們把鼠標放在上半部分的1-1,可以發(fā)現(xiàn),這三個字符PPU地址分別為2073,2074,2075。在斷點區(qū)加一個PPU里$2073的斷點,在游戲開機時打開。下半部分不一定可以,不要選錯了
可見下文關于命名表的介紹,SMB1里4個屏上下完全相同,$2000-$27BF與$2800-$2FBF完全一致,但是不確定后半部分的寫入是否有效

這是一種情況。此時用的是STA寫入數(shù)據(jù)的(PPU不能直接尋址,要寫好PPU地址$2006和數(shù)據(jù)$2007),而A是24,明顯不是1-1中的1。(#$24其實就是空格,也就是-1關左邊那個圖塊)

此時的A是01,即顯示1.就是這個時候!但是這是公用程序,不能直接改。往上翻,發(fā)現(xiàn)A是讀取$0312得來的。所以我們可以添加$0312的斷點,在重置時執(zhí)行。

這里的0312是大關號+1存儲的,即實際的大關號,因為存儲大關號是減1的。同時,這里0312-0314是一并寫入PPU的,所以只要把這個地方改成0313,0312和0314寫入空位24就行了。即0304,X改成0305,X,然后寫LDA #$24;STA $0304,X;STA $0306,X。
另外一種修改方法,可以看我最下面發(fā)的鏈接,最新的那個帖子里有
還是詳細解釋一下吧
僅僅是SMB中,$300-$3FF的可用255個字節(jié)可以稱為“PPU寫入?yún)^(qū)”,寫入這里的數(shù)據(jù)會每幀以子程序(即上文的公用程序)發(fā)送到顯存區(qū)顯示圖案。$300是一個寫入指針,代表現(xiàn)在寫入?yún)^(qū)的指針在哪里,在第幾個字節(jié)。寫入?yún)^(qū)的格式為:
寫入地址(2字節(jié))+寫入數(shù)據(jù)的個數(shù)(1字節(jié))+寫入的數(shù)據(jù)+#$00結束字符
這里的地址牽扯到一個叫“命名表”的地址,就是卷軸。每個卷軸圖塊都有一個對應地址,一行32個圖塊,一屏992個圖塊共四屏,圖塊映射地址為PPU內的$2000-$27BF,$2800-$2FBF,調色板為$27C0-$27FF,$2FC0-$2FFF。寫入地址就是PPU內的對應地址,并且一反常態(tài),正著寫($2000寫出來就是20 00)。這里,"1-1"的寫入字節(jié)如下:
20 73 03 01 28 01 00
綜合上面內容,不難發(fā)現(xiàn),第四個字節(jié)和第六個字節(jié)分別由$75F和$75C讀取而來,即大關號和小關號。理論上講,如果只覆寫中間那一位,可以直接這樣輸入數(shù)據(jù):
20 74 01 XX 00
XX為大關號。這樣直接少了2個字節(jié)
每關,開始前的X-1如法炮制注意開啟斷點時間,在按下開始后幀進一步再開斷點
現(xiàn)在引出的問題是,二周目選關時,標題界面的大關數(shù)會顯示錯位,在1的左邊。前面提到的,$2006是一個PPU偏移值,顯示時應該為74,但此時為73,所以要做的就是在此前將73寫入$2006改成74寫入。
最后有一個可選修改:二周目選關會選到5-1~8-1,不應該選到的關卡。先給$075F一個斷點,當07FC(周目數(shù))為01時按B就會暫停:
(一個有趣的事實:$7FC不管你打通游戲幾次,都是#$01,但是有關程序代碼全部寫的都是讀取后用BEQ和BNE是否為0來判斷,完全沒有用到#$01這個值,也就是說,完全可以把打通游戲的LDA #$01 STA $7FC換成INC $7FC)

這里其實是一個子程序,因為續(xù)關時也會執(zhí)行,所以不能直接改。(為什么這么清楚?因為這就是盜版0-1的成因之一,可以看最下面鏈接里有一個帖子)注意堆棧區(qū),最頂上兩個就是返回地址,即$82A9是返回地址。82A8是JSR指令的最后一個字節(jié),下一條指令就是地址加1

網(wǎng)上翻一點,發(fā)現(xiàn)這里的值是+1再與07作與運算。什么意思呢?就是保留二進制中的后三位,00~07。而因為這個改版只有四個大關,所以可以把這個AND #$07改成AND #$03,就是取4的余數(shù)。
如果不是2的次方數(shù)關,可以這么寫:
CMP #$XX(關數(shù))
BCC #$02
LDA #$00
BCC后面那個數(shù)在FCEUX里要在機器碼那個字節(jié)改
保存!保存!保存!重要的事情說三遍

非常感謝你能看到這里!
如對文中任何內容有什么疑問,歡迎私信UP主,會在周末時間統(tǒng)一回復。
另外,關于FC技術的論壇、提問地可以見:
https://tieba.baidu.com/f?kw=%E6%BA%A2%E5%87%BA%E5%85%B3%E5%8D%A1&ie=utf-8(有很多你想知道的東西!極其高質量)
