PlayLoud!!

Since1997

雷電 無敵改造

さて、改造の続き……
ボンバー無限と残機無限はできたので次は無敵とかできないかな……と考えたわけですが、無敵は数を数えてってわけにいかないので難しそうです。
 
で、いろいろ検索してたところMAMEにはチートがあるのを見つけました!
いや、それは知ってたんだけど普段MAMEで遊ぶことなんて無いのですっかり忘れてました。
実はMAMEのチート使うのも初めてです。
昨日のエントリにもコメントしてくださった方がいました。
 
今日はGUIMAME plusというのを使ってみました。こんな感じ。

チートのメニューがありますが、これはどこかからチートファイルを見つけてこないと出てこないようです。
チートの中身はこんな感じです。ボンバーや自機の無限てありますね……

で、問題はこのチートがどのように定義されているかってところなんですが、なんとわかりやすいことにxmlファイルでチートの内容とメモリに与える値が定義されています。

 <cheat desc="Infinite Lives PL1">
  < script state="run">
    <action>maincpu.pb@0BD5=0A</action>
  </script>
 </cheat>
 
 <cheat desc="Infinite Bombs PL1">
  < script state="run">
   <action>maincpu.pb@0BE3=06</action>
  </script>
 </cheat>

上のように、先日書き換えた残機やボンバー数が格納されているアドレスが書かれています。
MAMEでは、エミュレータ本体がエミュレートされているメモリ上にその値を継続して書き込んでくれるらしく、特にプログラムを書き換える必要はありません。
定義文から推測するに「script state」が"run"であるとメモリに対して常に値を書き込み続け、"on"であるとその場で一発書き込みって感じなんでしょうか。
他にも書式があるようですがそれについてはまた調べてみます。
 
さて、無敵のところを見てみましょう。

 <cheat desc="Invincibility PL1">
  < script state="run">
   <action>maincpu.pb@1215=0B</action>
  </script>
 </cheat>

どうやら1215Hに0BHを書きこめば無敵になるみたいです。
自機の状態を表しているフラグみたいなものなんでしょうか。一応デバッガで該当メモリを見張ってみましょう。
 
詳細は省略しますが、調べてみるとやはり自機の状態を表しているようです。

復活時などの無敵状態 0BH
通常の状態           03H
死んだとき           00H

で、さらに昨日と同じ手順でこのアドレスに書き込んでいるルーチンを探しました。
ここですね。

FBCA8: 88 46 0B mov  byte ptr [bp+0Bh],al

これを常に0BHを書きこむように直してやればよさそうですが……8086の命令がよくわからないので即値が書き込めませんw
しかも使えるのは3バイト限定でプログラムの前後に余裕はありません!

FBC9D: E8 08 00           call 0FBCA8h ←ここからコールされている
FBCA0: C7 06 28 04 00 00  mov  word ptr [428h],0h
FBCA6: EB AD              br   0FBC55h ←ここで以下のサブルーチンを飛び越している
FBCA8: 88 46 0B           mov  byte ptr [bp+0Bh],al ←イマココ!
FBCAB: 26 AD              ldmw   
FBCAD: 8B D8              mov  bw,aw
・
・

ソースを読むとcall命令は3バイトなので、空いているメモリに無敵状態書き込みルーチンを置いてそこを呼び出すようにしましょう。
メモリの様子を見て、FF800Hから空いている感じなのでこんな感じで……

FBCA8: E8 55 3B call FF800h

 
呼び出し先はこうですね。

push ax
mov  al,0bh
mov  byte ptr [bp+0Bh],al
pop  ax
ret

勝手サブルーチンでスタックポインタをいじっちゃうのはどうなの……とか思いましたが、まあ大丈夫かな?
さて、どうでしょうか……ゲームスタートしてみます。

 
アッー!リセットかかってタイトルに戻った!?ww

 
どういうことでしょうか?なぜかゲームが始まってほどなくリセットがかかってしまいます。
仕方ないのでデバッガで先程のcall命令にブレークポイントを設定。
プログラムが停止したところからステップ実行で進めてみます。
デバッガのコマンドは「bp FBCA8」ですね。
まずはcallに書き換えたところでプログラム停止。ここでF11を押してステップ……

アッー!?「call FF800h」なのにEF800hに飛んでる!?

これはセグメントを飛び越えられない的な何かですかね……この辺りの知識に乏しくて泣けます。
仕方ないので、サブルーチンの開始アドレスをもう少し低い方に移しました。
セグメント書き換える方法もあるんだろうけど、資料とか無いのでお手上げですw

