JavaScriptを有効にすると、ここにメニューを表示します。

FM TOWNSエミュレータ「津軽」で「大航海時代」をプレイする

ysflight.com

はじめに

どうして僕はレトロゲームをプレイするのか、と、考えてみると、自分がFM-7、FM77AVを使っていた当時はゲームを買いたくても何本も買うだけの財力が無く、FM TOWNSを使っていた当時は3次元グラフィックスとCプログラミングが楽しくてあまりゲームを買わなかった。楽しそうだと思ったゲームはたくさんあったのだが、遊んでみたいと思ったままになっていたゲームがあまりにも多すぎる。最新のゲームは技術の進歩もすごいし、多分遊んだら楽しいのだろうとは思うが、その前に自分の中でやりのこしたことになっているゲームが多すぎる。

「大航海時代」はそんなゲームのうちのひとつだった。今をさかのぼること30年ぐらい、当時青森県立弘前高校の地学準備室に置いてあったPC8801(だったと思う)で後輩が楽しそうに「大航海時代」をプレイしていたのだが、僕は指をくわえて見ているだけだった。ところが、大学に入ると自分でフライトシミュレータを作りたいという欲求がゲームをプレイしたい欲求よりも上回っていたもんで、「大航海時代」FM TOWNS用が出ていたことすら知らなかった。それが、去年ヤフオクで「大航海時代」を落とすことができたので、そのうちプレイしてみようと思っていた。

とはいえ、プレイを始めたらしばらく時間がかかりそうだから、まとまった時間ができるのを待っていて、ついにこの冬休み、30年の時を経て「大航海時代」を我がFM TOWNSエミュレータ「津軽」上でプレイしてみた。

なお、CD-ROM上のVER.DATによると、Ver 1.00 Rel 1.00、1990 10.31とのこと。プレイしてみて、合計4点のバグに遭遇して、うちひとつはゲーム続行が不可能になる致命的なもので、ひとつはセーブデータから再開すればなんとか回避できそうなもの、残るふたつは誰かが通してプレイテストしていれば発見できたと思われるバグだった。そのうち3か所はパッチを書くことができた。パッチを書けなかった個所は、それほど致命的なものではなく、再現しようとしているのだが、再現できなくなってしまった。 しかし、このレベルのバグが残っているということは、おそらく出荷前に誰もFM TOWNS版の通しテストプレイをしなかっただろうと推測している。果たしてアップデート版が後にリリースされたのだろうか?

Contents

FM TOWNS版大航海時代1世界地図

プレイしてわかったのは、このゲーム、詳細地図が無いと宝探しが実質不可能じゃん。レトロマップツールを使ってマップを書きながら進めたが、世界全体の地図を作るにはものすごい手間がかかるし、しかも、内陸が見えなくてスカスカの地図になってしまう!と、いうわけで、コード解析して世界地図を作った。各ブロックのコードさえわかれば、途中までレトロマップツールで作ったビットマップと対応を取ることで、未踏破の部分の地図もビットマップで埋めることができるので、世界全体をカバーしている。 手元には1倍の地図があるけど大きすぎるので、1/6に縮小版。このマップさえあれば、宝探しの依頼が来ても大丈夫!1991年当時、世界地図が無くて挫折した人、今から再挑戦してみては?

なお、港の名前に(*)がついているのは補給のみ。


潮流方向、港名入りバージョン

潮流・港名無しバージョン

確認したこと

互換ROMで最後までプレイ可能

まずひとつ確認したのは、互換ROMの最新バージョンでエンディングまで通してプレイできるということ。FM TOWNS実機を所有していなくても、「大航海時代」のCD-ROMとフロッピーディスクがあればプレイ可能。なお、「津軽」で使うためにフロッピーディスクイメージを作らなくてはならないが、ストレートにディスクイメージを作る他、ディスクにコピープロテクトはかかっていないので、ファイルを取り出すことさえできれば、津軽のホスト・VM間ファイル転送機能を使ってディスクにファイルを書いてもいいし、ディスクイメージにファイルを読み書きするユーティリティを使えば使用可能なディスクイメージを作ることは可能。

ひっかかると続行不能になるバグがある

プレイ中、海賊船がぜんぜん出ない、という状況が発生。とうとうあきらめて最初からプレイ再開するものの、今度は、エミリオ提督討伐の勅命を果たした後、何度やってもクラッシュしてしまう現象が発生。この解析の詳細は下にまとめて書く。

続行不能とまで行かないが、リセットが必要になるバグがある

ゲーム後半、エンディングが近くなったとき、突然BGMの演奏は続くのに、艦隊がピタっと止まって、マウスにも反応が無くなるバグがある。この点も下に詳しく書く。

最終艦隊決戦後は一度セーブしてロードする必要がある

根本原因はまだ発見できていないが、(原因解明。パッチも成功)最終決戦後にそのまま国王に謁見に行くと、エンディングが始まらない。この問題は互換ROMを使っても実機MXから抜き出したROMを使っても発生した。

最終決戦勝利後、王女様救出のグラフィック(多分)が崩れて見えないのだが、このとき何かを破壊しているのではないか。グラフィックが崩れる現象は実機FM TOWNS 2MXでも確認したので、どうやら「大航海時代」のバグ。(原因解明。パッチも成功。)

スタックオーバーフロー問題

一度目のプレイ中、海賊討伐の勅命を果たした後、海賊船がまったく出なくなった。海賊討伐の勅命が出たということはぼちぼち海戦の機会が増えていくのかと思っていたのに、まったく機会が出ない。経験値を上げるために相当海賊を探し回ったのだが一隻も出ない。結局あきらめて最初からやりなおすことにした。ところろが、二度目のプレイ中、エミリオ提督討伐の勅命を果たした後、突然プログラムがクラッシュする。最初、互換ROMの問題かと思ったのだが、INT 20Hでプログラムを強制終了させようとしていることが判明。津軽のデバッガで調べたところ、スタックオーバーフローによりコードが破壊されていることが判明。さらに、スタックオーバーフローの原因は、無限再帰呼び出しによるものとわかった。

津軽のCPUコアに問題があるのか?エミュレーションが「大航海時代」を実行できるレベルではないのか?それとも、「大航海時代」のバグなのか?この問題を突き詰めて調べることにした。

コールスタックから無限再帰呼び出しが起こる関数は簡単に特定することができた。さらに、その関数にブレークポイントを設定することで、呼び出し前の関数を特定。無限再帰呼び出し前に、36(24h)x72(48h)のマップを作っていることまで確認した。横軸が緯度、縦軸が経度になっていて、FEhが航行可能領域、FFhが陸地とわかった。

