再コンパイルなしでFCの『スーパーマリオブラザーズ1』の5-1の小さなお城を修正する方法

**FCの『スーパーマリオブラザーズ1』** には、各ワールドの第3ステージの終わりで、マリオが**大きなお城**に突入し、その後、お城を出たマリオが次のワールドに向かうという設定があります。この設定に従って（1-1を除く）、各ワールドの**最初のステージ**は大きなお城の前から始まるはずです。

しかし、**5-1**ではこの設定が崩れています！ 4-3の大きなお城を抜けたマリオが、**小さなお城から出て5-1が始まります**。

![](https://p.ipic.vip/kskroi.png)

[一説によると](https://tcrf.net/Prerelease:Super_Mario_Bros.#World_5-1)、5-1はもともと5番目のワールドの第1ステージではなく、後に何らかの理由で第1ステージとして使用されることになったものの、その際にスタート地点の小さなお城を大きなお城に置き換え忘れたということです。

そこで今回は、FC 6502アセンブリコードを**再コンパイルせずに**、この長年放置されていた小さなお城のミスを修正する方法を紹介します。

---

すでに多くの達人たちが『スーパーマリオ1』に様々な修正や改良を加え、その成果をGitHubで公開しています。例えば、以下のようなリポジトリがあります。

[https://github.com/Ribiveer/SMB-Tweaked/tree/main](https://github.com/Ribiveer/SMB-Tweaked/tree/main)

[Ribiveer](https://github.com/Ribiveer)というプログラマーは、『スーパーマリオ 1』に多くの微調整を加えており、その中には5-1の大きなお城の修正も含まれています。修正のソースコードは以下の通りです。

```asm
; https://github.com/Ribiveer/SMB-Tweaked/blob/28688edff5c9a2231327c1fed8353070396c7af9/data-levels.asm#L567

;level 5-1
L_GroundArea11:
.IF TWEAK_FIX_SMALL_CASTLES
      .db $92, $b1      ;set the header to "wall background"
      .db $0f, $20      ;increase height of castle
      .db $6e, $45      ;after 2 blocks of wall, we can go back to the snow theme
.ELSE
      .db $95, $b1
      .db $0f, $26
.ENDIF
      .db $0d, $02, $c8, $72, $1c, $81, $38, $72
```

修正のポイントは、5-1のステージデータの**最初の4バイトを調整し、さらに`$6e, $45`という2バイトを挿入する**ことです。これにより、大きなお城が現れるようになります。しかし、この修正によって後続のデータや命令のアドレスがずれ、`jmp`などで参照するアドレスもすべて修正しなければならないため、手動で行うのはほぼ不可能です。そのため、再コンパイルが必要となります。

---

では、**再コンパイルなしで**お城を修正する方法はあるのでしょうか？  

実は、**修正済みの5-1のデータをROMの末尾にある空き領域にコピー**することで、既存のアドレス配置を変えずに、修正を実現できるかもしれません。 あとは、5-1のデータの開始アドレスをその新しい場所に変更し、CPUが修正後のデータを読み込むようにすれば、問題なく修正が反映されます。

この方法は一見うまくいきそうに思えますが、実は**ROMの空き容量という大きな制約**があります。『スーパーマリオ1』のROM（PRG ROM）はわずか32KBの容量しかなく、すでに大部分が使われているため、**自由に使えるスペースはごくわずかしかありません**。

お城を修正した5-1のデータには**合計で65バイト**必要ですが、ROM内には**連続した65バイトの空き領域**が存在しないため、この方法ではデータを冗長に格納することができません。

---

では、どうすればいいのでしょうか？

実は、**FCエミュレーターのチートコード機能とゲームの仕様**をうまく活用することで、こうした問題を回避することができます。

具体的には、まず一連のチートコードを使って、**修正済みの5-1のステージデータを1-1のデータに上書き**します。次に、別のチートコードで**5-1のステージ読み込みアドレスを変更**し、CPUが1-1の場所から修正後の5-1データを読み込むようにします。

これらのチートコードは、**最初のワールドをクリアした後に有効化するのがポイント**です。というのも、マリオは一度**クリアした1-xステージには戻れない**ため、そのデータが上書きされてもゲームの進行にはまったく影響しないからです（二周目に入ったら、これらのチートコードは**無効にする必要**）。

今回は**67個のチートコード**が必要で、手作業での作成は大変です。そのため、**FCEUX**というエミュレーターの**チートコードファイル読み込み機能**を活用し、**Pythonなどのプログラミング言語を使ってチートコードファイルを自動生成**します。

1チートコードは1バイトを変更します。最初の65個は修正後の5-1ステージデータを格納し、最後の2個はそのステージデータの開始アドレス（読み込みアドレス、16ビット、2バイト）を変更します。

FCEUXのチートコードファイル形式は次の通りです：

- 1行には1つのチートコード
- チートコードの形式は `S<address>:<new value>:<comment>` 
    - `S` はプレフィックス
    - `<address>` はROM内のアドレス
    - `<new value>` は新しいデータの値
    - `<comment>` はコメント


Pythonを使って、この67個のチートコードを生成しました。

```python
import re

# cheat code file format
# S<address>:<new value>:<comment>

# https://6502disassembly.com/nes-smb/SuperMarioBros.html#SymVictoryModeSubroutines
level1_1_base_addr = 0xa68e
fixed_level5_1_area_data = """
;level 5-1
L_GroundArea11:
      .db $92, $b1      ;set the header to "wall background"
      .db $0f, $20      ;increase height of castle
      .db $6e, $45      ;after 2 blocks of wall, we can go back to the snow theme
      .db $0d, $02, $c8, $72, $1c, $81, $38, $72
      .db $0d, $05, $97, $34, $98, $62, $a3, $20, $b3, $06
      .db $c3, $20, $cc, $03, $f9, $91, $2c, $81, $48, $62
      .db $0d, $09, $37, $63, $47, $03, $57, $21, $8c, $02
      .db $c5, $79, $c7, $31, $f9, $11, $39, $f1, $a9, $11
      .db $6f, $b4, $d3, $65, $e3, $65, $7d, $c1, $bf, $26
      .db $fd
"""

# extract bytes($[0-f]{2}) from the level data by regex
level5_1_bytes = re.findall(r'\$[0-9a-f]{2}', fixed_level5_1_area_data)
for i, b in enumerate(level5_1_bytes):
    print("S{}:{}:5-1 area data offset {}".format( \
        hex(level1_1_base_addr + i)[2:], b[1:], i))

level5_1_area_data_addr_low = 0x9d39
level5_1_area_data_addr_high = 0x9d5b

# level 5-1 area data address <- level 1-1 area data address
print("S{}:{}:point to 1-1 area data addr low".format( \
    hex(level5_1_area_data_addr_low)[2:], hex(level1_1_base_addr & 0xff)[2:]))
print("S{}:{}:point to 1-1 area data addr low".format( \
    hex(level5_1_area_data_addr_high)[2:], hex((level1_1_base_addr >> 8) & 0xff)[2:]))
```

また、`L_GroundArea11`のようなラベルをキーに、[このリンク](https://6502disassembly.com/nes-smb/SuperMarioBros.html)で検索することで、各ステージのデータの開始アドレスがわかります。16ビットの開始アドレスを持つため、2バイトで高位8ビットと下位8ビットを格納する必要があります。

このPythonスクリプトを実行すると、チートコードの内容が得られ、FCEUXにインポートすることができます。4-2のステージに進むと、まさに「奇跡の瞬間」を目撃できるでしょう！

<iframe width="970" height="565" src="https://www.youtube.com/embed/tgeQe30tn3c" title="Fix SMB1 5-1 small Castle🏯 with Cheat Code in FCEUX" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

🔚