Death ForceはFM77AV専用ソフトとして1987年にリバーヒルソフトから発売された。FM77AVがせっかく4096色同時発色(あるいは8色4画面とか64色2画面とかパレットでなんとでもなった)を実現したのに登場直後専用タイトルがなかなか出なかったそのFM77AV界にあって、最初に多色同時発色を有効利用したタイトルだった。
先日ゲーム保存協会を訪問したとき、「果たしてFM77AV専用ソフト、Death Forceはクリアできるのか?」という問題があることを聞いた。なんでも、Death Forceはコピープロテクトの誤爆によりクリアが不可能である可能性があるということだった。その真相を調べたくなった。
そのDeath Force、自分は当時Death Forceは予算の都合で買わなかったので持っていなかったが、最近ヤフオクに3.5インチディスクが出品されていたので落としてみた。届いたフロッピーディスクは幸いカビなども無く、実機FM77AV40を利用して難なくイメージ化できた。コピープロテクトの内容を解析したところD77ディスクイメージで再現可能なレベルのようだったので、エミュレータXM7を利用してクリアを目指してみた。
結論
いきなり結論を書いてしまうと、噂はおおむね本当であった可能性が高い。クリアが不可能なバージョンと可能なバージョンが存在することが確認できた。
まず、ヤフオクで今回落としたバージョンではクリアは無理。難しいからではなく、ラスボスに通じる通路への入口に入ることができない。解析した結果、その入り口は、座標 ($19,$0D)にあることになっている。しかし、この位置は壁。入り口座標を上にずらすか、あるいは壁の当たり判定を下にずらさない限り通過できない。
具体的には、この画面。プログラム中ではマップ$79となっている。Death Forceの世界全体は16×16のマップで構成されていてそれぞれのマップに地上・地下がある。左上がマップ$00、右にひとつ移動すると番号は1増え、下に移動すると$10増える。$79は左上から下に7枚、右に9枚移動したマップを意味する。
隠れでもなんでもないただの階段に入らなくてはならない。が、そのままでは入れない。
最終ステージの階段位置テーブル($7C00~)は次の通り
$7C00~
14
75 05 0C 01
56 1B 09 04
77 17 1C 01
78 07 1F 01
79 19 0D 01
85 08 10 04
77 1C 07 04
87 1E 0F 04
58 0E 07 04
69 1E 13 04
6B 1E 13 04
48 0F 0B 01
89 05 16 04
6A 0F 14 04
57 19 0F 04
88 13 0F 04
8A 07 1D 01
7A 07 1D 01
7B 05 01 04
8B 05 07 01
最初の1バイトが階段個数。16進数なので、10進数だと20個の階段がある。続く4バイト単位の意味はマップID、マップ上のX座標、マップ上のY座標、階段の向き(上下)を表している。最後の1バイトが1のとき地上から地下は下向き、地下から地上は上向き。4の場合はその逆となっている。黄色でハイライトした階段が問題の階段で、オリジナルのディスクイメージではY座標が$0D (10進数で13) になる。
問題の画面、マップ$79の壁判定用のテーブル($4C00~)が次の通り。この値のビット1が0なら通行可能、1なら壁と判定される。Death Forceの画面は40×37個の小ブロックに分かれていて、衝突判定用テーブルは1行あたり縦2ブロック分をカバーする。赤くハイライトしたブロックが問題の壁で、自機は3×3ブロックを占有するので、グリーンにハイライトした場所より下に移動することができない。この座標が($19,$0A)。入り口の判定は壁判定よりも前に実行されるので、入り口座標が($19,$0B)であるならば入り口に入ることができる。しかし、入り口座標は($19,$0D)なので、ちょうど衝突判定テーブル1行分足りない。
$4C00~ 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 01 01 01 01 01 01 01 03 03 03 00 00 00 00 00 00 00 00 03 03 03 03 03 01 01 01 01 01 01 01 01 01 01 01 01 01 01 03 03 03 01 01 01 01 01 01 01 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 01 01 01 01 01 01 01 01 01 01 01 01 01 01 03 03 03 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 00 00 03 03 03 00 01 00 00 00 01 00 00 00 00 00 00 00 00 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 00 01 00 00 00 01 00 00 00 00 00 00 00 00 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 00 03 03 03 03 03 00 00 00 00 00 00 00 00 00 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 00 00 00 00 00 00 00 00 00 00 01 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
この状況を打破するためには、二通りの方法がある。ひとつは、プログラムディスク(ディスクA)を書き換えて階段の座標を($19,$0B)に変更する方法。しかし、この座標はプログラムディスクのトラック15、サイド0、セクタ12に書いてある。この座標を修正するにはプログラムディスクの書き換えが必要なので、望ましくない。原本ディスクの書き換え時にエラーが起きた場合原本ディスクを破壊する可能性がある。また現存しているDeath Forceの原本ディスクは貴重な資料だから、現在の内容を維持するべきだ。
もうひとつの方法がデータディスクを書き換える方法。上の衝突判定用テーブルは、画面切り替えのときいったん$7800からの20×10バイトにコピーされて、その情報を元に$4C00からの40×20のテーブルが展開される。この画面$79の20×10データは以下の通り。
$7800~ 01 01 01 06 03 5B 5B 5B 5B 5B 02 08 01 01 01 01 01 01 06 03 09 09 09 0E 0A 01 01 06 03 5B 0F 10 09 09 09 09 09 09 0E 0F 00 00 00 1B 09 09 09 0E 0F 5B 0F 18 00 00 00 00 00 00 16 0F 00 00 00 00 00 00 00 16 0A 01 0B 18 2D 2E 2F 00 00 00 16 0F 00 00 00 00 00 00 00 1B 09 09 09 19 46 47 5A 00 1D 02 1E 1F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 0F 5B 5B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 0A 06 03 1E 1E 1E 1E 1E 1E 1E 1E 1E 03 1C 00 00 00 00 00 1B 09 0E 0F 5B 5B 5B 5B 5B 5B 5B 5B 5B 0F 18 00 00 00 00 00 00 00 16 0F 5B 5B 5B 5B 5B 5B 5B 5B 5B 20 1E 1E 1E 1E 1E 1E 1E 1E 1E 1F
コピー元は$5C80~に格納されている。
このうち黄色でハイライトした部分が問題の階段に対応する。これを一段下に下げてしまえばいい。幸いにして、このデータはデータディスクのトラック11、サイド1、セクタ9に書かれている。(データディスクを生成したときにディスクBからコピーされたまま。) データディスクを生成した後で、以下の書き換えを加えることで、階段を一段(2ブロック)下に下げてラスボス戦に通じる通路に突入が可能になる。
修正前 Disk:0 Track:11 Side:1 Sector:9 0000 18 00 00 1d 04 05 1c 00 5b 02 08 01 0b 18 00 1b|........[....... 0010 09 09 09 09 19 00 00 16 0c 0d 18 00 5b 0f 10 09|............[... 0020 09 19 00 00 00 00 00 00 00 00 00 1b 09 09 19 00|................ 0030 5b 0f 18 00 00 00 00 00 00 1d 02 1e 03 1c 00 00|[............... 0040 00 00 00 00 5b 0f 18 00 00 00 00 1d 02 1e 1f 5b|....[..........[ 0050 20 1e 1e 1e 1e 1e 1e 1e 5b 0f 18 2d 2e 2f 00 16| .......[..-./.. 0060 0f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 20 1e 1e|.[[[[[[[[[[[[ .. 0070 1e 1e 1e 1e 1f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b|.....[[[[[[[[[[[ 0080 01 01 01 06 03 5b 5b 5b 5b 5b 02 08 01 01 01 01|.....[[[[[...... 0090 01 01 06 03 09 09 09 0e 0a 01 01 06 03 5b 0f 10|.............[.. 00a0 09 09 09 09 09 09 0e 0f 00 00 00 1b 09 09 09 0e|................ 00b0 0f 5b 0f 18 00 00 00 00 00 00 16 0f 00 00 00 00|.[.............. 00c0 00 00 00 16 0a 01 0b 18 2d 2e 2f 00 00 00 16 0f|........-./..... 00d0 00 00 00 00 00 00 00 1b 09 09 09 19 46 47 5a 00|............FGZ. 00e0 1d 02 1e 1f 00 00 00 00 00 00 00 00 00 00 00 00|................ 00f0 00 00 00 00 16 0f 5b 5b 00 00 00 00 00 00 00 00|......[[........
修正後 Disk:0 Track:11 Side:1 Sector:9 0000 18 00 00 1d 04 05 1c 00 5b 02 08 01 0b 18 00 1b|........[....... 0010 09 09 09 09 19 00 00 16 0c 0d 18 00 5b 0f 10 09|............[... 0020 09 19 00 00 00 00 00 00 00 00 00 1b 09 09 19 00|................ 0030 5b 0f 18 00 00 00 00 00 00 1d 02 1e 03 1c 00 00|[............... 0040 00 00 00 00 5b 0f 18 00 00 00 00 1d 02 1e 1f 5b|....[..........[ 0050 20 1e 1e 1e 1e 1e 1e 1e 5b 0f 18 2d 2e 2f 00 16| .......[..-./.. 0060 0f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 20 1e 1e|.[[[[[[[[[[[[ .. 0070 1e 1e 1e 1e 1f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b|.....[[[[[[[[[[[ 0080 01 01 01 06 03 5b 5b 5b 5b 5b 02 08 01 01 01 01|.....[[[[[...... 0090 01 01 06 03 09 09 09 0e 0a 01 01 06 03 5b 0f 10|.............[.. 00a0 09 09 09 09 09 09 0e 0f 00 00 00 1b 09 09 09 0e|................ 00b0 0f 5b 0f 18 00 00 00 00 00 00 16 0f 00 00 00 00|.[.............. 00c0 00 00 00 16 0a 01 0b 18 00 00 00 00 00 00 16 0f|................ 00d0 00 00 00 00 00 00 00 1b 09 09 09 19 2d 2e 2f 00|............-./. 00e0 1d 02 1e 1f 00 00 00 00 00 00 00 00 00 00 00 00|................ 00f0 46 47 5a 00 16 0f 5b 5b 00 00 00 00 00 00 00 00|FGZ...[[........
この修正を加えたデータディスクで問題の画面、マップ$79 に移動すると、こうなる。
階段が2ブロック下にずれる。これによりプログラムディスクに修正を加えることなくクリアが可能になる。
それだけだと不親切なので、データディスクイメージにパッチを当てるPython Scriptを書いた。
import sys inFName=sys.argv[1] outFName=inFName+"_patched" print("Input="+inFName) print("Output="+outFName) ifp=open(inFName,"rb") bin=ifp.read() ifp.close() print("Python inferiority.") print("Why do I have to convert an array to a list, edit,") print("and then convert it back to an array?") print("Surprisingly poor programming language.") print("Ditch Python. Use C++.") lst=[] for b in bin: lst.append(b) print("Input Length="+str(len(lst))+" bytes") nFound=0 for i in range(len(lst)-60): if lst[i:i+5]==[0x0b,0x18,0x2d,0x2e,0x2f] and lst[i+20:i+25]==[0x09,0x19,0x46,0x47,0x5a]: print("Found at 0x"+hex(i)) lst[i+42]=lst[i+22] lst[i+43]=lst[i+23] lst[i+44]=lst[i+24] lst[i+22]=lst[i+2] lst[i+23]=lst[i+3] lst[i+24]=lst[i+4] lst[i+2]=0 lst[i+3]=0 lst[i+4]=0 nFound+=1 if 1!=nFound: print("This should occur only once.\n") print("Something is not right.\n") quit() ofp=open(outFName,"wb") ofp.write(bytearray(lst)) ofp.close() print("Wrote to "+outFName) print("Rename the extension before using.")
使い方は、データディスクのイメージが DFORCEDATA.D77、スクリプトがdforcepatch.py だとすると、
python dforcepatch.py DFORCEDATA.D77
DFORCEDATA.D77_patch というファイルができるので、拡張子をD77に変更して使う。
ちなみにPythonは嫌い。みんなC++使おうよ。なんでバイナリの配列をいったんリストに変更してまた配列に戻さなきゃならないんだ。酷い言語だ。なんでこんな欠陥言語が流行するかな。早く消滅してほしいが、消滅されると山のように書いたメンテナンススクリプトが書き直しになるから消滅してもらうと困る。なんで上のをPythonで書いたかというと多分C++コンパイラ入れてる人よりPython入れてる人の方が多いだろうと思ったんで。
それと、下はラスボス直前とエンディング直前のデータファイル。 なお、データは改造してある。最終ステージの猛攻は改造なしでは自分の腕では突破できなかった。なお、ラスボス直前から無敵コマンドを使ってもクリアは可能。
なお、このデータディスクは上の方法とは違う方法で問題の階段に突入したので、トラック11、サイド1、セクタ9は元のまま。
バグ修正版ディスクは実在した
Death Forceに関するうわさのひとつに、クリアできないというクレームを入れたところリバーヒルソフトからバグ修正版のディスクが送られてきてそれを使うとクリアできた、というものがある。それは本当なのか?
もしもバグ修正版が存在するとすれば、これまでの解析により修正箇所はディスクAのトラック15、サイド0、セクタ12、またはディスクBのトラック11、サイド1、セクタ9である可能性が高い。しかし、クリアできなかったユーザがいて、リバーヒルソフトから送られてきた修正版ディスクでクリアできたとする噂が本当であるとすれば、ディスクAの修正である可能性が高い。ディスクBは新たにゲームを開始するときしか使用しないので、ディスクBの修正であったとすればそのユーザがそれまでプレイしていたデータを破棄してデータディスク作成からやりなおしたのでなければクリアはできない。
もしもバグ修正版とするディスクの存在が確認できればこの謎は解決する。そして、クリア不能バージョンの問題はただのバグだった可能性が非常に高くなる。
この謎は、ゲーム保存協会メンバーが所有するDeath ForceのディスクA、およびゲーム保存協会が収蔵するDeath ForceのディスクAを確認することで解決した。まず、ゲーム保存協会メンバーが所有するディスクは裏面に 75524X 2D という刻印があった。おそらく、これはフロッピーディスク製造時のバッチを表す刻印であってDeath Forceのシリアルナンバーでは無いかもしれないが、手元のクリア不能バージョンの刻印 6C184F 2D よりと比べると最上位桁が6から7に上がっているのでおそらく後に製造されたディスクと推測できる。
以下はそのディスクのトラック15、サイド0、セクタ12のダンプ。
0000 14 75 05 0C 01 56 1B 09-04 77 17 1C 01 78 07 1F |.u...V...w...x..
0010 01 79 19 0B 01 85 08 10-04 77 1C 07 04 87 1E 0F |.y.......w......
0020 04 58 0E 07 04 69 1E 13-04 6B 1E 13 04 48 0F 0B |.X...i...k...H..
0030 01 89 05 16 04 6A 0F 14-04 57 19 0F 04 88 13 0F |.....j...W......
0040 04 8A 07 1D 01 7A 07 1D-01 7B 05 01 04 8B 05 07 |.....z...{......
0050 01 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
0060 00 00 00 00 00 90 00 02-04 00 00 C0 03 90 02 1E |...........タ....
0070 04 93 00 02 04 00 00 C0-03 90 02 28 06 96 00 04 |.......タ...(....
0080 08 82 03 00 01 8C 0B 32-08 AE 00 04 08 82 03 00 |.......2.ョ......
0090 01 0C 0B 32 0A C6 00 04-08 82 03 00 01 0C 0B 46 |...2.ニ.........F
00A0 0F 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00B0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00C0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00D0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
そして、以下は手元のクリア不能バージョンの同じセクタのダンプ。
0000 14 75 05 0c 01 56 1b 09 04 77 17 1c 01 78 07 1f|.u...V...w...x..
0010 01 79 19 0d 01 85 08 10 04 77 1c 07 04 87 1e 0f|.y.......w......
0020 04 58 0e 07 04 69 1e 13 04 6b 1e 13 04 48 0f 0b|.X...i...k...H..
0030 01 89 05 16 04 6a 0f 14 04 57 19 0f 04 88 13 0f|.....j...W......
0040 04 8a 07 1d 01 7a 07 1d 01 7b 05 01 04 8b 05 07|.....z...{......
0050 01 39 1a 10 cc 0f 2f 8d 41 cc 03 0e f7 fd 16 b7|.9..../.A.......
0060 fd 15 7f fd 15 90 00 02 04 00 00 c0 03 90 02 1e|...............
0070 04 93 00 02 04 00 00 c0 03 90 02 28 06 96 00 04|...........(....
0080 08 82 03 00 01 8c 0b 32 08 ae 00 04 08 82 03 00|.......2........
0090 01 0c 0b 32 0a c6 00 04 08 82 03 00 01 0c 0b 46|...2...........F
00a0 0f 15 7f fd 15 f7 fd 16 4a b7 fd 15 7f fd 15 39|.......J.....9
00b0 34 02 64 e4 24 14 bd 2a cb cc 1d 00 b7 fc 80 f7|4.d.$..*........
00c0 fc a0 96 36 b7 fc a1 7f fd 05 64 e4 24 14 bd 2a|...6.....d.$..*
00d0 cb cc 1d 01 b7 fc 80 f7 fc a0 96 35 b7 fc a1 7f|...........5...
00e0 fd 05 64 e4 24 14 bd 2a cb cc 1d 02 b7 fc 80 f7|..d.$..*........
00f0 fc a0 96 37 b7 fc a1 7f fd 05 64 e4 24 14 bd 2a|...7.....d.$..*
クリア可能バージョンではオフセット$0011からの4バイトは 79 19 0B 01 とある。クリア不能バージョンでは 79 19 0D 01。この変更は、まさに上の結論の中で書いたディスクAの修正方法のひとつと一致する。
そして、ゲーム保存協会が収蔵するDeath ForceのディスクAも調べていただいたところ、問題のセクタのオフセット$0013は3枚のうち2枚が$0D (クリア不可能)、1枚が$0B(クリア可能) だった。調べたDeath ForceのディスクA合計6枚のうち2枚がクリア可能、4枚がクリア不可能だった。当時のユーザがなんらかの方法で修正方法を知り、ユーザの手で書き換えが加えられた可能性は排除できないものの、2枚ともユーザの手による改造が加えられていた可能性は非常に低く、リバーヒルソフトが初期バージョンを出荷した後でバグに気づいて修正バージョンを作ったと考えるのが自然だ。
考えようによってはヤフオクで届いたディスクがクリア不能バージョンだったからこの謎が解けたとも言える。クリア可能バージョンだったのであれば多分普通にクリアしてDeath Forceに関する噂はまったく確認できなかった。たまたまクリア不能バージョンが手元に届き、問題箇所を特定でき、ゲーム保存協会メンバーが収蔵するディスクと比較することができたので、クリア可能バージョンとクリア不能バージョンが存在することを確認できた。
コピープロテクトの誤爆なのか?
果たして、クリアできないバージョンの問題がコピープロテクトの誤爆によるものなのか、確定的な判断はできなかった。しかし、コピープロテクトの誤爆である可能性は非常に低いと考えている。そう考える根拠はマップ$79を含むステージ9のデータが読み込まれるタイミングからマップ$79に到達するまでの間でコピープロテクトチェックのためのディスクアクセスが無い点。
Death ForceのコピープロテクトはFM-7シリーズでは一般的なF6, F7セクタの存在と内容確認、読み込み後CRCエラーを確認する方法を使っている。XM7のソースコードを改造してF5, F6, F7をセクタレジスタ$FD1Aに書き込んだらブレークするようにしてテストした結果、最終ステージ突入時からマップ$79到達までの間でF5, F6, F7セクタアクセスは無かった。
従って、Death Forceがクリアできない問題の原因がコピープロテクトの誤爆である可能性は低い。
バグなのか?
自分としてはDeath Forceの初期バージョンがクリアできないのはただのバグによる可能性が高いと思う。そう考える理由もいくつかある。
最大の理由は、クリア可能なバージョンの存在が確認できた点。クリア可能なバージョンでは特別な条件を満たすこともなく問題の階段に入ることができる。
また、もうひとつの大きな理由は最終ステージの完成度が低い点。最終ステージではデータとして入口座標は存在するが壁に阻まれて入れない階段が多い。また同じ理由で入ったら二度と出られない部屋が複数ある。意図的に閉じ込めている可能性もあるが、最終面はデバッグ不足のように思う。
Death Forceは ゲームを通じてある特別な状態にのみ発動する処理が少ない。特別な処理があったのはシールを15個すべて集めた瞬間に自機の外見とBGMが変わった瞬間とラスボス戦に勝ったタイミングのみで、あとはまったく同じ法則でゲームが進む。問題の通路のためだけに特別な処理を書いただろうか?もしもそのような処理があれば他の場所でも使いそうな気がする。
そもそも仕掛けを作るとすれば見えているのに入ることができない階段を作るだろうか?初期状態で何も見えない床にしておいて、フラグが立ったら階段を出すように作るのではないだろうか。フラグによって階段がずれるというのも不自然に思える。
以上の理由により、自分としてはクリアできない問題は単純なバグによるものだと思う。
だが、仮にバグで無かったとすると、なんらかの方法でクリアが可能になるはずだ。初期バージョンには後期バージョンにない仕掛けがあり、それを解明しない限りクリアできなかったが、あまりにも難しいため次のバージョンから除去されたという可能性も完全に排除することはできない。可能性としては、なんらかの条件を満たすことによって、
(1) MAP $79の階段入り口座標($19,$0D)が($19,$0B)に変わる。
(2) MAP $79の階段のグラフィックスと壁の衝突判定が1段(2ブロック分)下に移動する。
(3) 鍵によってMAP $79の階段のグラフィックスと壁の衝突判定が1段(2ブロック分)下に移動する。
(1)の可能性はまず無い。問題の階段の入り口座標はゲームのたびにプログラムディスク(ディスクA)から読み込まれる。この座標が記録されているのはデータディスクではない。もしも(1)だとすると、その状態はデータディスクに保存できない。
(2)の可能性もまず無い。仮にそのような条件が存在するとしたら、状態がセーブできるべきだ。マップデータはデータディスク上に存在するのでセーブできる可能性もあったので、XM7上で実験してみた。まず、最終ステージに移動した状態で、$5C80~のバイトを書き換える。そのうえでマップ$79に移動して変化を確認した。その状態でF5キーを使ってセーブして、ディスクイメージにマップデータが保存されたかを調べた結果、マップデータの変更はセーブされなかった。リセットしてデータを読み込んでみたところマップは元通りだった。
(1)と(2)はセーブできないステートである可能性もゼロとは言い切れないが、他のすべてのステートがセーブできるのに、この一点だけセーブできないのはありえないように思う。
最も可能性が高いのは(3)。実は画面切り替え部分に非常に気になるコードがあって、
2812 30 88 13 LEAX $13,X マップ構造体(問題のMAP $79だと$A6B0)+$13 2815 86 06 LDA #$06 6バイトだけ? 2817 97 13 STA <$13 ループカウンタ 2819 E6 80 LDB ,X+ 281B C1 FF CMPB #$FF 281D 27 1A BEQ $2839 281F CE 78 00 LDU #$7800 2822 4F CLRA 2823 33 CB LEAU D,U 2825 A6 80 LDA ,X+ 2827 D6 20 LDB <$20 2829 C4 01 ANDB #$01 282B 27 02 BEQ $282F 282D 9A 14 ORA <$14 282F A7 C4 STA ,U 2831 0A 13 DEC <$13 2833 26 E4 BNE $2819
あるマップが鍵を消費するように設定してある場合、マップに二通りの変更が加わる。ひとつは上位2ビットが一定の条件を満たすバイトに対して、上位2ビットを違う値に置き換える。もうひとつは6バイトだけ特定の値に書き換える。後者の変更を使えば問題の階段を書き換えることができる。
MAP$79に対応するその情報は、物理アドレス$0B6B0~の48バイトに書いてある。
0B6B0: 04 04 03 08 04 08 0C 04 0B 16 04 12 16 00 00 00 :7C ................ 0B6C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 :02 ................ 0B6D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :00 ................
このうち、オフセット$1Fのビット7が1のとき、このマップに入ったとき鍵を消費する。オフセット$13~$1Eが6バイトの書き換え(位置・新しい値のペアx6)にあたる。この部分を以下のように変更することで、鍵を持ってマップ$79に入ったとき階段が下に伸びるように
なる。
0B6B0: 04 04 03 08 04 08 0C 04 0B 16 04 12 16 00 00 00 :7C ................
0B6C0: 00 00 00 5c 2d 5d 2e 5e 2f 70 46 71 47 72 5a 82 :02 ................
0B6D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :00 ................
そして
、重要なのは、この変更はセーブしたときデータディスクに保存される。
この書き換えを実行してマップ$79に入ったときの画面はこうなる。
プレイヤーがなんらかの条件を満たすことでMAP$79のプロパティが上記のように書き換わる仕掛けがあったのかもしれない。 しかし、この方法では階段が不自然に伸びてしまう。グラフィックスの整合性を壊すような仕掛けを作るだろうか?これも可能性は低いような気がする。
いずれにしても、そのコードが見つかっていないからと言っても、存在しないとは言い切れない。だから可能性はゼロとは言い切れない。
ただ、上の可能性を検証してみた結果、自分はどれも可能性が低く、やはりDeath Forceがクリアできなかったのはただのバグの影響だと思う。
隠しコマンド
Death Forceには、起動時に大文字で”SILVER”とタイプしてからスペースを押すと、リセットするまで無敵になるコマンドが知られている。出展: http://fm-7.com/museum/database/urawaza/810101300.html
コードを解析した結果、他に二種類の隠しコマンドを確認した。ちなみに、文字の順序は関係なく、また、文字列のチェックではなく、XORとADDの結果を比べているので、同じ効果を持つ単語は他にも無数に存在する。以下のキーワードはその中からひとつ選んだものだが、意味のある文字列が存在するのかどうかは候補が多すぎて確認できなかった。なお、大文字・小文字は正しくタイプする必要がある。
いずれも、オープニングのBGMが鳴っているところでタイプしてスペースでゲームを開始することで適用できる。
“SILVER” 無敵 (チェックサム 0x17D5)
“1VKJIC” LIFゼロ (チェックサム 0x6CA8) <- その後確認したところLIFはデータディスクから上書きされるので意味が無かった。
“ARKoxo” 無敵+鍵無限+爆弾連射可能+? (チェックサム 0x2034)
なお、以下はフィーチャー2とフィーチャー3要キーワードを発見するために使ったC++コード。根気のある人は、この中から意味のある単語を見つけ出せるかも。
// Copyright 2019 CaptainYS (soji@andrew.cmu.edu) // Can redistribue free of charge without permission. #include <stdio.h> #include <string.h> unsigned int Sum(const unsigned char buf[6]) { unsigned int sum=0; unsigned int A=0,B=0; for(int i=0; i<6; ++i) { A=(A^buf[i])&0xff; // AND is not really necessary. B=(B+buf[i])&0xff; } // 236C A8 84 EORA ,X // 236E EB 84 ADDB ,X // 2370 6F 80 CLR ,X+ // 2372 31 3F LEAY -1,Y // 2374 26 F6 BNE $236C return A*256+B; } inline unsigned char Increment(unsigned char c) { ++c; if('9'+1==c) { c='A'; } else if('Z'+1==c) { c='a'; } else if('z'<c) { c='0'; } return c; } int main(void) { printf("%02x\n",Sum((const unsigned char *)"SILVER")); // 17D5 "SILVER" Invincible // 6CA8 LIF=0 ?? // 2034 Invincible, Infinite KEY, Simultaneous bomb shots, +??? char buf[7]="000000"; while(0!=strcmp(buf,"zzzzzz")) { buf[5]=Increment(buf[5]); for(int i=5; 0<i; --i) { if('0'==buf[i]) { buf[i-1]=Increment(buf[i-1]); } else { break; } } for(int i=0; i<4; ++i) { // Doesn't take two same key strokes. if(buf[i]==buf[i+1]) { goto NEXT; } } auto s=Sum((const unsigned char *)buf); if(0x17D5==s || 0x6CA8==s || 0x2034==s) { printf("%04x: %s\n",s,buf); } NEXT: ; } printf("%s\n",buf); return 0; }
エンディング。ネタバレごめん。http://fm-7.com/forum/viewtopic.php?f=3&t=71 の情報によりラスボス戦後爆弾で自爆することでエンディング。
DUR Maxがぜんぜん成長しない問題
このゲーム、DURというパラメータ以外はボスが落とすアイテムで成長していくが、DURの最大値だけはなかなか上がらない。一体どういう条件で上がるのか?DURの最大値は<$3C に書いてある。それが上がる条件を調べたところ、ダメージを受けるたびに <$48 が1上昇して、128回に1度DUR Maxが1だけ上昇することがわかった。
以下はダメージルーチン内。
1BFC 0C 48 INC <$48 1BFE 96 48 LDA <$48 1C00 84 7F ANDA #$7F 1C02 26 0D BNE $1C11 <- <$48をカウントアップして128回に1回しか上がらない? DURが1減ったタイミングで128回に1度? 1C04 96 51 LDA <$51 <- 爆弾による自爆の場合 <$51 は1.通常のダメージだと0。 1C06 26 09 BNE $1C11 1C08 96 3C LDA <$3C <- DUR Max読み込み 1C0A 81 FF CMPA #$FF 1C0C 27 03 BEQ $1C11 <- オーバーフロー防止 1C0E 4C INCA <- Increment DUR Max 1C0F 97 3C STA <$3C <- DUR Max書き込み
ちょっとあまりにも成長が遅い。本来であれば普通にゲームを進めて最終ステージに達するころDUR Maxも上限に達するぐらいに調整するべきだと思うけど、まともにやるとほとんど成長しない。というか、なるべくダメージを受けないように工夫しながら進めるとぜんぜん成長しない。工夫するほど不利になるのはゲームバランス的にどうかと思う。1987年当時だと今のようにゲームバランスに関する考え方が固まってなかった。
なお、DURの現在値はだまっていれば回復するが、ひどく回復が遅い。成長すると回復も速くなるかと思ったらまったくそんなことはなくて、カウンタ <$24 (メインループで毎回1ずつアップ) の値と$3FのANDを取って0のとき回復。だからメインループ64回に1度のペースで回復。大雑把に1秒につき2回復。これもあまりにも遅すぎる。戦闘のあと平気で30秒程度の待ち時間が発生するのはあまりにも苦痛だった。これも1987年のゲームだからこれでいいと思ったんだろうな。
以下はDUR現在値回復部分の逆アセンブル。
05E5 96 24 LDA <$24 <- カウンタ 05E7 84 3F ANDA #$3F 05E9 26 0E BNE $05F9 <- 64回に1度通過 05EB 96 38 LDA <$38 <- DUR現在値 05ED 91 3C CMPA <$3C 05EF 24 01 BCC $05F2 <- まだ回復の余地がある場合通過 05F1 4C INCA <- 1回復 05F2 97 38 STA <$38 <- DUR現在値書き込み
全マップ
2D Retro Map Toolを利用して作成した全マップ(クリックで拡大)。なお、Death Forceの地下のマッピングに対応するためにツールに透明色機能を追加した。機能追加版は近日公開の予定。
プレイガイド
序盤
ゲームの大まかな流れとしては、武器と封印を一通り回収してラスボスを倒して最後に自爆するだけ。隠し階段をいくつか見つけなくてはならない以外はとくに謎解きのような要素は無い。隠し階段は上のマップに書いてあるのでそれを参照してもらえればわかると思う。
このゲームはロールプレイングな割には最初いくら敵を倒してもプレイヤーキャラクターが強くならない。このゲームではプレイヤーキャラクターを強化するには、アイテムを取ってLIF, WEP, MOVの上限を上げ、ダメージを何度も受けてDURの上限を上げなくてはならない。LIF, WEP, MOVの上限を上げるアイテムはそれぞれL-MAX, W-MAX, M-MAXで、ボスキャラを倒すとひと手に入る。どの種類が出るのかはランダム。
また、KEYというアイテムがあり、持っていないと通行できない箇所が多数あるが、よりによって最大5個までしか持てない。たくさん備蓄してから前進ということはできなくて、補給可能な地点でマメに補給しなくてはならない。1987年当時のゲームということを考慮しても、相当な忍耐力を要求される。
なお、XM7でプレイする場合はメインシステムの$0000~$007Fのダンプを常に表示しておくと、あと何度ダメージを受ければDUR Maxが1上がるのか、いくつKEYを持っているか、自分がいるマップのIDなどを確認できるのでややストレスが減る。$0000~$007Fのうち、意味を解明したものについては下の解析結果抜粋に書いたので参考になると思う。
ゲーム開始直後は、まずLIF、MOVを温存しながらKEYを貯める。同様の作業はゲーム中何度も必要になる。それには、開始地点から一枚右のマップ(マップ$01)、とそのすぐ下のマップ(マップ$11)を行き来しながらひたすら雑魚(大きな方がアイテムを落とす)を倒してはアイテムを回収する。LIFが減ってきたら、マップ$11は壁に隠れているけれどマップ右上の方にLIFを回復するアイテムL-PACが何度でも復活するので、補充できる。
このゲームのマップには、入るたびに敵・アイテムが回復するマップと、いったん敵を倒してアイテムを回収してしまったら復活しないマップと二種類ある。敵が復活するマップではアイテムも復活する。例えばL-PACが何度でも回復するマップを利用するとLIFを一気に回復することができる。ただし、KEYを消費するマップに入ると自動的にKEYがひとつ使われてしまうので、LIFを復活させようと思って何度も出入りしているうちにKEYがゼロになっていて脱出不可能になるようなパターンがゲーム中かなり多いのでKEYを消費するマップは避けた方がいい。もちろん入るたびにKEYを取ることができるマップもあるので、そういうマップだと仮に入った瞬間ひとつKEYを消費したとしても、その分補給できるので、KEYの消耗を気にせず何度も出入りして補給に使うことができる。なお、敵・アイテムが復活するマップではボスも復活するので弱いボスが復活するマップで何度も倒してLIF, MOV, WEPの上限を鍛えることができる。
KEYがたまったら、マップ$21で8方向ショットを回収する。この武器は以後かなり重宝する。なお、敵を全滅させてしまうと8方向ショットを取った途端にボス戦(青と白のさわやかな色をしているので勝手に「ミント」と命名)に突入する。直接攻撃は避けて少し離れた位置から銃弾を連続で浴びせると多分勝てると思うが、ボス戦を回避したければ雑魚を全滅させずに8方向ショットだけ回収して脱出してくればいい。
8方向ショットを回収したら、地下経由でマップ$03に行く。ここにはアイテムを落とす敵が多い。全滅させてしまうとボス戦に入ってしまうが、全滅させなければマップから出て入りなおしてまた倒すというのを繰り返すことで、LIF, MOV, WEPを補給できる。至近距離から8方向ショットを撃つと割と簡単に倒せる。なお、補給しながらなるべく敵の攻撃を受けるようにしていると非常にゆっくりDURの上限が上がってくる。DURが表示で05ぐらいになったらボス戦にも勝てるようになってくる。マップ$03にはボスが2匹潜んでいて、雑魚を全滅させると2連戦。ただし、どちらも弱いので中央直下から8方向ショットを撃つと弾が8発のうち3発命中する。この手で結構倒せる。倒すとL-MAX, M-MAX, W-MAXのうちどれかが手に入るので、何度も倒すことでLIF、MOV、WEPの上限をほぼ限界まで引き上げることができる。
この作業は序盤でやってしまった方がいい。後半でもできるけど、序盤でやっておけば後半が楽になる。ここで2~3時間ひたすらボスと雑魚を倒しまくって強化したら、次は誘導弾、爆弾、火炎放射器の強奪作戦。
誘導弾・爆弾・火炎放射器強奪作戦
2~3時間ひたすらプレイヤーキャラを強化したら、ぼちぼち誘導弾・爆弾・火炎放射器強奪作戦が決行できるようになる。地下経由でステージ8に向かう。ステージ8ではマップ0x62から地上に出る。出たマップではボス2連戦だがどちらも弱い。なんだったら地上と地下を往復して何度も倒してもいいぐらい。
マップ$62からひとつ上のマップ(マップ$52)でアイテムをすべて回収するとボス戦。このボスが強い。勝手に「水筒」と命名。とにかく、最初は直接攻撃しながら8方向ショットを撃ちまくる作戦でなんとか倒す。倒したら、マップ$52から地下に潜って、誘導弾を回収してくる。誘導弾を回収してしまえば水筒は割と簡単に倒せるようになる。壁の陰に隠れて体半分ぐらい出るようにして誘導弾を撃ちまくる。壁に隠れていれば水筒からの攻撃はほとんど当たらないので誘導弾を撃っていればそのうち勝てる。強いボスは、以後壁に隠れて誘導弾撃ちまくり作戦でほとんど勝てる。実はラスボス戦でも同じ手が通用する。
誘導弾を回収したらマップ$62まで戻って、地下に潜る。そして、適当に途中補給しながら南下してステージ7に向かう。ステージ7ではマップ$A0から地上に出る。そして、なんかして$B0で火炎放射器を回収。なお、ボスが強いので雑魚は必ず一匹残すようにして先に進む。
マップ$C1では、マップ右側から下のマップ$D1に入る。マップ$D1ではボス2連戦を避けられない。最初のボスが「目玉焼き」次のボスが「ピエロ」。どっちも強い。ここは、ホバー移動で素早く階段の壁の背後に隠れて誘導弾を撃ちまくる。
うまいこと倒せたらマップ$C0から地下へ。マップ$C0地下で重要なことがひとつ。ひとつは、必ずKEYをひとつは持って入る。もうひとつは、扉の背後にKEYが2個隠れているので必ず回収すること。地下でマップ$C0と$B0を往復しながらKEYを5個まで回復させることも可能。とりあえずKEYを2個回収したら、マップ$D0で爆弾を回収。この爆弾は重要で、エンディングを見るためにはラスボスを倒した後で爆弾で自爆しなくてはならない。
これで、弱いボス、ミント、銀、緑は至近距離から8方向ショット、それ以外の強いボスは壁の背後から誘導弾でほぼ勝てるようになる。
封印の回収
あとは封印を15個回収してボス戦に向かう。これは、マップを見てもらえればどこに封印があるか全部書いた。ただし、封印は順番通りにしか取れないので、回る順番に注意が必要。
また、武器も一応一通り回収した方が良さそう。武器をすべて持たない状態でラスボスに勝てるかは未確認。これもマップに回収できる場所を書いたので参考になると思う。
DUR Max特訓
封印と武器をすべて回収したら、あとはラスボス戦、と行きたいところだがDUR Maxを鍛えておかないとぜんぜん歯が立たない。このゲームは128回ダメージを受けることでやっとDUR Maxの内部値が1上がるという鬼仕様。なお、画面上の1は内部値の2に相当するから、画面表示のMaxを1上げるためには256回ダメージを受けなくてはならない。
いろいろ試した範囲で最も効率よくDUR Maxを鍛えることができるのはマップ$B5。雑魚(「エビ」と命名)が4匹出る他、L-PACも2個回収できる。LIFが厳しくなったら右の画面にもL-PACが何度でも復活するからLIFの回復には困らない。WEPが減ったら少し下に行ったところの隠し階段から地下に降りると何度でもW-PACを3個回収できる。
この4匹の倒し方は、なるべく横に4匹並んだ状態で8方向ショットを撃ちまくる。うまいこと行くと4匹すべてに弾が命中して少ないWEPの消費で4匹を倒すことができる。攻撃するとエビも反撃してくるのでダメージを受けるのでこれを数時間続けることでDUR Maxを引き上げることができる。
1987年のゲームなので、この作業は数時間繰り返さなくてはならない。まさに忍耐力が鍛えられるゲームと言える。
ラスボス戦
DUR Maxは画面表示で最低でも50ぐらいは欲しい。そのぐらいまで鍛えたら、いよいよラスボス戦。ラスボス戦に行き方はマップ上に緑色の線で示した。
しかし、最終ステージ、ステージ9の敵の猛攻は突破できてもかなりのダメージを受けてしまう。ラスボス戦前、最後に補給できるのはマップ$7B。ここは、ボス「緑」が入るたびに復活する。しかし、マップ$7Bから唯一出ることができるのはマップ$8B。ピエロ2連戦でかなりダメージを受ける。しかし、L-PACをすべて回収して緑を倒した状態で、F5キーでセーブ。そしてF1キーでロードすると、なんと、同じ部屋から出ることなくL-PACが復活して再度緑を倒すことができる。緑はボスの中でも弱い方だからここまで到達するキャラクタならば簡単に倒すことができる。そして、L-MAX, M-MAX, W-MAXのうちのどれかを落とす。これらのアイテムは、上限を上げる以外にLIF, MOV, WEPの値を回復させる効果もあるので、ここでセーブとロードを繰り返すことで簡単にステートを回復できる。なお、セーブする前にじっとしてDURを回復するのを忘れないこと。
そして、最後のラスボス戦。部屋に入ると、雑魚(「ブラックベリー」と命名)が弾を連射してくる。動きが速くて誘導弾はほとんど当たらないので、爆弾や8方向ショット、直接攻撃を適当に使って全滅させると、まず普通ボス戦(ピエロ)。ピエロを倒すと、ラスボス戦。ただし、両目が開いてるだけであとは見た目はただのピエロ。グラフィックデザインに疲れたんだろうか。
晴れてラスボスを倒したら、爆弾で自爆するとエンディング。
解析結果抜粋
メインループ
この手の解析では、デバッガで止めてコールスタックをたどっていくとそのうちメインループが見つかるので足掛かりにする。
$230~$24E メインループ前の初期化?
$251~$277 メインループの模様
$279~$28B PAUSE解除待ちループ
メインループ
0251 8D 26 BSR $0279 <- ESCを押すとPAUSE解除までループ 0253 BD 22 0B JSR $220B 0256 25 D8 BCS $0230 0258 8E 00 00 LDX #$0000 025B 9F 0E STX <$0E 025D BD 0A 5F JSR $0A5F 0260 BD 02 8E JSR $028E 0263 BD 02 B6 JSR $02B6 <- 移動。この中でアイテム取得も処理。 0266 BD 15 E4 JSR $15E4 0269 BD 14 D4 JSR $14D4 026C BD 0A 28 JSR $0A28 026F BD 21 29 JSR $2129 0272 BD 2A 4C JSR $2A4C 0275 0C 24 INC <$24 0277 20 D8 BRA $0251
マップIDとステージID対応表
Addr +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F Sum 7D00 : 00 00 00 00 00 00 01 01 01 01 01 02 02 02 02 02 :0F ................ 7D10 : 00 00 00 00 00 00 01 01 01 01 01 02 02 02 02 02 :0F ................ 7D20 : 00 00 00 00 00 00 01 01 01 01 01 01 02 02 02 02 :0E ................ 7D30 : 00 00 00 00 00 00 01 01 01 01 01 01 02 02 02 02 :0E ................ 7D40 : 00 00 00 00 00 00 09 09 09 01 01 01 03 03 03 03 :2A ................ 7D50 : 08 08 08 08 08 08 09 09 09 01 01 01 03 03 03 03 :5A ................ 7D60 : 08 08 08 08 08 08 09 09 09 09 09 09 03 03 03 03 :72 ................ 7D70 : 08 08 08 08 08 09 09 09 09 09 09 09 03 03 03 03 :73 ................ 7D80 : 07 07 07 08 08 09 09 09 09 09 09 09 03 03 03 03 :70 ................ 7D90 : 07 07 07 08 08 09 09 09 09 04 04 04 03 03 03 03 :61 ................ 7DA0 : 07 07 07 07 06 06 06 06 04 04 04 04 04 04 03 03 :52 ................ 7DB0 : 07 07 07 07 06 06 06 06 04 04 04 04 04 04 04 04 :54 ................ 7DC0 : 07 07 07 07 06 06 06 06 05 05 04 04 04 04 04 04 :56 ................ 7DD0 : 07 07 07 06 06 06 06 06 05 05 04 04 04 04 04 04 :55 ................ 7DE0 : 07 07 07 06 06 06 06 05 05 05 05 05 05 05 05 05 :5A ................ 7DF0 : 07 07 07 06 06 06 06 05 05 05 05 05 05 05 05 05 :5A ................ ---------------------------------------------------------- ---------------- Sum : 50 50 50 4F 4C 4F 5E 5C 56 41 3F 41 34 34 33 33 :79
これにより、大雑把に、
Stage 0 Stage 1 Stage 2 Stage 8 Stage 9 Stage 3 Stage 7 Stage 6 Stage 4 Stage 5
であることがわかる。あれ?Stage 5ってあったっけ?
基本的な変数
メインCPUアドレス空間の$0000~$00FF (全RAM空間上の$30000~$300FF)
DP=0x00で、 +00 キーというかジョイスティックコードに対応? +01 自機の向き (0が上。時計回りに8方向。) +02 画面内X座標 自機が右端で$25 +03 画面内Y座標 自機が下橋で$22 +04 画面内X座標 +05 画面内Y座標 +06 ジョイスティックコードのコピー +07 <$06の値のコピーと0と交互 +08 +09 突き状態 3で最も伸びた状態 +0A +0B 移動のカウンタらしい。0のとき一歩進む。通常歩行では一歩進むと3になる。ホバー時は2。 +0C ボタンを押している間 <$0Dの内容と0と交互に切り替わるようだ。射撃リピート用? +0D 最後のボタン状態(射撃、突きのイベント検出用) +0E タイマー上位 +0F タイマー下位 +10 +11 +12 +13 汎用のカウンタ。 +14 +15 取ったアイテムのコード +16 +17 $0A92~$0BE1のループのカウンタ。敵キャラのループぽい。 +18 +19 +1A +1B +1C +1D ステージID +1E [$7E00+MapID] +1F +20 ビット0は地上/地下フラグ。ビット1は地上/地下の行き来があった瞬間に1になりマップ切り替えが必要フラグ。 切り替え後ビット1はすぐ0に戻る。 +21 +22 MAP ID。左上が$00。下位4ビットがX,上位4ビットがY。 +23 +24 メインループで1ずつカウントアップ +25 マップ切り替え(スクロール)で、$7800[20*(Y/4)+(X/2)]&0xC0をストアしてる。 +26 マップデータ構造体へのポインタ マップ切り替えルーチンでセット +27 +28 +29 +2A +2B +2C +2D 画面上の残敵数。 +2E これが0じゃないと階段に入れなかったり(0x01?)、ボス倒すまで部屋から出れなくなったり(0x80)。 +2F ステージ切れ目で更新の模様。意味は未確認。 +30 ステージ切れ目で更新の模様。意味は未確認。 +31 +32 +33 +34 最後に突きまたは射撃してからのカウンタ +35 LIF +36 MOV +37 WEP +38 DUR +39 LIF Max +3A MOV Max +3B WEP Max +3C DUR Max +3D Key残量 +3E 武器フラグ +3F +40 シール個数 +41 +42 多分最後に取ったアイテムの種類、メッセージ全般?USE KEYもこれ使ってる。 +43 GET **** 表示のカウンタだと思う。 +44 +45 +46 +47 +48 DUR減少カウンタらしい。128回に一度だけDUR Maxが上がるチャンスがある模様。 これ注意してるとDUR Maxが上がるタイミングがわかる。 シールが15個そろったときIncrement。ん?シール15個でひとつだけ? +49 ステージ切れ目で更新の模様 +4A ステージ切れ目で更新の模様 +4B ステージ切れ目で更新の模様 +4C ステージ切れ目で更新の模様 +4D ステージ切れ目で更新の模様 +4E ステージ切れ目で更新の模様 +4F ステージ切れ目で更新の模様
解明したサブルーチン
$0694 敵キャラ/アイテムとの衝突判定 $0A14 メッセージ表示。<$42がメッセージコード <$43が多分表示残り時間 Aがアイテム個数? $0E87 ステージ切り替え処理。$245B(マップ切り替え)から呼び出し $2414 D=20*(Y/4)+(X/2) 画面上座標から何かのオフセット $241F D=$4500+B*#$28+A そのまま TFR D,X として、$4500からのアイテムシンボルとか敵キャラシンボルの取得用。 $2429 マップ切り替え $27A0 マップ切り替え後移動可能範囲の更新
テーブル等
特に、$7C00からの入り口座標テーブルは役に立った。それと隠れたアイテム探しには$4500~$4B3Fのマップが役に立った。
$2109~ 移動ベクトルのテーブルっぽい。 $2199~ 移動ベクトルのテーブルの模様。$2199+(ジョイスティックコード&0x0F)。$FFは多分ありえない方向。 $3200~ なにかのパラメータ。多分敵関係。 $328A~ 取ったシール (1バイトずつ。0か1。) $3299~ 多分敵のリストだと思うんだけど。 $3330~ マップっぽいんだけどな。短すぎる。 $38C0~ BGMのワークっぽい。BGMと連動して値が変わってるバイトがいくつかある。 $4500~$4B3F 敵キャラとアイテム当たり判定用のマップ '0' が自機。$FFが何もない空間。 $00~$2Fは敵キャラ。$30~$7Fはアイテム。 $4C00~$4F1F? 移動判定用のマップ。多分$02が入り口。 $7800~ マップの何からしいんだけど、何であるかが不明。鍵によって変化してる。 $7C00~ 最初1バイトが要素数。続く4バイト単位が階段テーブル。MapID, X, Y, 向き $7D00~$7DFF マップID→ステージID対応表
階段テーブル
Stage 06 階段
A4 05 0B 01 A7 1C 07 04 A7 17 1B 01 C7 05 17 01 D6 08 07 01 D7 05 17 01 E6 08 07 01 F6 08 04 01 このY座標バグじゃない?
Stage 07 階段
5F 07 0F 01 バグ。マップ5FはStage 07の範囲外。 82 07 0B 04 90 0D 15 01 90 1B 15 01 92 05 13 04 A0 05 03 04 A0 05 16 03 A3 05 0F 04 A3 1F 1B 04 B2 03 0D 01 C0 05 0F 04 C1 21 13 04 C2 17 09 01 C3 07 08 01 F0 0F 13 01
Stage 08階段
70 07 0F 04 71 13 13 04 71 23 0F 04 51 0B 13 04 52 23 17 04 62 07 13 04 72 1B 0B 01 63 0B 13 04 53 0B 13 04 64 17 13 04 54 19 0B 04 74 1F 0F 04 84 03 1B 04 94 17 13 04 65 21 0B 01
Stage 09階段
48 0F 0B 01 56 1B 09 04 57 19 0F 04 バグってて入り口は壁の中。 58 0E 07 04 69 1E 13 04 入ったら出口が壁の中で出られん。 6A 0F 14 04 6B 1E 13 04 75 05 0C 01 77 17 1C 01 77 1C 07 04 78 07 1F 01 79 19 0D 01 入れない。クリア可能バージョンディスクでは 79 19 0B 01 なので普通に入れる 7A 07 1D 01 7B 05 01 04 85 08 10 04 バグ?座標がずれてて入れない。階段のグラフィックスはある。 87 1E 0F 04 88 13 0F 04 89 05 16 04 出口が壁の中で出られん。 8A 07 1D 01 8B 05 07 01
アイテム取得処理
アイテム取得は$0694からのルーチンで、敵キャラとの衝突もこの中で判定している。なお、敵キャラとの衝突はとくにダメージなどは受けない。単に進めないだけ。
JSR $15A8で多分実際取ったアイテムのコードを取得して、11より上ならシール、以下なら通常アイテムとして処理している模様。シールだと正しい順に取らないと取れないから。これは、取りこぼしがあったらわかるように配慮しているものと思う。DUR Maxでプレイヤーを拷問しておきながら、この点は親切。通常アイテムの場合、アイテムコードからジャンプテーブル ($076B~) のアドレスに飛ぶ。そうなんだよ。6809ってBSR [A,X]ができたら良かったんだけどJSRなんだよな。あるいはレジスタをオフセットとしてBRAができたらなあ。と、ゲームとは関係なくテーブルジャンプを見るたびに思ってしまう。
06A9 BD 15 A8 JSR $15A8 <- 多分この中で実際どのアイテム取ったかを判定。 06AC 81 0B CMPA #$0B 06AE 22 22 BHI $06D2 <- 11より上だとシール 06B0 C6 01 LDB #$01 06B2 96 15 LDA <$15 06B4 BD 15 A8 JSR $15A8 <- 多分ここでアイテム表示消してるんだと思うんだよね 06B7 97 15 STA <$15 06B9 8E 07 6B LDX #$076B 06BC 48 LSLA 06BD AD 96 JSR [A,X]
ジャンプテーブルは以下の通り。
0 0783 KEY 1 0790 WEAPON 0 8-Shot 2 0790 WEAPON 1 Guided 3 0790 WEAPON 2 Framethrower 4 0790 WEAPON 3 Boomerang 5 0790 WEAPON 4 BOMB 6 079D L-PAC 7 079D M-PAC 8 079D W-PAC 9 07CD L-MAX 10 07CD M-MAX 11 07CD W-MAX
またシールの取得は以下の部分。CとかC++で書いてるときは、シール個数とシールのフラグと別に持つのが嫌だから多分フラグだけにすると思うんだけど。
06D2 8E 32 8A LDX #$328A <- 取ったシールのフラグ(1バイトにつきシールひとつ) 06D5 80 0C SUBA #$0C <- アイテム12がシール0版だから、アイテムコードから12を引く。 06D7 27 08 BEQ $06E1 <- 0番のシールはいつでも取れる 06D9 4A DECA <- ひとつ前のシールをチェックするためにA=A-1 06DA E6 86 LDB A,X 06DC 10 27 00 28 LBEQ $0708 <- DECAによってシール[x-1]を既に持ってるかチェック 06E0 4C INCA <- Aの値元に戻す 06E1 6C 86 INC A,X <- シール取ったフラグ立てる 06E3 0C 40 INC <$40 <- シール個数増やす
ステージ切り替え処理
$7C00からの内容が切り替わることでステージが切り替わったことがわかる。ボス戦後も呼ばれてるようだ。
ここで重要なのが$7C00からの階段テーブルの読み込み。コピープロテクトの誤爆によってによってラスボス戦に進出できないとする説が正しいとすれば、高い確率でここでプロテクトのチェックが入って最終ステージの階段の位置をずらすと考えられる。
しかし、このルーチンの最後に階段テーブルを$7C00~に読み込んですぐPULS B,PCで抜けている。 したがって、読み込み後テーブルには何も手が加えられていない。
もちろんまったく別の場所で特別な処理をしている可能性もゼロでは無いけど。例えばここまで解析される可能性まで考慮していたとするならば、あえて階段テーブル読み込みとその修正を離れた場所で実行する可能性も考えられる。しかし、エミュレータの無かった当時そこまで考えていただろうか?
0E87 34 04 PSHS B <- BレジスタがステージID 0E89 86 02 LDA #$02 0E8B B7 FD 90 STA $FD90 0E8E 8E 0F 31 LDX #$0F31 0E91 E6 85 LDB B,X 0E93 8E 30 C3 LDX #$30C3 0E96 58 LSLB 0E97 EC 85 LDD B,X 0E99 10 8E 00 60 LDY #$0060 0E9D 8E 90 00 LDX #$9000 0EA0 BD 29 91 JSR $2991 0EA3 E6 E4 LDB ,S 0EA5 58 LSLB 0EA6 58 LSLB 0EA7 58 LSLB 0EA8 EB E4 ADDB ,S <- 多分Bがステージ番号。8倍して1倍を足してるから9倍。 0EAA 8E 0E D7 LDX #$0ED7 <- [0ED7~] 9バイトごとのテーブルのようだ 0EAD 3A ABX 0EAE EC 84 LDD ,X 0EB0 DD 49 STD <$49 0EB2 EC 02 LDD 2,X 0EB4 DD 4B STD <$4B 0EB6 EC 04 LDD 4,X 0EB8 DD 4D STD <$4D 0EBA EC 06 LDD 6,X 0EBC 97 4F STA <$4F 0EBE D7 2F STB <$2F 0EC0 A6 08 LDA 8,X 0EC2 97 30 STA <$30 0EC4 8E 31 06 LDX #$3106 0EC7 EC 84 LDD ,X 0EC9 EB E4 ADDB ,S <- Dレジスタには、トラックとセクタが入る。 0ECB 10 8E 00 01 LDY #$0001 0ECF 8E 7C 00 LDX #$7C00 <- 階段テーブル読み込み先 0ED2 BD 29 91 JSR $2991 <- セクタ読み込み 0ED5 35 84 PULS B,PC [0ED7~] 02 02 03 03 03 04 05 00 00 02 02 03 03 03 04 05 01 00 03 02 03 03 03 05 05 01 01 03 02 03 03 04 05 06 01 01 03 03 03 04 04 05 06 02 01 04 03 04 04 04 05 06 02 02 04 03 04 04 05 06 07 03 02 05 04 04 05 05 06 07 03 03 06 05 05 05 06 07 09 04 03 07 06 06 06 06 07 0A 06 05
ボス戦
ボス戦は通常モードとメインループは共有。ボス戦用に別なループは存在しない。ボスのいるマップで敵を全滅させてしまうと、ボスを倒すまで出られない。ボス発生条件は敵全滅かつ全アイテム取得。敵を全滅させてしまうと、アイテムが絶対取れない場所に存在するため二度と脱出不能になる箇所が多数あった。(あ、だけどだからセーブしてロードするとフィールドの敵が復活するのか?それはまだ試してなかったけど、もういいや。)
ボスがいるか、いないか、または何匹目のボスか(1画面に最大2匹のボスがいる)は、<$2Eに書いてある。$80以上の場合、ボスを倒すまで出られなくなる。ボスを倒す前の状態では <$2Eは01となり、画面から出入りできるが階段に入ることができない。その値をコントロールしているのは、以下の場所。
0F61 LDA #$80 STA <$2E アイテム全取得→ボス戦まで出られない状態 0FDF LDA #$81 STA <$2E ボス発生時、$80->$81 106B LDA #$01 STA <$32 STA <$2E 画面から出られるけど階段に入れない状態 11D5 LDA #$82 STA <$2E 2匹目ボスのようだ。 11FE CLRA STA <$2E STA <$32 どうも<$32と<$2Eは連動してるのか? <$32の意味は未確認。
ボス発生ルーチンは$0F69~
0F69 96 2E LDA <$2E 0F6B 10 2A 01 19 LBPL $1088 <- ボスがいないならスキップ。 0F6F 81 80 CMPA #$80 0F71 10 22 00 6E LBHI $0FE3 <- 既にボス戦中? 0F75 96 23 LDA <$23 0F77 9B 2B ADDA <$2B 0F79 9B 52 ADDA <$52 >0F7B 10 26 FC 66 LBNE $0BE5 <- <$23, <$2B, <$52の合計が0以外でもボス発生しないが、意味は未解明 0F7F F6 32 99 LDB $3299 <- ボスのID 0: ミント 1: 緑の脳みそ 2: 銀の脳みそ 3: 目玉焼き 4: 水筒 5: ロボ 6: タコ 7: ピエロ ラスボス戦1匹目 8: テントウムシ 9: ピエロ A: ピエロ ラスボス戦2匹目 0F82 D7 13 STB <$13
なお、敵の名前は僕が勝手に命名した。おもしろいのは、デバッガで$0F82で止めてBレジスタの値を書き換えると好きなボスを発生させられること。$0Bにしたところグラフィックスが崩壊した点、またラスボス線では値が$0Aだったこと、テーブルが11個分しかないことなどを考えるとボスは$00~$0Aの合計11種類と思われる。ラスボス($0A)以外にピエロが二種類いるのだが、ひょっとすると気が付かなかっただけで両方と対戦していたか、あるいは9番のピエロは未使用かもしれない。
なお、ボスのデータはふたつのテーブルに書いてある模様。$30F0からの2バイト単位のテーブルと$30CFからの3バイト単位のテーブル。意味は未解明。
$30CF 0 1F 01 16 1 20 07 16 2 21 0D 16 3 43 02 24 4 45 06 16 5 46 0C 24 6 48 10 24 7 4B 04 16 8 4D 0A 24 9 4B 04 16 A 14 03 16 $30F0 0 3C 0B 1 46 1B 2 64 1B 3 78 05 4 96 0F 5 AA 05 6 BE 05 7 C8 0F 8 BE 05 9 D2 1B A E6 1F
※パッケージやゲーム画像などの著作権は著作者に帰属します。
特定非営利活動法人ゲーム保存協会
ライター:山川 総司(賛助会員)
ホームページ:http://www.ysflight.com