DS:009883Cが地図データで、そのFEhを..に置き換えると、世界地図が見える。なお、経度は東経を単位としているらしいので、下では東西が反転した地図になっている。さらに、再帰呼び出しのたびに、起点からの距離が書き込まれていくことがわかった。このパターンはまず間違いなくパスファインディングとわかった。また、下のダンプは経路探索中で00が起点、FDhが目標点のようだ。実際の経路は逆にたどっているかもしれない。

  
FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. .. FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF FF .. FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF FF .. FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF FF .. FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. FF FF FF FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. FF FF FF FF FF FF FF FF FF .. FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF FF FF FF FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF FF FF FF FF FF FF FF FF FF FF FF .. FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF FF| FF FF FF FF FF FF FF FF FF FF FF .. .. .. FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. FF FF FF FF FF FF FF FF .. FF FF .. .. .. FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. FF .. .. FF FF FF FF FF FF FF FF .. .. .. FF FF FF .. FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. .. .. .. FF FF .. FF FF FF FF FF FF .. .. .. FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. FF| FF .. FF .. FF FF FF .. FF FF FF FF .. .. .. FF .. .. FF FF .. FF FF FF FF .. .. .. .. .. .. .. .. .. .. FF| FF .. .. FF .. FF .. .. FF .. .. .. .. .. .. .. .. FF FF FF .. .. FF FF FF .. .. .. .. .. .. .. .. .. .. FF| FF .. .. .. .. FF .. FF FF .. .. .. .. .. .. .. .. FF FF FF .. .. FF .. FF FF FF FF .. .. .. .. .. .. .. FF| FF .. .. .. .. .. FF FF .. .. .. .. .. .. .. .. .. .. FF FF .. FF .. FF FF FF FF FF FF FF FF FF .. FF .. FF| FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF .. .. .. .. FF FF FF FF FF FF FF FF .. FF .. FF| FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF .. FF FF FF FF FF FF FF FF FF .. .. .. .. .. FF| FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF FF FF FF FF FF FF .. .. .. .. .. .. FF| FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF FF FF FF FF .. .. .. .. .. .. .. .. FF| FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF FF .. .. .. .. .. .. .. .. .. .. .. FF| FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF FF FF FF .. .. .. 04 04 04 .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF FF FF .. .. 03 03 03 03 03 03 03 .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF .. .. .. .. FF FF| FF .. .. .. .. 03 02 02 02 02 02 03 .. .. FF .. FF .. .. .. .. .. .. .. .. .. .. .. .. FF .. .. .. .. FF FF| FF .. .. .. .. 03 02 01 01 01 02 03 .. FF FF .. FF FF .. .. .. .. .. .. .. .. .. .. .. FF FF .. .. .. FF FF| FF .. .. .. .. 03 02 01 00 FF FF 03 FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. .. .. 03 02 01 FF FF FD .. FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. .. .. 03 FF 02 FF .. .. FF FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. .. FF .. FF FF FF .. .. FF FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. FF .. .. FF FF .. .. .. .. FF FF FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. FF .. .. FF FF .. .. .. .. FF FF FF .. FF FF FF FF FF FF .. FF FF .. .. .. .. .. .. .. .. .. .. FF| FF .. .. FF FF .. FF FF .. .. .. .. .. .. .. .. FF FF FF FF FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. FF| FF .. .. .. FF FF FF FF .. .. .. FF FF FF FF .. .. FF FF FF FF FF FF FF .. FF FF FF .. .. .. .. .. .. FF FF| FF .. .. .. FF FF FF FF .. .. FF FF .. .. FF FF FF .. .. FF FF FF FF FF .. FF FF .. .. .. .. .. .. .. FF FF| FF .. .. .. FF FF FF .. FF FF FF FF FF FF .. .. FF FF FF .. FF FF FF FF .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. .. FF FF .. FF FF FF FF FF FF FF FF FF .. FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. FF| FF FF .. .. FF FF .. FF FF FF FF FF FF FF FF FF .. .. FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF FF .. .. FF FF FF FF FF FF FF FF FF .. FF FF .. .. FF .. .. .. FF FF .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. .. FF FF FF FF FF FF FF FF FF .. FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. FF .. .. FF .. .. FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. .. .. FF .. FF .. FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF .. FF FF| FF .. .. .. FF FF FF FF FF FF FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. .. FF FF FF FF FF FF FF FF FF .. FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. .. FF FF FF FF FF FF FF FF .. .. .. FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. .. FF FF FF FF FF FF FF FF FF FF FF .. FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF| FF .. FF FF FF FF FF FF FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF .. FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. FF FF FF FF FF FF FF FF .. .. FF .. FF FF FF FF FF .. .. FF FF .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. .. FF FF FF FF FF FF FF .. FF FF .. FF .. .. FF .. .. .. .. FF .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. .. FF FF FF FF FF FF FF .. FF .. FF FF .. .. .. .. .. FF FF .. .. .. .. .. FF FF FF FF .. .. .. .. FF| FF .. FF FF FF FF FF FF FF .. .. FF .. FF .. .. .. FF FF .. FF .. FF .. .. .. FF FF FF FF FF .. .. .. .. FF| FF .. .. FF FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. FF .. .. .. .. .. .. FF FF FF FF FF .. .. .. .. FF| FF .. .. FF FF FF FF FF FF FF FF .. FF .. .. .. .. .. .. .. .. FF .. .. .. FF FF FF FF FF .. .. .. .. .. FF| FF .. .. FF FF FF FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. FF .. .. FF FF FF FF FF FF .. .. .. .. FF| FF .. .. FF FF FF FF FF FF .. FF FF .. .. .. .. .. .. .. .. .. .. FF FF .. .. FF FF FF FF FF .. .. .. .. FF| FF .. .. FF FF .. FF FF .. .. FF .. .. .. .. .. .. .. .. .. .. .. .. FF .. FF FF FF FF FF FF .. .. .. .. FF| FF .. .. FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF .. .. FF FF FF FF FF FF .. .. .. FF| FF .. .. FF FF .. .. .. .. .. .. .. .. .. .. FF FF .. .. .. .. .. .. .. .. .. .. FF FF FF FF FF FF .. .. FF| FF FF .. FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF FF FF .. .. .. .. FF| FF .. .. FF FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF .. FF| FF .. .. FF FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF FF .. .. FF| FF .. .. FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF FF .. .. FF| FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF| FF .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. FF|

また、「大航海時代」の実行ファイルMAIN.EXPにはデバッグ用シンボルテーブルが書いてあることが判明、というか、これはずいぶん前に気が付いていた。そこで、津軽のデバッガに.EXPファイルのシンボルテーブルを認識させるようにした。これにより、無限再帰呼び出しの関数名が「SAITAN」であることがわかった。最短距離の経路探索ということだろう。

SAITAN関数は、引数に経度・緯度を受け取る。クラッシュ時は、ゴールの緯度がF5h、経度が47hであることがわかった。経度はぎりぎり範囲内だが、緯度は最大値を超える35を遥かに上回っている。このため、ゴール位置を示すFDhが世界地図の外に書かれてしまって、経路探索関数は永遠にゴールを探し続け、スタックオーバーフローを引き起こしていた。経度が0ならば緯度がオーバーフローしても、FDhはたまたま世界地図のバッファ内のどこかに書き込まれるのでスタックオーバーフローはしない。しかし、経度が最大値で、緯度も範囲を超えているため、世界地図にFDhが書き込まれなかった。

前後の逆アセンブルから、どうやらこの座標の以上には、次のふたつの構造体が関わっているらしいことがわかった。なお、艦隊構造体の最初の2バイトが経度・緯度。

0D90h~ 27バイト単位 21個の構造体の配列 艦隊構造体と呼ぶ (シンボルテーブルではKANTAIK) 02F4h~ 53バイト単位 20個の構造体の配列 経路構造体と呼ぶ (シンボルテーブルではCKANTAI)

どうやら、艦隊構造体の経度と緯度は、経路構造体のどこかからコピーされるようだ。47h,F5hのコピー元は以下の個所と特定できた。

DS:000006E3 16 1F 08 0B 04 33 00 F8 06 00 00 08 FE 07 FD 06 FC 05 FB 04 FA 03 F9 02 F8 01 F7 00 F6 47 F5 FF FF FF FF FF FF FF ^^^^^

エミリオ提督戦勝利直後のステートをロードしたところ、問題の艦隊(おそらく破ったエミリオ艦隊)の艦隊構造体の経度が0、緯度がFFhに初期化されることがわかった。 この経路構造体内のコピー元のデータはコース計算関数(SET_KOURO)内でセットしているらしい。この中で経路探索関数(SAITAN)を呼ぶ。クラッシュするときはこの関数が無限再帰呼び出しを起こす。どうやら、SET_KOUROは、出力として DS:22418h からのintの配列にステップごとのvx,vyを出すようだ。だから、(00h,FFh)からインクリメンタルに移動すると(47h,F5h)に到達してしまう可能性が発生する。また、02F4h~の経路構造体にはインクリメンタルに移動する経路の緯度、経度をストアするらしい。

