今更ながらに「西暦2000年問題」を修正した話
MSXの西暦2000年問題に直面して「ROMさえ焼ければ数バイトのパッチあてで修正できそう」などと言っていたんですが……意外や意外、ROMライターって安く手に入るんですね。amazonで6K円弱。なんかROMライターを手に入れるのが現実味を帯びてきたのと、最近ハードウェア寄りのネタばっかりだったので、今回はソフトウェア寄りでパナソニック系MSX2+内蔵ソフトの西暦2000年問題パッチを作ってみようかと。
手元にある動作するパナソニック系MSX2+のうち、FS-A1WSXは2000年問題無しなので関係なし、FS-A1WXは内蔵ソフトの規模が大きいのと問題があまり深刻ではないので保留として、内蔵ソフトの規模が手ごろで解析しやすく問題(日付が1988年に強制変更されてしまう)も深刻なFS-A1FXを対象にします。
メインボードに載っているROMは2Mbit(256KB)あるものの、内蔵ソフトに使われている領域はそのうち32KBだけで、2MbitのROMを物理的に引っこ抜いて吸い出すような真似はしなくても、スロット#3-3のページ1〜2に該当の32KB分が現れているので、実機のツールで吸い出してディスクにセーブすればイメージは得られます。それを、昔からお世話になっているZ80逆アセンブラ「HOJA(ほぉじゃあ)」のJava版*1で逆アセンブルして解析用のソースコードを作成します。
ではいざ解析開始。まず取っ掛かりとして「初期化(1988年1月1日に設定)しているコード」を探します。RTCの内容が書き換わっている以上、どこかでその値を書き込んでいるはず。ということで「WRTCLK」(RTCへの書き込みBIOS)ルーチンのアドレスである「01F9h」を検索します。このBIOSはSUB-ROMにあるので、IXレジスタに呼び出し先である01F9hを入れて「EXTROM」ルーチン(アドレス015Fh)を経由して呼び出しを行っているはずです。なので検索するのは「LD IX,01F9H」命令。これでRTCに書き込みを行っている場所を抽出します。
順に見ていくとアドレス77C2hからのルーチンがそれっぽいです。RTCのバンク0・レジスタ#7からに1,0,1,0、つまり「1月1日」を書き込んでいます。*2
LD C,07H ;77C2 0E 07 LD HL,0784CH ;77C4 21 4C 78 LD A,(HL) ;77C7 7E LD IX,01F9H ;77C8 DD 21 F9 01 PUSH HL ;77CC E5 PUSH BC ;77CD C5 CALL 015FH ;77CE CD 5F 01 POP BC ;77D1 C1 POP HL ;77D2 E1 INC HL ;77D3 23 INC C ;77D4 0C LD A,(HL) ;77D5 7E CP 0FFH ;77D6 FE FF JR NZ,077C8H ;77D8 20 EE (中略) DB 01H,00H,01H,00H ;784C 01 00 01 00 DB 08H,00H,0FFH ;7850 08 00 FF
となると、このコードの前に判定部分があるかも……と思って見てみたら、本当に直前で判定していました。
LD HL,(0C566H) ;77AA 2A 66 C5 LD DE,1900 ;77AD 11 6C 07 OR A ;77B0 B7 SBC HL,DE ;77B1 ED 52 JR C,077C2H ;77B3 38 0D LD HL,(0C566H) ;77B5 2A 66 C5 LD DE,2000 ;77B8 11 D0 07 PUSH HL ;77BB E5 OR A ;77BC B7 SBC HL,DE ;77BD ED 52 POP HL ;77BF E1 JR C,07808H ;77C0 38 46 (略)
……ぉぅふ。なんか2000って数値*3が見えるんだけど……(汗)。アドレスC566hはおそらく4桁西暦のワークエリア、アドレス7808hはRTCへの書き込み処理が終わった後の続き、77C2hは先ほど見つけたRTCへの書き込み処理の開始位置ですね。引き算してキャリーフラグで判定してジャンプしているので、要するにこれは「西暦が1900年未満または2000年以上ならRTCへの書き込み処理へ進む」というコード。ってことは2000年以降の日付が1988年に再設定されてしまうのは「バグ」じゃなく「仕様」の可能性が高いってことか。まったく、酷い仕様もあったもんだ……(苦笑)。
4桁西暦で比較して異常値の判定をしているので、その範囲を修正すれば2000年以降の西暦でもRTCが初期化されることは無くなるはず。ということで、MSXシステムの仕様(1980年〜2079年に対応)に合わせてこんな感じに修正します。
LD HL,(0C566H) ;77AA 2A 66 C5 LD DE,1980 ;77AD 11 BC 07 OR A ;77B0 B7 SBC HL,DE ;77B1 ED 52 JR C,077C2H ;77B3 38 0D LD HL,(0C566H) ;77B5 2A 66 C5 LD DE,2080 ;77B8 11 20 08 PUSH HL ;77BB E5 OR A ;77BC B7 SBC HL,DE ;77BD ED 52 POP HL ;77BF E1 JR C,07808H ;77C0 38 46 (略)
異常値として1980年より前や2079年より後がワークに入ることがあるのかどうかは不明ですが、パッチですし出来るだけ元のコードを尊重するのならこうなるのかなと。ROMのバイナリイメージ的には先頭がアドレス4000hなので-4000hしてオフセット37AEh〜37AFhを「6C 07」から「BC 07」に、同様に37B9h〜37BAhを「D0 07」から「20 08」に、それぞれ書き換えることになります。
さて。パッチは当てたものの非メガROMのROMカートリッジのイメージなので、似非RAMに書き込んでも動かせません*4。どうやって動作チェックしようか……。
……うーむ。無いならば 作ってしまおう SRAMカートリッジ(字余り)。他にも使えるかなということで、動作試験用にSRAMカートリッジをサクっと*5作ってしまいました(笑)。詳しく書くとまたハードウェア寄りの記事になってしまうので別の機会に譲りますが、書き込み禁止スイッチとバックアップ用のキャパシタの付いた64KBのRAMカートリッジです。書き込み禁止スイッチが付いているのは、MSX2+以降のシステムではブートシーケンス中でRAM上のROMヘッダを消去してしまうため。書き込み禁止スイッチONにすれば、MSXシステムからはROMのように見えて消されなくなります。
テスト用の環境も用意できたので、レッツ実行。にがさん作成の「NSLOAD.COM」*6でパッチを当てたROMイメージをカートリッジにロードし*7、WX/WSXの内蔵ソフト無効化の機能がある似非RAMも一緒に挿して電源ON。無事起動したFS-A1FXの内蔵ソフトを経由してBASIC(MSX-DOS)を起動し、DATEコマンドで日時を確認すると……あれれ? やっぱり1988年に初期化されてる?
WSXでFXの内蔵ソフトが起動しているので、起動しているのはSRAMカートリッジに読み込まれたパッチを当てたイメージであることは間違いありません。となるとまだどこかで似たような処理が動いているんでしょう。再度1988年1月1日を設定しているところを探してもいいですが、今度は観点を変えて*8「RTCを読み出しているコード」を探します。
ざっと見てみると718ChからのルーチンでC422hからのワークにRTCのレジスタを連続して転送しているようなので、718Chをコールしたあとメモリの内容をチェックしているようなコードが無いか探します。すると70DDhからのルーチンでそういう処理を行っていました。MSXのRTCであるRP5C01は1ニブル(4ビット)のレジスタに1桁のBCDを保持していて、秒の1の位、秒の10の位、分の1の位……というふうにレジスタが配置されているので、どうやらここではそれが時分秒の範囲内に収まっているかどうかを確認しているようです。範囲外だと7114hへジャンプしていますが、7114hはやはり1月1日を設定するコードになっていました。
CALL 0718CH ;70DD CD 8C 71 LD HL,0C422H ;70E0 21 22 C4 LD A,(HL) ;70E3 7E CP 0AH ;70E4 FE 0A JR NC,07114H ;70E6 30 2C INC HL ;70E8 23 LD A,(HL) ;70E9 7E CP 06H ;70EA FE 06 JR NC,07114H ;70EC 30 26 (略)
ただ2000年以降の日付がこのチェックで異常と判定されるようには思えませんし、このルーチンでは時分秒しかチェックしていないようです。もう少し追ってみると時分秒チェックの後に714Bhのルーチンを呼んでからキャリーフラグを判定しています。この714Bhが年月日のチェックになっていました。年月日の範囲判定は各桁単独では難しいのか、BCDを16進数値に変換したうえで比較しているようです。
CP 018H ;7109 FE 18 JR NC,07114H ;710B 30 07 INC HL ;710D 23 INC HL ;710E 23 CALL 0714BH ;710F CD 4B 71 JR NC,0713DH ;7112 30 29 (略) LD A,(HL) ;714B 7E CP 0AH ;714C FE 0A LD B,A ;714E 47 JR NC,0718AH ;714F 30 39 INC HL ;7151 23 LD A,(HL) ;7152 7E ADD A,A ;7153 87 LD C,A ;7154 4F ADD A,A ;7155 87 ADD A,A ;7156 87 ADD A,C ;7157 81 ADD A,B ;7158 80 JR Z,0718AH ;7159 28 2F CP 020H ;715B FE 20 JR NC,0718AH ;715D 30 2B (略)
そのまま読んでいくと、最後に年の判定が。10の位を10倍して1の位に加算しそれを……ん? 20以上だと異常値として判定されてる? この20ってのはどこから出てきた?*9 そもそも20で判定したら1920年以降全部アウトじゃないのか? うむむ?
LD A,(HL) ;7175 7E LD B,A ;7176 47 CP 0AH ;7177 FE 0A JR NC,0718AH ;7179 30 0F INC HL ;717B 23 LD A,(HL) ;717C 7E ADD A,A ;717D 87 LD C,A ;717E 4F ADD A,A ;717F 87 ADD A,A ;7180 87 ADD A,C ;7181 81 ADD A,B ;7182 80 CP 20 ;7183 FE 14 JR NC,0718AH ;7185 30 03 OR A ;7187 B7 JR 0718BH ;7188 18 01 SCF ;718A 37 RET ;718B C9
ちょっと疑問は残りますが、年の判定があって異常の場合のジャンプ先が初期化コードになっているので、ここもパッチ対象のはず。数値20の意味が不明なので数値には手を加えず、エラー戻りへのJR命令を潰して様子を見ます。
CP 20 ;7183 FE 14 NOP ;7185 00 NOP ;7186 00 OR A ;7187 B7 JR 0718BH ;7188 18 01 SCF ;718A 37 RET ;718B C9
ここまで書き換えたROMを先ほどと同様にSRAMカートリッジに書き込んで実行します。すると……。
おぉ。カレンダー表示で1988年じゃなく設定しておいた今日の日付になってる。となると、やはり先ほどの数値20が2000年を表していて、あの判定で範囲外だと判定されていることに。数値20はRTCから読み出した生の値と比較しているので……もしかして内部表現の西暦は見た目通りじゃないのか? と、ここで「じゃあ初期値の1988年はどう表現されているのか?」と思いつき、一番最初の初期化ルーチンのところに戻ります。
DB 01H,00H,01H,00H ;784C 01 00 01 00 DB 08H,00H,0FFH ;7850 08 00 FF
このデータ、FFhは終端コードとして、日の1の位、日の10の位、月の1の位、月の10の位、年の1の位、年の10の位と並んでいるはずなので……あれ? 88年のはずなのに数値はBCDで08h? ……って……あ!そうか! 確かRTCに書き込まれる年は80年がBCDで00hになるんだっけ。ようやく思い出した*10。となると1980年が0だから……2000年は2000−1980=20で……これでようやく辻褄が合ったよorz。今回は2000年→2080年に修正するので20+80=100(またはBCD2桁で99hまで許容するので100)。こんな風に書き換えれば解決。しかし、ここでも2000年を判定しているのなら、もう言い訳の余地なく「仕様」ですねこれは。まったく、酷い仕様もあったもんだ……(苦笑)。
CP 100 ;7183 FE 64 JR NC,0718AH ;7185 30 03 OR A ;7187 B7 JR 0718BH ;7188 18 01 SCF ;718A 37 RET ;718B C9
すでに行ったパッチ当てに加えて、ROMのバイナリイメージのオフセット3184hを「14」から「64」に書き換え。これで先ほどの仮設パッチ同様うまく行きました。休日(写真では成人の日)がズレてるのはパッチ当てのレベルではどうしようもない*11ので保留ですね。内蔵ソフトスイッチONでRTCの日時が初期化されてしまう問題は解決したので、ひとまずこれで完成ということで。内部的に西暦値が2000以上になった場合に正常動作するかが微妙なところですが、カレンダー表示以外に西暦に依存するものは無いと思われるので、気にしないことにします。最終的なパッチとしては3か所で合計4バイトの書き換えです。
念のため「01 00 01 00 08 00」の6バイトのテーブルがここまで見つけた2か所以外に無いか検索してみましたが見つからず。さすがに「LD A,1」とかで1個ずつレジスタ値を用意しているコードは……無いでしょう……無いはずです……無いと思いたいな(苦笑)。あとは上位4ビットが無視されるのを見越して上位に0以外が入ってるテーブルを使ってる、とか……そんな意地悪なコードが無いことを祈ります。
さて。FS-A1FXの内蔵ソフトのY2K対策パッチ当てが完成してしまいました。この32KBのイメージを、メインボードの2Mbit ROM全体イメージのオフセット20000h〜27FFFh*12に書き込むことが出来れば挿し替えられます。もうこれはROM焼いて挿し替えてみるしかないかなと思ってますが……。流石にコレ頒布したらパナソニックに怒られるかな。お目こぼし頂けるなら、Y2K対策品として焼いたROMチップを頒布したいところなんだけど。でもまあ、いまさらFX用のY2K対策済みROMなんて欲しい人は居ないかな? 頒布の目途が立つならWXとかFS-A1STの対策ROMもやってみたいですが……。
*1:作者さんが2014年の公開時に「もう需要がないのは百も承知」(http://d.hatena.ne.jp/njisho/20140224/1393241927)などと書かれていていましたが、そんなことはありません。私は昔っからお世話になりっぱなしです。今回はJava版があって助かりました。
*2:この時点で西暦の内部表現について見落としていたので後でちょっと引っかかりました。
*3:実際の逆アセンブルリストでは16進表記ですが、西暦に関連する数値は判りやすいよう10進表記に直してあります。
*4:メガROMはバンクの初期値が全ページで0になっていて、バンクサイズ以上のイメージの場合はバンクの初期化コードが必要になるため。
*5:と言いつつ設計したり材料買い集めたりして作っているので実質丸1日掛かってますが。
*6:「フリーウェアコーナー」のページ(http://niga2.sytes.net/msx/free.html)からダウンロードできます。
*7:作成したSRAMカートリッジは「にせRAM」カートリッジとしてNSLOAD.COMから認識されて普通に使えました。
*8:この辺は勘です(苦笑)。
*9:先に書いた通りここで引っかかりました(苦笑)。
*10:公式MSXエミュレータの時に同じ問題に当たってたはずなんですよね。忘れるなよ自分…。
*11:1988年当時は祝日が固定日だったので祝日判定ルーチンもそのように実装されているはず。直すとなると新規書き起こしになると思われるので……。
*12:これまたにがさんの「松下FS-A1FXの改造・前編」ページ(http://niga2.sytes.net/msx/FX.html)の情報から。足向けて寝られないですね。ありがたやー。