Gigamix Online

懐かしの8bitおもちゃPC「MSX」を骨までしゃぶり尽くそう。MSXの最新ニュース、ブログ、自作ソフトの配布など。

MSXのBSAVE形式バイナリデータの読み書きにバグ発見!?BSAVE・BLOAD命令の挙動を検証

 MSX BASICのBSAVE命令はメモリの内容をバイナリーデータで保存する命令、BLOAD命令はバイナリーデータをメモリへ転送する命令ですが、一体どのくらいのメモリ容量が扱えるのか、知らなかったので調べてみました…と思ったらバグかもしれない場面に遭遇しました。

https://p.gigamix.jp/devmsx/cg/bload-bsave_title.png

結論

  • BLOAD命令・BSAVE命令ともども、メモリの最大容量は65535バイトまで扱えます。
  • メインRAMの最大容量である64kB(65536バイト)以上のデータには対応しません。誤動作が発生します。
  • そういう動作をするのは仕様でした。

BSAVE形式ファイルの仕様

 今一度MSXのBSAVE形式ファイルの仕様を確認します。

オフセット 容量 内訳
+0 1 BSAVE形式の明示(0xFEが必ず入る)
+1~+2 2 開始アドレス
+3~+4 2 終了アドレス
+5~+6 2 実行アドレス
+7~ n 実際のバイナリデータ

 先頭7バイトがいわゆる「BSAVEヘッダ」と呼ばれるヘッダデータ、以後は実際のバイナリデータが連結されます。MSXZ80 CPUなので、アドレス指定はリトルエンディアン(下位アドレスから先に記述する方式)となります。

 例えばメインRAMのC000hからC00Fhまでの16バイトをBSAVE命令で保存する場合、以下の通りになります。

  • BASICコマンドは「BSAVE "(ファイル名)",&HC000,&HC00F」
  • 保存されるファイルのBSAVEヘッダは「FE 00 C0 0F C0 00 C0」
  • 保存されるファイルのデータ容量は 7+16=23バイト

 なおBLOAD・BSAVEともにBSAVEコマンドの末尾に「,S」を記述すると、対象がVRAMになります。

  • 「BSAVE"(ファイル名),0,256*212-1,S」← SCREEN8の1画面を保存する
  • 「BLOAD"(ファイル名),S」← VRAMへバイナリデータを転送する

BSAVE命令のテスト

SAVEヘッダのテストプログラムを作る(2回目)。65536.SC8…BSAVEヘッダのみ7バイト(FE 00 00 FF FF 00 00)で保存される。65535.SC8…0から始まって256バイトごとに1増える、65535バイトのバイナリデータで保存される。※1回目のプログラムは失敗だったのでツイート消しました pic.twitter.com/NYNeFmmn8h — Takashi Kobayashi (@nf_ban) 2022年5月25日

↓↓↓ 皆さんも今すぐブラウザ上で動作テスト!

結果

結果 65535.SC8…正常表示。1バイト欠けてるのが見える。65536A.SC8…96ライン目の途中から最後までバグってる。フリーズはしない。なんで途中からバグるのか。32768.SC8…最大量が65536でなければFFFFhまで正常に保存できるらしい。MSX BASICではBLOAD・BSAVEの最大量は65535バイトのようです。 — Takashi Kobayashi (@nf_ban) 2022年5月26日

  • 0000~FFFFh(65536バイト)を保存すると、BSAVEヘッダのみ7バイト(FE 00 00 FF FF 00 00)で保存される。これはバグなのか仕様なのか…
  • 0000~FFFEh(65535バイト)の保存は、正常に動作する。
  • 保存するデータ容量が65536バイト未満であれば、FFFFhの値は保存できる。「FFFFhが保存できない」というわけではない。

 つまり、64kBの1バイト少ない65535バイトがBSAVE命令で保存できる最大データ容量となります。

 もしかしてFFFFhが保存できないのでは?と一時期思ったので念のためFFFFhだけを保存するテストを行ったところ、これは正常に動作しました。

BLOAD命令のテスト