問題の緯度FFhはどこから来たのか? 実は、経路構造体のダンプを見たとき違和感があったのだが、構造体+7は自身が持つ配列内へのポインタらしい。どうやら、艦隊が一ステップ移動するときこのポインタからの2バイトを艦隊構造体の先頭にコピーして、ポインタはインクリメントされるようだ。なのだが、破壊が起こる艦隊のポインタのみ自身の外側をポイントしている。他のステートファイルで調べたところ、このポインタが00000034hというぜんぜんとんでもないアドレスを指しているものもある。どうやらこのポインタの破壊が本質的な問題のようだ。

  
DS:000002F4 20 08 03 0C 04 00 00 07 03 00 00 23 09 22 0A 21 0B 20 0B 1F 0A 1F 09 20 08 FF 0B 1F 0A 1F 09 FF FF FF FF (All FF omitted) DS:00000329 24 08 0B 00 04 00 00 34 03 00 00 FF 09 1F 0A 20 0B 21 0A 22 09 23 09 24 08 FF 0B 1F 0A 1F 09 FF FF FF FF (All FF omitted) DS:0000035E 24 08 0B 00 02 04 01 69 03 00 00 FF 09 1F 0A 20 0B 21 0A 22 09 23 09 24 08 FF 0B 1F 0A 1F 09 FF FF FF FF (All FF omitted) DS:00000393 11 12 0B 01 00 10 00 9E 03 00 00 FF 12 FF 10 13 10 12 11 11 12 FF 0A 19 0B 18 0C 17 0D 16 0E FF FF FF FF (All FF omitted) DS:000003C8 11 12 0B 01 00 10 00 D3 03 00 00 FF 12 FF 0A 19 0B 18 0C 17 0D 16 0E 15 0F 14 10 13 10 12 11 FF FF FF FF (All FF omitted) DS:000003FD 28 09 05 0A 04 01 00 08 04 00 00 FF 0A 22 09 23 09 24 08 25 08 26 08 27 08 28 09 FF 08 18 07 FF FF FF FF (All FF omitted) DS:00000432 20 0A 06 05 04 01 00 45 04 00 00 27 08 26 09 25 08 24 09 23 0A 22 09 21 0A 20 0A FF 08 18 07 FF FF FF FF (All FF omitted) DS:00000467 28 09 05 0A 04 01 00 72 04 00 00 FF 0A 22 09 23 09 24 08 25 08 26 08 27 08 28 09 FF 08 18 07 FF FF FF FF (All FF omitted) DS:0000049C 11 12 0B 01 00 10 00 A7 04 00 00 FF 12 FF 10 13 10 12 11 11 12 FF 0A 19 0B 18 0C 17 0D 16 0E FF FF FF FF (All FF omitted) DS:000004D1 11 12 0B 01 00 10 00 DC 04 00 00 11 12 FF 0B 17 0C 16 0D 15 0E 14 0F 13 10 12 11 11 12 FF 11 FF FF FF FF (All FF omitted) DS:00000506 26 09 0B 02 04 02 00 19 05 00 00 22 09 23 09 24 08 25 08 26 09 FF 08 25 08 26 09 FF 0E 1E 0D FF FF FF FF (All FF omitted) DS:0000053B 26 09 0B 02 04 02 00 50 05 00 00 22 09 23 09 24 08 25 08 26 09 FF 08 25 08 26 09 FF 0E 1E 0D FF FF FF FF (All FF omitted) DS:00000570 26 09 0B 02 04 02 00 7D 05 00 00 25 08 26 09 FF 09 FF 09 23 09 24 08 25 08 26 09 FF 0E 1E 0D FF FF FF FF (All FF omitted) DS:000005A5 11 12 0B 01 00 11 00 B0 05 00 00 FF 12 FF 09 19 0A 18 0B 17 0C 16 0D 15 0E 14 0F 13 10 12 11 FF FF FF FF (All FF omitted) DS:000005DA 37 10 06 08 00 12 00 E5 05 00 00 FF 13 37 12 38 11 37 10 FF 14 31 13 32 12 33 11 34 12 35 13 FF FF FF FF (All FF omitted) DS:0000060F 11 12 0B 01 04 32 00 1A 06 00 00 FF 0A 18 0B 17 0C 16 0D 15 0E 14 0F 13 10 12 11 11 12 FF 09 FF FF FF FF (All FF omitted) DS:00000644 11 12 0B 01 04 32 00 4F 06 00 00 FF 0A 18 0B 17 0C 16 0D 15 0E 14 0F 13 10 12 11 11 12 FF 09 FF FF FF FF (All FF omitted) DS:00000679 37 10 06 08 04 34 00 84 06 00 00 FF 13 32 12 33 11 34 12 35 13 36 13 37 12 38 11 37 10 FF 14 FF FF FF FF (All FF omitted) DS:000006AE 37 10 06 08 04 34 00 B9 06 00 00 FF 13 36 13 37 12 38 11 37 10 FF 11 32 12 31 13 30 14 2F 15 FF FF FF FF (All FF omitted) DS:000006E3 24 08 02 0A 04 33 00 FA 02 00 00 21 0A 22 09 23 09 24 08 FF 10 1C 0F 1D 0E 1E 0D 1F 0C 20 0B FF FF FF FF (All FF omitted) ^^^^^^^^^^^

経路構造体+7がポインタだとすると、現在のプログラミングの常識ではディスクからセーブデータを読み込んだ後に再計算するべきではないか。と、思ってロードしてみたら、津軽のデバッガはDMAがそのポインタを書いていると言っている。どういうことかというと、ポインタをfread/fwriteで読み書きしていた。これは、データが格納されるアドレスが固定の場合は正しいが、仮に再コンパイルするとメモリレイアウトが変わってセーブデータが読めなくなる可能性がある。現在のプログラミングでは許される書き方ではない。

しかし、セーブデータにポインタが直接出ているということは、実機でプレイしたデータのポインタが見えるということだ。ヤフオクで落としたディスクからダンプしたイメージに入っていたセーブデータをロードしてみたところ、

  
DS:000002F4 13 11 07 09 02 19 00 09 03 00 00 1F 07 1E 06 1D 07 1C 08 1B 09 1A 0A 19 0B 18 0C 17 0D 16 0E FF FF FF FF FF FF DS:00000329 20 08 03 0C 04 00 00 38 03 00 00 14 10 15 0F 16 0E 17 0D 18 0C 19 0B 1A 0A 1B 09 1C 08 1D 07 FF FF FF FF FF FF DS:0000035E 13 11 07 09 02 19 00 6D 03 00 00 15 0F 14 10 13 11 FF 08 1B 09 1A 0A 19 0B 18 0C 17 0D 16 0E FF FF FF FF FF FF DS:00000393 16 0D FF FF 00 11 00 A2 03 00 00 13 10 14 0F 15 0E 16 0D FF 12 FF 10 FF 0C 18 0B 17 0C FF 0B FF FF FF FF FF FF DS:000003C8 16 1F 08 0B 00 14 00 D3 03 00 00 FF 18 1C 19 1B 1A 1A 1B 19 1C 18 1D 17 1E 16 1F FF 09 FF 0B FF FF FF FF FF FF DS:000003FD 26 0B 06 07 04 01 00 68 02 00 00 21 0A 22 09 23 09 24 09 25 0A 26 0B FF 0E 1D 0D 1E 0C 1F 0B FF FF FF FF FF FF ^^^^^^^^^^^ DS:00000432 26 0B 06 07 04 01 00 D2 02 00 00 21 0A 22 09 23 09 24 09 25 0A 26 0B FF 0A 19 09 1A 08 1B 07 FF FF FF FF FF FF ^^^^^^^^^^^ DS:00000467 26 0B 06 07 04 01 00 78 02 00 00 21 0A 22 09 23 09 24 09 25 0A 26 0B FF 0A 19 09 1A 08 1B 07 FF FF FF FF FF FF ^^^^^^^^^^^ DS:0000049C 22 06 03 0C 00 00 00 B1 04 00 00 1E 04 1F 03 20 04 21 05 22 06 FF 07 1C 06 1D 05 1E 06 1F 07 FF FF FF FF FF FF DS:000004D1 20 08 02 09 04 01 00 84 00 00 00 FF 09 FF 07 1E 08 1F 09 FF 08 20 09 FF 07 20 08 FF 06 1E 07 FF FF FF FF FF FF ^^^^^^^^^^^ DS:00000506 22 07 05 0A 04 02 00 18 01 00 00 25 08 24 09 23 0A 22 09 21 0A 20 0B 1F 0A 1E 09 1F 08 20 07 FF FF FF FF FF FF ^^^^^^^^^^^ DS:0000053B 22 07 05 0A 04 02 00 F2 00 00 00 FF 06 22 07 FF 0A 22 09 21 0A 20 0B 1F 0A 1E 09 1F 08 20 07 FF FF FF FF FF FF ^^^^^^^^^^^ DS:00000570 21 0A 0A 06 02 07 00 81 05 00 00 25 08 24 09 23 0A 22 09 21 0A FF 09 FF 0E 1E 0D 1F 0C 20 0B FF FF FF FF FF FF DS:000005A5 16 0D FF FF 00 11 00 B2 05 00 00 14 0F 15 0E 16 0D FF 12 FF 0F 13 10 FF 09 19 0A 18 0B 17 0C FF FF FF FF FF FF DS:000005DA 26 0A 06 05 04 02 00 2A 01 00 00 FF 0A FF 09 23 09 24 08 25 08 26 09 FF 0E 1E 0D 1F 0C 20 0B FF FF FF FF FF FF ^^^^^^^^^^^ DS:0000060F 22 07 05 0A 04 32 00 A2 00 00 00 FF 07 1D 06 1E 05 1F 04 20 05 21 06 22 07 FF 0A 1A 09 1B 08 FF FF FF FF FF FF ^^^^^^^^^^^ DS:00000644 22 05 09 0C 00 00 00 59 06 00 00 14 0F 15 0E 16 0D 17 0C 18 0B 19 0A 1A 09 1B 08 1C 07 1D 06 FF FF FF FF FF FF DS:00000679 22 06 03 09 00 00 00 88 06 00 00 38 11 37 12 36 13 35 13 34 12 33 11 32 12 31 13 30 14 2F 15 FF FF FF FF FF FF DS:000006AE 22 05 06 04 00 00 00 BB 06 00 00 35 13 34 12 33 11 32 12 31 13 30 14 2F 15 2E 16 2D 17 2C 18 FF FF FF FF FF FF DS:000006E3 22 05 05 05 00 00 00 F0 06 00 00 17 1E 18 1D 19 1C 19 1B 1A 1A 1A 19 1A 18 1B 17 1B 16 1B 15 FF FF FF FF FF FF

