🏰

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

に公開

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

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

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

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


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

https://github.com/Ribiveer/SMB-Tweaked/tree/main

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

; 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個のチートコードを生成しました。

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のようなラベルをキーに、このリンクで検索することで、各ステージのデータの開始アドレスがわかります。16ビットの開始アドレスを持つため、2バイトで高位8ビットと下位8ビットを格納する必要があります。

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

https://youtu.be/tgeQe30tn3c

🔚

Discussion