FBCA8: E8 55 2E call FE800h

デバッガで確認すると、このアドレスは呼び出せる感じ。
じゃあプレイ続行で……
おおっ!無敵です!成功です!

攻撃してないのにアイテムキャリアが火を噴いているのがちょっと気になりますが……
あ、あれ!?敵も死なない…?ww

なんということでしょう!なんということでしょう!自分も死にませんが敵も死にません!w
ボンバーにも耐えてますww

敵が無敵なので中型機がボス戦まで着いてきてしまいます。
なんというカオス。

うーん、ボスはダメージ入るのか……

いずれにしても何かおかしい感じです。
 
ちょっと悩んでいたのですが、よくよく考えると無敵状態の数値を書き込んでいる命令にbpレジスタが使用されています。
呼び出し元がわからないんだからこのbpは不定ですよね……

mov byte ptr [bp+0Bh],al

上の命令にブレークポイントを設定してbpレジスタをよーく眺めてみたところ、自機の状態のアドレスに値を書き込んでいる場合と、それ以外のアドレスに値を書き込んでいる場合があるようです。
どうやら、自機、敵で状態を書き換えるルーチンは共用だったみたいです。
気づけばごく当然のことながら、ここに気づくまで1時間ぐらいかかってしまいました。思い込みってだめだよね……orz
で、サブルーチンを改良です。

mov  byte ptr [bp+0Bh],al
push ax
mov  al,0bh
cmp  [01215h],al
je   skip
mov  [01215h],al
skip:
pop  ax
ret

とりあえず、[bp+0Bh]にalレジスタは書きこんでおいて、その後で自機の状態をチェック。
無敵状態の0BH以外だったら改めて0BHを書き込むようにしました。
ちょっと効率悪い感じで、勝手サブルーチンもあまり巨大化させない方がいいんだろうけど、まあ仕方ないです。
いやまあ、ぶっちゃけ無敵状態のチェックとかいらないですけど、今後0BH以外の自機の状態が判明したときに柔軟に対応できるように準備しておきました。
 
さて、それじゃあこれをハンドアセンブルして……と、ここで行き詰まりました。
ハンドアセンブルの限界です。
いろいろと命令を増やしたので、雷電のソースから同じニーモニックを見つけられなくてコードがわかりません。
ぐぬぬ……

こ、困ったらツイートだよね……w
 
ツイッターでそんなことを書いてたら、ume3さんnasmというアセンブラを教えていただきました。
なんでもV30特有の命令なんてそんなにないから8086でアセンブルするのが普通だとか……そうだったんですね。
勉強になります。
 
で、上のルーチンをアセンブルしてみました。

mov  byte ptr [bp+0Bh],al  88 46 0B
push ax                    50
mov  al,0bh                B0 0B
cmp  [01215h],al           38 06 15 12
je   skip                  74 03
mov  [01215h],al           A2 15 12
skip:
pop  ax                    58
ret                        C3

おおー!流石流石!
 
生成されたコードを手作業でまた交互に書きこんでいきます。
で、実行……
 
おおっ!キター!

敵にはきちんとダメージが入ります!そして自機は無敵!
なんとか完成しました。
もうよくわかってる人には簡単な改造だと思いますが、やっぱり自分でやってみないとわかりにくいですよね。
でもこれで少しコツが掴めてきました。
 
ちなみにROMからメモリへ交互にデータを読むのは、V30のバス幅が16bitであるのに対し、当時の一般的なEPROMのバス幅が8bitだったということをEXCEEDさんに教えていただきました。
id:EXCEEDさんのブログはよく読んでいるんですよね。
fujikkoさんにも、2つのROMから交互に読めば読み出し速度が最大1/2の遅さでも間に合うというメリットもあると教えていただきました。
うーん、わからないことはツイートすると賢くなりますねw
 
まとめ

新しいコール
FBCA8H
rai3.bin
1DE54 88  0BE8 2E
rai4.bin
1DE54   4655
 
新しいサブルーチン
FEB00H
rai3.bin
1F580 16 C0 1688 0B B0 38 15 74 A2 12 C3
rai4.bin
1F580 EF EE EF46 50 0B 06 12 03 15 58

 
無敵についてはROMに焼いて実機で動かしてみるつもりです。
動画録ればキャプチャしてマップ作れるかもしれないですね。
 
あ…!w