実機で実行した場合でもポインタの破壊は起きていた!この時点で津軽のCPUコアの可能性は自分の中では99%消えた。逆に、99%の自信を持ってこれはFM TOWNS版大航海時代のバグと確信した。

ではこのポインタが破壊されるのはどのタイミングか。エミリオ艦隊戦直後のステートから再開して、問題個所への書き込みでブレークするように仕掛けたところ、勝利画面から海上に戻る間でNULLが書き込まれていた。NULLを書いている関数は、シンボルテーブルには出ていなかったが、どうやら艦隊を消去する関数のようだ。そして、このポインタが次にCPUでReadされるまでの間に、ポインタはセットされていなかった。

NULLポインタから読み込んでしまうのはどこか? これも津軽のデバッガで簡単に見つかった。そのときのコールスタックがこれ。

  
FR=000C:00056AC2 TO=000C:000298E0 RET=000C:00056AC7{MAIN ()} FR=000C:0002999D TO=000C:0003A1BC RET=000C:000299A2{MAITUKI_MAIN ()} FR=000C:0003A1C1 TO=000C:00039D80 RET=000C:0003A1C6{KAKKOKU ()} FR=000C:00039DB7 TO=000C:00039600 RET=000C:00039DBC{KAIJI ()} FR=000C:0003960C TO=000C:000393D4 RET=000C:00039611{MAKE_KAIZOKU ()} FR=000C:0003944A TO=000C:00038C40 RET=000C:0003944F{MAKE_SENTYOU ()}

MAKE_海賊! この関数内を相当探したが、どこにもポインタを再設定している様子はない。単純にNULLポインタから2バイト読んで、ポインタをインクリメントしている。NULLポインタから2バイトずつインクリメントするわけだから、破壊されたポインタはいずれも02F4h未満を指している。実機のセーブデータの壊れ方とも一致する。

なぜCOM艦隊の座標が(00h,FFh)になってしまったのか? ポインタがNULLになってから、経度にFFhが現れるまで、毎ステップ2バイトずつ読み込んで艦隊座標としてきたようだ。だから、そのCOM艦隊は世界中を転々とワープして飛び回っていた。NULLポインタから始まって2バイトずつインクリメントしていくと、それまでたまたま偶数アドレスにFFhが無ければ02FEhに達する。しかし、COM艦隊#0の経路は、02FFhから始まる。これは奇数アドレスだ。すると、本来終端を表す意味だったはずの経度となるはずだったFFhが緯度に、直前の00hが経度として読み込まれてしまっていた。それでもそのうち経路構造体後半の山のようなFFhに当たってポインタはリセットされるのだが、既に艦の座標は地球外、二度とプレイヤーと遭遇することは無い。

この問題は、簡単なパッチで解決できた。

  
修正前 000C:000488E2 C7470700000000 MOV DWORD PTR [EDI+07H],00000000H 修正後 000C:000488E2 8B7F07 MOV EDI,[EDI+07H] 000C:000488E5 C607FF MOV BYTE PTR [EDI],FFH 000C:000488E8 90 NOP

これでポインタの破壊は防止できる。FFhというのは、現在経路のゴールにあたるらしい。問題は、これによって艦隊の経路をきちんと再計算してCOM艦隊が戻ってきてくれるかということだが、これはプレイしないとわからない。ただし、マップ範囲外の座標に飛んでクラッシュすることは少なくとも防止できるようになるはずだ。

実機でなんとかしようとしてもMAIN.EXPはCD-ROM上だが、津軽上だと修正できるので、-APP DAIKOUKAIオプション、またはGUIでアプリケーションからDAIKOUKAIJIDAIを選ぶとマウスBIOS初期化時に、パッチを当てるようにした。

エンディング近くでフリーズする現象

爵位も侯爵になり、エンディングが近づいてきたころ、洋上で突然何の反応もなくなり船も進まなくなる現象が発生。

調べたところ、IBENTO_MAIN(イベントメイン)という関数の中で無限ループになっていた。厳密には、IBENTO_MAINから呼び出される関数、000C:0002CD44 の中なのだが、この関数はシンボルテーブルに出ていない。

この関数はゲーム内の一日に一度、20分の1の確率で呼び出されるようだ。そして、その中では、乱数を使って、発生可能なイベントの中からひとつイベントを発生させるというものらしい。

どうやらそのイベントのひとつらしいのだが、人物の中から、主人公の仲間になって一緒に旅していない人の中で、フラグ02hが立っていない人をランダムに抜き出す関数が使われている。しかし、その時点でこの条件に該当する人物がいないため、何度も乱数を発生させても人物が見つからず無限ループに陥っていた。

その後、000C:0002CD44 にブレークポイントを設定して通しプレイした結果、どうやら、この関数を通過した直後は、かならず宝探しの依頼があるらしい。宝探しの依頼では、必ず最低3名の人物を経由して地図が手に入るか、または誰かの艦隊が持っていることがわかる。000C:0002CD44は、経由する人物を探す関数のようだ。

これも、津軽のCPUの問題かと思ったのだが、関数の構造を見ると、どうもFM TOWNS版大航海時代のバグのようだ。1991年ではプレイテストの方法論もぜんぜんできてなかったろうからそんなものかもしれない。

可能性として、上のパッチで修正したスタックオーバーフローバグによって、何名かの人物は世界の外を旅する艦隊とともに旅を始めてしまうのかもしれない。すると、最後までこの条件に該当する人物がいるためこの関数は無限ループに陥らないのかもしれない。しかし、上のバグを塞がない限り、スタックオーバーフローで先に進めなくなる可能性があるので上のバグは塞ぐべきだ。

この問題は、約20日に一度発生するイベントで、たまたま乱数で問題のイベントにヒットしてしまわない限り問題にはならないようだ。もしも航行中、すべてが止まってしまってマウスのボタンにも反応しなくなったら、あきらめてセーブデータから再開すればなんとかなるようだ。

また、クエスト中は新しいイベントを発生させないのか、問題にならないらしい。セーブデータから再開して、近くの艦隊と戦闘して名声ポイントを上げて国王の勅命をもらってしまえば大丈夫のようだ。

この問題にパッチを当てるためには、既に発生可能なイベントが無いことを検出して、イベント発生を断念すればいいだけなのだが、そのためのコードを入れ込むだけのバイト数を見つけることができなくて、まだ成功していない。宝探しの依頼を全面的にブロックすることは可能だが、それではゲームのおもしろさが減ってしまう。

頻繁にセーブして、これが起きたら少し手前まで戻ってやりなおすしか対策は無い模様だ。また、他国の敵対心を上げないように、他国の同盟港は取らず、基本海賊以外は倒さないようにプレイしたところ、最後までこの現象は発生しなかった。

艦隊が爆速で進む問題