バイナリエディタで 65535.SC8 を改造。BSAVEヘッダは終了アドレス FFFFh に変更、データの終端に 45h(SCREEN8の01001001b=灰色)を1バイト追加して、65536A.SC8(65536バイトの嘘データ)を作成。さて、どうなるのか… pic.twitter.com/laauhYGrAx — Takashi Kobayashi (@nf_ban) 2022年5月25日

 Windowsバイナリエディタを用いてデータ加工し、BSAVEヘッダが付いた65536バイトの偽バイナリデータを作成しました。その際、BSAVEヘッダの終了アドレスがFFFEhでなくFFFFhで終了するよう偽装してあります。

 また、BSAVEヘッダの定義内容と実際のバイナリデータの量が違う偽のバイナリデータも作成し、テストしてみました。

結果

  • 強引に作成した65536バイトの読み込みは、正常に読み込めない。フリーズはしないが途中で関係の無いデータが勝手に読み込まれる。
  • 65535バイトまでのデータ容量であれば、正常に読み込める。
  • BSAVEヘッダの定義よりも実際のデータ量が多い場合は、余分なデータは無視される(読み込まれない)。
  • BSAVEヘッダの定義よりも実際のデータ量が少ない場合は、関係の無いデータが勝手に追加される(不定値)。

オマケ BSAVEヘッダで定義している以上のデータが実際に含まれている場合は無視される。0000~000Fhの16バイトが45h(灰色)で、その後にFFh(白)を16バイト埋めてみたら、灰色しか表示されない。なるほど…これだと開発者にしか分からない暗号データやメッセージ等をファイルに埋め込めるかもなぁ。 pic.twitter.com/N9VXtbhQ8K — Takashi Kobayashi (@nf_ban) 2022年5月25日
オマケ② BSAVEヘッダの定義よりも実際のデータが少ない場合は、何かのデータが勝手に追加される。色づかいに見覚えがあるから前回ロードしたバッファのような何かなのかな? pic.twitter.com/q5ZKNBsE4Q — Takashi Kobayashi (@nf_ban) 2022年5月25日

 よって、MSX BASICではBLOAD・BSAVE命令で扱える最大データ量は65535バイトでファイナルアンサーのようです。

バグではなく仕様でした

 アドレスに入る値の有効範囲は0~65534(&HFFFE)である旨は、テクハン(アスキー刊「MSX2 テクニカル・ハンドブック」)に記載されていました。実際にはエラーは出力されず命令は実行されるので、「有効範囲」という言い方になっているのですね。

なぜそうなるのか仮説

BASIC内部でBSAVEの長さ計算が16bitなんでしょうなぁ。17ビットになる65536=10000hだと0000hに化けると😅 ファイル形式的には65536バイト保存を許容するものの、BASICの仕様(というかほぼバグ)により65536バイト保存されたは扱えない(多分BLOADでも0バイト読み込み)だろうなと。 https://t.co/7YNX5joJk0 — ごりぽん (@goripon_tw) 2022年5月25日

 この頃はまだ何も分かっていなかった。

今ざっとOpenMSX上のFS-A1STで試したんですが、7バイトのファイルをVRAMに読むとゴミ書き込まれますね…。BSAVEは65536バイト書けないのに、BLOADは65536バイト読めるっぽい? — ごりぽん (@goripon_tw) 2022年5月25日
SCREEN8:BSAVE

 実データが無いBSAVEヘッダのみのファイルの読み込みでゴミ(不定なデータ)が追加されるのは、BSAVEヘッダの定義よりも実際のデータ量が少ない場合は何かのデータが勝手に追加される(不定値)条件だったことが、実験で判明する。

バグはBSAVE側(64KB保存されない)で、BLOADは想定されない壊れたデータ(ヘッダに64KBの情報があるのに本体が0バイト)のロードによる症状で、ある意味正常…かなぁ。たぶん64KBのBLOADはイケるかなと。 — ごりぽん (@goripon_tw) 2022年5月25日
BLOAD側がバグっているなら多分…①BSAVE同様に0バイト読み込み(VRAMは書き換わらない)②64KBのファイルだと読み込みが終わらず無限ループ(VRAMには本来のデータに続けてゴミが上書き)…のどちらかになると思うんですよね。どちらでもないので多分イケてるかなーと。 — ごりぽん (@goripon_tw) 2022年5月25日

 テストしてみたら64kB(65536バイト)のファイルは正常に読めないというオチに…

ちなみに「メインROMの内容をVRAMに書き込んだ場合の画面」は見る人が見れば判ります(少なくとも私は判った😅🤣)。 — ごりぽん (@goripon_tw) 2022年5月25日

 特殊スキルすぎる!!