果たしてFM77AV用Death Forceはクリアできるのか?

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を利用してクリアを目指してみた。

使用したディスク

AディスクBディスク共通でディスク裏面に6C184F 2Dと刻印がある

結論

いきなり結論を書いてしまうと、噂はおおむね本当であった可能性が高い。クリアが不可能なバージョンと可能なバージョンが存在することが確認できた。

まず、ヤフオクで今回落としたバージョンではクリアは無理。難しいからではなく、ラスボスに通じる通路への入口に入ることができない。解析した結果、その入り口は、座標 ($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の上限をほぼ限界まで引き上げることができる。

序盤の修行地点。マップ$03

この作業は序盤でやってしまった方がいい。後半でもできるけど、序盤でやっておけば後半が楽になる。ここで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年のゲームなので、この作業は数時間繰り返さなくてはならない。まさに忍耐力が鍛えられるゲームと言える。

マップ$B5

ラスボス戦

DUR Maxは画面表示で最低でも50ぐらいは欲しい。そのぐらいまで鍛えたら、いよいよラスボス戦。ラスボス戦に行き方はマップ上に緑色の線で示した。

しかし、最終ステージ、ステージ9の敵の猛攻は突破できてもかなりのダメージを受けてしまう。ラスボス戦前、最後に補給できるのはマップ$7B。ここは、ボス「緑」が入るたびに復活する。しかし、マップ$7Bから唯一出ることができるのはマップ$8B。ピエロ2連戦でかなりダメージを受ける。しかし、L-PACをすべて回収して緑を倒した状態で、F5キーでセーブ。そしてF1キーでロードすると、なんと、同じ部屋から出ることなくL-PACが復活して再度緑を倒すことができる。緑はボスの中でも弱い方だからここまで到達するキャラクタならば簡単に倒すことができる。そして、L-MAX, M-MAX, W-MAXのうちのどれかを落とす。これらのアイテムは、上限を上げる以外にLIF, MOV, WEPの値を回復させる効果もあるので、ここでセーブとロードを繰り返すことで簡単にステートを回復できる。なお、セーブする前にじっとしてDURを回復するのを忘れないこと。

最後の補給地点。MAP$7B

そして、最後のラスボス戦。部屋に入ると、雑魚(「ブラックベリー」と命名)が弾を連射してくる。動きが速くて誘導弾はほとんど当たらないので、爆弾や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

※パッケージやゲーム画像などの著作権は著作者に帰属します。

ゲーム保存協会 Game Preservation Society特定非営利活動法人ゲーム保存協会
ライター:山川 総司(賛助会員)
ホームページ:http://www.ysflight.com

投稿カテゴリー:投稿記事

コメントを残す