FM TOWNS版「大航海時代」は、ビジーウェイトでタイミングを取っていた。どういうことかというと、次の関数でタイミングを取っている。

  
XWAIT 000C:00025CE4 8B542404 MOV EDX,[ESP+04H] 000C:00025CE8 2BC9 SUB ECX,ECX 000C:00025CEA 3BD1 CMP EDX,ECX 000C:00025CEC 7610 JBE 00025CFE 000C:00025CEE 2BC0 SUB EAX,EAX 000C:00025CF0 81F8581B0000 CMP EAX,00001B58H 000C:00025CF6 7303 JAE 00025CFB 000C:00025CF8 40 INC EAX 000C:00025CF9 EBF5 JMP 00025CF0 000C:00025CFB 41 INC ECX 000C:00025CFC EBEC JMP 00025CEA 000C:00025CFE C3 RET
おそらくこれはCで次のように書いてあった。
  
void XWAIT(unsigned int counter) { int i,j; for(i=0; i<counter; ++i) { for(j=0; j<0x1B58; ++j) { } } }

さらにもっと長く待つ場合のために、FIXWAITという関数もあって、これはおそらくCでは次のように書いてあった。

  
void FIXWAIT(unsigned int counter) { XWAIT(counter*6); }

問題は、どの程度の待ちが入るのかCPUスピードに依存するので、486DX 66MHzのような機種ではとても人間の反応速度では対応できない速度で艦隊が移動することになる。これは、津軽だとウェイトを取る関数XWAITにパッチを当てて、OUT 6CH,ALを使うことによって解決できる。

しかも、調べたところ、各ステップごとに、ゲーム中の全艦隊のうち港に停泊している艦隊数に比例した時間ウェイトを入れることがわかった。従って、全艦隊が洋上にいるとき、まったくウェイトが入らないため艦が爆速で進んでしまう。これは仕様とは言えないのではないだろうか。気持ちとしては、航海中の艦隊はその処理時間がかかるから、航海中でない艦隊はウェイトを入れてかかる時間を一定にしようとしたのかもしれない。が、航海中の艦隊の処理にほとんど時間がかかってない。このため、COM艦隊がすべて出港してしまうと、386 16MHz程度でも艦隊が1秒もかからずにリスボンからヴァージニアに達してしまう有様で、とてもコントロールできない状態になった。

これは、メインループにパッチを当てて、各タイムステップ後に一定回数のXWAITを呼ぶように書き換えることで改善できる。難点は、造船所や酒場などで一晩経過する場合も同じ関数を使うため、本来一瞬で一日がパスするべき場面でも洋上と同じだけの時間がかかるようになる点だが、洋上の一日も数秒というレベルなので、大西洋を1秒で横断されてしまうよりはこの方がいい。本当は、プレイヤー艦隊が港にいるフラグをチェックして、その場合はウェイトを入れないようにパッチを当てることができればよかったのだが、そのチェックを入れ込むだけのバイト数が無かった。

津軽では-APP DAIKOUKAIを選ぶことで、このふたつのパッチが有効になる。

艦隊戦後の戦利品の積み込みでものすごい回数のクリックが必要になる問題

このゲーム、苦痛だったのが、操作性が余りにも悪い点で、そのうちのひとつが、艦隊戦後、戦利品を積み込むに数百回マウスのボタンを連打しなくてはならない点だった。実は、マウスボタンを押しっぱなしにするとリピートがかかるのだが、間隔が長すぎる。これは、Up/Downの関数内でFIXWAIT関数を呼び出している個所を、呼び出さないようにして、リピート間隔をほぼゼロにすることができる。-APP DAIKOUKAIを選ぶことで、このパッチが有効になる。

王女救出時のグラフィックが崩れる問題と勝利後一度セーブしないとエンディングが見られない問題

最後の艦隊戦に勝利して王女を救出したときのグラフィックが崩れる。実機MXでも発生を確認したので、これも津軽の問題ではないらしい。いまだに、本来どういう絵だったのかわからん。


グラフィック崩壊。実機でも発生を確認。

しかし、肝心のエンディング近くの絵が見えないのはいかにも悔しい。どうも、斜めに縞々が入っているように見えるのは、ビットマップを描画するとき、横幅を間違っている典型のように見えるので、試しに、キャプチャイメージをロードして、1行ごとにピクセルをちょっとずつずらして表示するプログラムを書いてみた。すると、1行あたり16ピクセルずらしたとき、なにやら意味ありげな絵が!


意味ありげになった

しかし、斜めに断層みたいになっている。なんだこりゃ?1行あたり横方向に16ピクセルずつずらしていくということは、かなり強烈なシアー変換(日本語でシアーをなんて訳すかわからん。「せん断」?)がかかっている。どうやら、断層のずれは、X=0が変換されたライン上で起きているようだ。そして、ずれはY方向のずれということになる。一体どういう変換ミスをするとこういう崩れ方をするのか?と、思ったが、とりあえず整理すると、変換は下の図のようになる。


せん断の結果、変換したビットマップは横にびろーんと伸びてしまうのだが、これを幅で割り算した余りを取って元の幅に収めることができる。この割り算の商によってセグメントを識別することができる。で、実験の結果、各セグメントでYを1だけずらすと絵が直った。また、セグメント0が縦方向に3ピクセルずれるように変換すると、絵の左端がビットマップの左端に来た。単純な横方向の幅のエラーだったらコードにパッチあててやろうかなと思ったけど、ちょっとやめようかな。


直った

ちなみに、使ったプログラムは下。最初、1行1ピクセルずつずらしていくプログラムだったけど、下のはセグメントごとにずれを調整するプログラム。1セグメントあたりY方向に1にしたらいきなり絵が直った。

  
#include <fssimplewindow.h> #include <ysbitmap.h> void Shear(YsBitmap &dst,const YsBitmap &src,int d) { // 1:16 dst=src; for(int xDst=0; xDst<dst.GetWidth(); ++xDst) { for(int yDst=0; yDst<dst.GetHeight(); ++yDst) { int xSrc=(xDst+yDst*16)%src.GetWidth(); int ySrc=yDst; int xSrcUB=(xDst+yDst*16); // UB:Unbounded int segment=(xSrcUB/src.GetWidth()+3); // Why 3? ySrc=(ySrc-segment*d); while(ySrc<0) { ySrc+=src.GetHeight(); } ySrc%=src.GetHeight(); auto srcPix=src.GetRGBAPixelPointer(xSrc,ySrc); auto dstPix=dst.GetEditableRGBAPixelPointer(xDst,yDst); dstPix[0]=srcPix[0]; dstPix[1]=srcPix[1]; dstPix[2]=srcPix[2]; } } auto toSave=dst; toSave.Invert(); FILE *fp=fopen("shear.png","wb"); toSave.SavePng(fp); fclose(fp); } int main(void) { YsBitmap bmp,src; bmp.LoadPng("CorruptedGraphics.png"); printf("%d %d\n",bmp.GetWidth(),bmp.GetHeight()); int wid=288; // By trial and error. int shift=0; src=bmp; int dy=1; int mx=100,my=100; Shear(bmp,src,dy); wid=bmp.GetWidth(); FsOpenWindow(0,0,1280,800,1); for(;;) { FsPollDevice(); auto key=FsInkey(); if(key==FSKEY_UP) { ++dy; Shear(bmp,src,dy); wid=bmp.GetWidth(); } else if(key==FSKEY_DOWN) { if(0<dy) { --dy; Shear(bmp,src,dy); wid=bmp.GetWidth(); } } else if(key==FSKEY_SPACE) { Shear(bmp,src,dy); wid=bmp.GetWidth(); } glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glRasterPos2i(0,799); glPixelZoom(4,4); glDrawPixels(bmp.GetWidth(),bmp.GetHeight(),GL_RGBA,GL_UNSIGNED_BYTE,bmp.GetRGBABitmapPointer()); FsSwapBuffers(); FsSleep(25); } return 0; }

と、ここまで書いて、ビットマップがどうなってるのかわかって見たら、簡単にパッチが書けてしまった。

000C:00048D26 68DE010000 PUSH DWORD PTR 000001DEH [EBP+18h] 000C:00048D2B 68B0000000 PUSH DWORD PTR 000000B0H [EBP+14h] 000C:00048D30 6A22 PUSH DWORD PTR 22H [EBP+10h] 000C:00048D32 6A50 PUSH DWORD PTR 50H [EBP+0Ch] 000C:00048D34 68B8000000 PUSH DWORD PTR 000000B8H [EBP+8] 000C:00048D39 E8EECFFDFF CALL 00025D2C {XNUP ()}

PUSH DWORD PTR 22HというのがPUSH DWORD PTR 20Hであるべきだった。このパッチも津軽でアプリケーション特定サポートDAIKOUKAIJIDAI用を有効化して起動すると有効になるようにした。

それから、最終艦隊戦後、いったんセーブしてリセットしてロードしないとエンディングが見れないようだ。おそらく、グラフィックが崩れたとき一緒にDOSの何か大事なものを破壊してしまっているのではないかと推測している。

いったんはこのバグはまあいいか、と思ったのだが、クラッシュ系だから案外簡単にわかったりして、と思って気が変わって解析したら原因判明。最終決戦後、国王に謁見すると、メインプログラムMAIN.EXPは終了して、エンディングプログラムEND.EXPが起動することになっている。ところが、異常終了時はMAIN.EXP終了時、サウンドBIOSを止める関数SND_end();を呼ぶ前にプログラムがクラッシュしているため、サウンド割り込みが有効のままリアルモードに戻り、次にサウンド割り込みがかかった時点で死亡していた。

なんでSND_end();を呼ばないのかと思ったら、実は真面目にmallocしたバッファをfreeしようとしているのだが、どうやらどこかでHeap領域を破壊しているようでfree関数の中でクラッシュしていた。すべてのバッファを次々にfreeしているのは、000C:31624の関数で、その次のMB_RESETという関数の中でSND_freeしている。これなんだけど、もうプログラムの最後なんだから無視してfreeしないで終了しちゃったらいいんじゃない?と思ったら大成功で、000C:3138F CALL 31624をNOPで置き換えた最終決戦後、そのまま国王に謁見してもエンディングが流れるようになった。これも津軽でアプリケーション特定サポートDAIKOUKAIJIDAI用を有効化して起動すると有効になるようにした。これ書いてる時点でまだリリースにしてないけど。

  
In END_MAIN function 000C:0003183F E8E0FDFFFF CALL 00031624 { ()} 000C:00031844 E87348FFFF CALL 000260BC {MB_RESET ()} 000C:00031849 83C41C ADD ESP,1CH 000C:0003184C C3 RET

果たしてFM TOWNS版大航海時代は誰か通してプレイテストしたのだろうか。グラフィックが崩れる問題は、出荷前に誰かが通してプレイテストしていれば見つけられた問題だと思うんだけど。

バグ? 海流は絶対南、南西、西、北西を向かない

プログラム中で、海流方向を出すとき、

  
TYOURYU 000C:00028918 0FB6053D250000 MOVZX EAX,BYTE PTR [0000253DH] 000C:0002891F C1E806 SHR EAX,06H 000C:00028922 2407 AND AL,07H 000C:00028924 C3 RET ; Return ([253DH]>>6)&7()

このようにしている。しかし、BYTE PTRだから8ビットの値を右に6回シフトしているので残るのは2ビット。0,1,2,3の値しか取らない。潮流方向は北が0で45度単位に時計回りなので、南、南西、西、北西を向くことは無い。たしかに、プレイ中に レトロゲームマップツールで作ったマップで確認したところ、一か所も海流が南、南西、西、北西を向いている個所は無かった。これは意図的なのかバグなのか不明。MOVZX EAX,BYTE PTR [0000253DH]これがWORD PTRなのではないかという疑惑も持ってる。

操作性があまりにも悪い件

しかし、操作性があまりにも悪い。とくに、洋上で進路を決めるためにマウスで左右ボタンをクリックしなくてはならないのが極めてつらい。また、港で買い物をするときなど数字を打ち込むキーパッドもマウスでクリックしなくてはならない。ときどき出てくるYes/Noのダイアログまでマウスカーソルを移動させるのが苦痛だった。艦隊戦後に数百回マウスクリックで戦利品を積み込む作業ももはや指の筋肉にも良くないし、マウスのボタンが物理的に壊れる心配も出る始末。ゲームの背景、世界観、バランスなど非常に優れていて、FM TOWNS版はシリーズ第2作までしか出なかったものの、他機種、ゲームコンソールで今でもシリーズが続く、間違いなく傑作と言えるこのゲームで、この操作性の悪さは残念だった。

このうち、艦隊戦後の数百回のマウスクリック問題は、ウェイトを消すパッチでなんとかなったが、そのほかの場面でキーボードショートカットをつけられないか?

もちろん実機ではどうにもならない。しかし、エミュレータ上だったら、ボタンの位置を特定できれば、キーを押したとき、マウスをその位置に移動してクリックしたことにすればキーボードでの操作が可能になる。洋上のメニューのボタン位置、進路決定ダイアログの位置は固定だから、これはスクリーンショットで位置が特定できる。あとは、電卓ダイアログ、Yes/Noダイアログの位置だが、これはいったん移動すると、次に開くときも同じ位置に開くのでどこかに位置を記録しているはず。デバッガを使ってその位置を特定した。ついでに、商店などのダイアログ位置もわかってしまった。

  
DWORD [0001F098H] Y/NダイアログX (下位3ビットは無視される。8dot単位) DWORD [0001F09CH] Y/NダイアログY DWORD [0001F0ACH] 電卓ダイアログX (下位3ビットは無視される。8dot単位) DWORD [0001F0B0H] 電卓ダイアログY DWORD [0001F4F8h] 商店ダイアログX (下位3ビットは無視される。8dot単位) DWORD [0001F4FCh] 商店ダイアログY

問題はキーが押されたとき、リアルモードだったりすると、DS:[1F098h]を読むことができないから、エミュレータ上ではマウスBIOS初期化時を捕えて、物理アドレスをキャッシュする。これまで、アプリケーション特定のマウスインテグレーションで使ったのと同じ手を使う。これによって、電卓ダイアログ、Y/Nダイアログのボタン位置を特定できたので、キーボードショートカットが実現できた。津軽の-APP DAIKOUKAI オプションでこの機能が有効になる。なお、マウスBIOS初期化を通過しなくてはならないので、既にセーブしたステートからの再開では有効にならない。ステートロードした場合はいちどリセットが必要。

プレイ日記

2022/01/14

最初の2度は多分「大航海時代」プログラムのバグにより途中で断念、3度目はクリアしたもののイスパニア国の同盟港を次々に取ってたら後半は海賊とイスパニア艦隊に追い回される展開になったので、4度目、他国と戦わないつもりで開始。「津軽」にキーボードショートカット機能、無限再帰バグ回避パッチ、ビジーウェイト回避パッチを実装したので、そのパッチが当たった状態でクリアができることの確認を目指す。

主人公の名はセスナ。旗艦はC-123輸送機よりプロバイダー号。能力値は以下の通り。

  統率力 70
  判断力 71
  知力   70
  勇気   71
  強靭さ 70

まずは、砂糖満載でボルドーへ行き、売った金で陶磁器を満載してリスボンに帰港して売る。これを数度繰り返す。酒場にフェルナンドとマルコがいたがどっちもまだ仲間になってくれない。

航海レベルが3にアップでマルコが仲間になってくれた。

今度は砂糖満載でロンドンを目指す。ロンドン・リスボン間でも砂糖・陶磁器貿易が可能。一隻では一往復あたりの収益が少ないので、とりあえず、船を増強。ベルガンティン級C-47輸送機よりスカイトレイン号。

酒場にフェルナンド、ファスコ、ソリス、ホセがいるんだが、まだ仲間になってくれない。

ボルドーの物価が上がって、リスボンの物価が下がってしまったので、3往復ぐらいリスボンで砂糖を買うが、ボルドーでは売るだけの往復を試みる。

やっぱりフェルナンドは仲間になってくれない。埒が明かないので、ホセを仲間にして、一隻増強。ベルガンティン級C-2 グレイハウンド号。海賊が入港してるようだ。まだ大砲積んでないから今襲われたらひとたまりもないのだが。

とりあえずボルドーを同盟港にして名声457。ロンドンを同盟港にして名声919。ナポリで誰かが俺を呼んでいるらしい。

ん?ナポリに来たら、今度は国王が呼んでいる。羊毛36単位の発注。この近所だとロンドンだな。簡単に達成。2000枚の金貨と士爵の称号をもらった。名声ポイントは上がらないのか。

それよりロンドンの物価がすごい。砂糖売りに行こう。陶磁器の買値が60を超え、リスボンでの売値が70を切ったから、もう陶磁器貿易は断念する。ワイン貿易という手もあるが、樽あたり25程度は砂糖も一緒なんだよな。やっぱ砂糖だ。ロンドン行くついでにオスロとハンブルグの様子も見てこよう。

ハンブルグを同盟にして名声1309。今度はイスタンブールで誰かが俺を呼ぶ。行くか。いきなりサンゴの発注!?サントドミンゴで売ってるのは知ってるが、ベルガンティンで大西洋超えられるかな?やってみるか。数忘れた。。。。確か14単位とか言ってたか。だけど満載で帰ってくるわな。

試しにイスタンブールでどういう船が買えるかと思ったら、リスボンと同じだった。いや、やっぱりリスボンで買おう。物価激安だから金貨7392枚でカラックが買えるリスボン。カラック級C-141 スターリフター号。やっぱ大西洋超えようってのにベルガンティンは無いから、2隻売って、もう一隻カラック級にしよう。C-130 ハーキュリーズ。

リスボンで61で買った武器がサントドミンゴで171で売れた。サンゴを250ぐらい買って帰るか。

サンゴ届けて名声1524。サンゴを元手にこのへんを平定するか。マジョルカ同盟で1850。チュニス同盟で2030。ナポリ同盟で2420。ピサ同盟で2808。サンゴ無くなっちゃった。美術品買っていこう。マルセイユでボナロッティを仲間にする。サインアップボーナスと、サンゴボーナス出しておくか。マルセイユ同盟で3244。他国の同盟を崩してないから敵対心は増えてないと思うのだが。アルジェ同盟で3423。

リスボンに戻ると、国王陛下からもサンゴの注文27単位。やったろうじゃないか。カラック級増強。KC-135 ストラトタンカー号。オスロ同盟で3709。アントワープでニコラウスを雇う。

リスボン帰還。4000枚のコインと准勲爵士の称号をもらった。次はアフリカ開拓か。まず、船首像の増強だな。

あれ。アフリカ開拓と思ったら、既に国王からの召喚が。象牙29単位の発注。やってみよう。サンジュルジュだな。訓練も必要なのだが、船を増強するかどうか。まだかな。

アルギン同盟で、3840.うーん、サンジュルジュの金はそんなに安くないんだよな。でも収入にはなるから、金30、象牙160買って帰る。勲爵士の称号をもらった。コインいくらくれたかな?10000ぐらいかな?金とか象牙とか売って予算に換えないといかんのだが。バレンシアあたりの物価はどうだろうか。

アルジェが物価99%だな。まあ、いいか。バレンシアはほっとくと物価が上がるのか。素晴らしい。次に高いもの売るのはバレンシアが良さそうだ。

よし。アフリカ作戦。ソファラ同盟で3951。ボナロッティとニコラウスの航海レベルがやや遅れ気味だから、この2名に船長させるかな。モザンビケ同盟で4021。しばらくこの周辺にとどまって同盟港を増やしたいのだが。モンパザで象牙を買い、メッカで売り、メッカで穀物を買い、モンパザで売るので結構滞在できそうだ。

モンパザにバンボラなる冒険者がおる。誘ってみるか。顔は海賊だな。知力が低すぎる。まあ、よかろう。バンボラは「うっかり八兵衛」役として航海を共にすることになる。

モンパザ同盟で、4173。ディブ同盟で4319。ゴア同盟で4623 。アルギンで誰かが呼んでるようだが、間に合わんだろうな。カリカット同盟で4927 。コチン同盟で5004 。ちょっと象牙が尽きたがセイロンまで同盟。5113。そろそろ国王から呼ばれるのではないか?

アルギンに寄ったが、やはり既に以来はキャンセルになっていた。あ、今度はバレンシアか。貯金入れて、バレンシアに向かおう。

あ、水晶?持ってるけど。あと24単位必要?というか、買ってきたの没収されちゃった?ではない。買ってくれたわけね。じゃあ、仕入れに行ってこよう。次行くときはナガサキまで同盟にと思ったけど、仕方ないな。モンパザ往復とするか。 武器を満載して出港。武器商人。主人公の名前はマッコイが良かっただろうか。スマトラ、ジャワ、テルナーテの制圧も兼ねるか。あわよくばアンボイナも。スマトラ同盟で5211 。ジャワ同盟で5258 。テルナーテ同盟で5297 。アンボイナ同盟で5424 。バレンシアに水晶を届けて5649 。

途中すれ違った商船の話では、残った水晶はトレビゾンドで売るといいらしい。遠いな。ん?バレンシアで新たな任務が。と、思ったら実はオスロ?

また水晶か!全部買ってくれて、まだ24単位足りない!?またアフリカツアーか。一隻増強するか。新造艦にするかな。C-5 ギャラクシー号。

水晶ミッション達成。5894 。波風立てずに同盟にできる都市は全部してしまった、ではなく極東が残ってた。国王からの召喚が無ければジパングを目指そう。あ、ヴェネチアがまだ中立だから取れる!ヴェネチア同盟で6217 。

よし。ジパングを目指す。一隻新造しよう。カラック級CV-22 オスプレイ号。

海賊とすれ違ったな。ぼちぼち砲を装備するか。マラッカ同盟で6366 。マカオ同盟で6599。ついにナガサキ同盟で6726。

王の召喚キター!ザイトンも同盟で名声7125 。サンジュルジュで、セランを雇った。なんだ、密書ミッション。イスラム国王宛て。了解。やってみよう。あ、ヴェネチアがイスラムに取られてた。水晶売ろうと思ったのにどこも首都は物価安いんだな。

准男爵になった。ん?パナマで俺を呼んでいる?行くか。パナマに来たのに、今度はハンブルグ。金買って帰るか。(* 解析して後でわかったけど、毎日1/20の確率で新しい任務に変わるらしい。あまり遠くの依頼だとせっかく行っても着くころには別の場所の別の依頼に変わるようだ。ただし、王の勅命が出た後は王に会うまで変わらないらしい。)

ハンブルグに戻ったのに、またパナマで依頼と言われる。とりあえず買ってきた金塊を換金してるうちにどっちかに落ち着くだろう。と、思ったら、今度はアントワープのギルドのおっさんだ。宝探しだな?やってやろう。聖騎士の盾を探せ!アメリクスが知っている。どこにいるんだ?マジョルカ。アメリクスはロザンナから聞いた。ロザンナはパナマにいる。アメリクスを仲間にした。ロザンナによると、ミゲルから聞いた?ミゲルは北緯35度、東経0度にいる?マルセイユあたりかな?いや、アルジェだ。ミゲルは地図くれない。出直して来いって言われた。3度出直したら。金貨400枚で売ってくれた。ペルナンブーゴでクリストフを仲間にする。

作戦成功。名声一気に8575。海賊を狩るぐらいしかすることが無くなったが、しばらく海賊に会ってないな。お、今度はナポリで依頼が。

おおー、今度はピメント92単位の発注。やってやろう。ホルムズにクライホって冒険家がいたから一緒に旅をすることになった。いきあたりばったり。ナポリにデリバリーで名声8800。

今度はセビリアでミッションか。あ?酒場の親父の依頼だ。海賊シングルトン討伐の依頼!!やりましょう!ちょうどセビリア。旗艦をガレオン級に更新しようかと思ったが、とりあえず、どこにいるかによるな。ん?パナマが中立になったか?

時は1506年12月。

やはり、ノヴァグラナダに向かってるのか。行ってみるか。パナマ、知らないうちに我が国の同盟港になってた。

うーむ、どうも既にシングルトンは入港してしまったようだ。いったんリスボンに戻って次の動きを待つ方がいいな。

野良海賊いたんで撃退しちゃった。お、シングルトン艦隊は出港してきたらしい。そして、我が艦隊を狙ってくれてるらしい。なら話は速い。

セビリア沖で待つ。迎撃に成功!このゲームでおもしろいのは、艦隊戦を入る時点の敵艦隊との位置関係によって、海戦モードの初期位置が決まる点だ。なんとかして敵艦隊の背後を取った位置で戦闘を始めると、圧倒的に有利な位置で海戦を始めることができる。運が良ければ1ターン目にダメージをほぼ受けない敵艦の真後ろから数回の砲撃を加えることができる。逆に、敵艦隊に追いつかれて混戦状態で戦闘に入ると、斬りこみを余儀なくされて犠牲が増える。海戦モードに入る前から勝負が始まっている点が実におもしろい。 シングルトン艦隊討伐で名声ポイント11220 。4番艦が撃沈寸前まで行ったのは戦闘経験の問題かな。

これから増えるであろう海戦に備えてガレオンを新造した。旗艦コンコルド号!エル・ドラゴが追ってきたが、手負いでは相手できん。いったんセビリアに引っ込む。引き続きわざわざエル・ドラゴはこっちを追ってきてくれた。ありがたい。返り討ちにして名声11890。よし。2隻体制にするかな。ガレオン2隻目建造。ヴァルキリー号。

海賊ロバートまで襲い掛かってきてくれた。が、リスボンで武装しようと思ってたヴァルキリー号にまだ砲を積んでない!残り4隻でなんとか迎撃したが、よく乗組員が減る。中古のストラトタンカ号もリスボンで売却して新造する。グローブマスタ号。これですべてマホガニー製の新造艦になった。

そして、王の勅命キター!なんだ、イスパニアに密書。隣の港じゃないか。男爵になった。

海賊アホンが攻めてきてくれたので、撃退。名声ポイント13240。お、今度はボルドーでミッションか。

イスラム戦艦隊のアリーを討伐してほしい?スペインと仲良くしてたのに。。。。イスラム艦隊とは敵対関係になるのか?仕方ない。そういうことなら引き受けるか。

イスタンブールに向かっているのか。待ち伏せして撃破。ちょっとイスラムの敵対心が上がってしまったが、まだ戦争状態ではないみたいだ。今度は、怪力キッドを倒してほしい?倒さなかったっけ?

ノヴァグラナダに向かうのか。どうせそのうちうちの艦隊を狙ってくるだろう。出港してこっちを狙ってくれてるらしい。では、待つとしよう。

怪力キッドを待つ間に雑魚海賊と雑魚と呼ぶにはちょっと強い海賊を倒しまくっていたので名声20780。子爵になった。

あれ、アントワープ行って帰ったらすぐまた国王の指令が。海賊狩り。強者シルバーを討伐せよ。たった2隻で暴れてたのか、強者シルバー。可哀そうだけど勅命なもんで。伯爵になった。

ピサで依頼。コショウ160単位。余裕だ。

2022/01/15

最初の3度のプレイで作ったマップがあっても一日ではクリアできんかった。

ロンドンでミッション。宝探しかな。ギルドの親父に黄金の小手探しを頼まれた。やってみよう。

なんと。おはるちゃんが知っている。じゃあ、ナガサキ行ってみる。

おお、途中ですれ違った商船によると、ソファラは経済が上がってくると金を売り出すのか。これからは帰りは水晶じゃなく金だな。

おはるちゃん、、ボニータから聞いた。ボニータがナガサキに来たのか!?それともおはるちゃん、ヴェラクルス行ったことがあるのか!?ヴェラクスルか。南下してアンボイナ、レベク、ケープタウン、リオグランデ、ペルナンブーコ経由か。太平洋ルートはやめておこう。今度はビセンテ。誰だっけ?トレビゾンドにいるそうだ。

地中海までは慣れたルート。トレビゾンドに来たらアゾフと言われた。あ、出直せって言われたら酒場を出て、何度もトライすればいいだけなのか。マップ売ってもらって、ついでだからビセンテ雇っちゃった。

なんと、まだ未解明だったニュージーランドに宝はあった。

名声ポイント26133。次が宝探しだったら死ねる。

国王陛下の勅命キター!。。。。死ねる。。。。国王陛下は伝説の水晶の手鏡をご所望か。手鏡が好きなのか。

シーターはゴアにいる。行くか!シーターはベアトリスから聞いた?ジェノアの酒場にいるのか。ベアトリスはマルガリータから聞いた?セビリアにいる?近くて良かった。マルガリータはカルロータから聞いた!?リスボンじゃん。がーん、刺青シルバーの艦に積んであるのか。我が艦隊を狙ってくれてるらしいのだが。じゃあ途中偶然遭遇してたら手に入ってしまった可能性もあるのか。

刺青シルバーは向こうから水晶の手鏡を持ってきてくれた。侯爵になった。エンディングは近い。名声ポイント27993 。30000必要なのだろうか。

うーん、ヴェラクルスで誰かが俺を呼んでいる。まあ、行くか。

残虐ディノってのがいたんで撃退したら28633。残虐ロバーツとかいうのがカラックたった2隻で海賊業を営んでいたらしいのだが、かわいそうだが撃退した。29303 。そうこうしてるうちにヴェネチアに変わった。毎日20分の1の確率でイベントが更新されるらしいから、ヴェラクルスまで行く間に絶対変わってしまう。と、思ったら、獅子の王冠探せ!?またか!やってやろうじゃないか。ロンドンのメアリーだな。バルボアから聞いた。えーと、バルボアは、北緯5度東経75度にいる?モンパザの酒場で、テルナーテに移動したとの情報。アルジュナから聞いたそうだ。誰だそれは。アルジュナはカリカットにいるそうだ。ソリスから聞いた。マカオにいるらしい。ソリス。地図を500で売ってくれた。なんか追ってきた海賊を撃退してるうちに名声が31213 。

北極海である可能性が高い。。。。。今自作ツールでマッピングしながらやってるから場所特定できるけど、普通の人はできないんじゃ。。。。

名声35000突破。勅命来た。そして、宝探しだ。。。。降魔の聖杯が欲しい。。。。はい。行きます。ん?バルボア?さっき会ったな。テルナーテにおったな。さっき通ってきたのに。。。。

えーと、リスボン出港した途端に海戦になって、たまたま降魔の聖杯持ってる人がわざわざ来てくれたんだけど。ついにセスナ公爵になった。名声35683 。オスロで誰かが俺を呼んでいるのか。白銀の剣が欲しい?探しましょう。アルジェのミゲルが知っている。マルガリータから聞いた。セルビアか。イスタンブールのファテシアか。おはるちゃん!イダルゴの商船に積んである!?それを襲えって?なんとか交渉で売ってくれないだろうか。

イダルゴはセビリアに向かってる?サンジュルジュに向かったと聞いたら今度はサンジュルジュでは北緯20度西経20度にいた、セビリアに向かってる?いた。交渉したけど売ってくれない。艦隊戦で勝てば取れるけど、それは、したくない。試しにセビリアで入港するのを待って、酒場で交渉できないかと思ったが、来て、酒場に寄らずにジェノアに向けて出港してしまったらしい。

相手は商船。ほとんど無傷で倒すことができて、白銀の剣も手に入った。だが、無抵抗の商船を襲うのは後味が悪すぎる。ロードして途中まで戻ってやりなおそう。たしか1511年10月だったか。

王の勅命が出てもいい頃のはずだから、とりあえず海賊を掃討しながら王命を待とう。マジョルカ、と、思ったらアルギンで俺を呼んでいる。まだ王命出ない。今度はアゾフか。行ってみるかな。チョウジを188?簡単なことだ。。。。。テルナーテか。こらまた遠いねえ。東南アジアツアーから帰還。名声40428 。

ついに、王女様救出ミッション開始!ええっ、エミリオ提督が王女誘拐犯!?南緯0度西経25度付近にいた?セルビア周辺だろう。やはり、帰還途中だ。セビリア沖で待つ。 エミリオ提督艦隊は、弱かった。FM TOWNS版「大航海時代」では最終艦隊決戦後のグラフィックが崩れてしまって本来どういう絵のはずだったのかいまだにわからない。 (-> C++の力で絵を直して見た)

エンディング。津軽だとなぜか艦隊戦後セーブしてロードしなおさないとエンディングが始まってくれないという問題があるが、クリア確認できた。またひとつ、心残りだったゲームクリアできてよかったな。 プログラミングの品質の問題と、ユーザーインターフェースに難があったものの、「大航海時代」は間違いなく傑作だった。 そのうち「大航海時代2」もやってみよう。

Comments are welcome.  Send E-Mail to: 

Back to http://www.ysflight.com