いまだにちゃんと動かないソフト/ハードの解析情報

裏技の概要

電源を入れたらリセットボタンを押し続け、スタートボタンとAボタンを押す。そして、リセットボタンを離すとゲームが始まるが、牌が配り終わるまでスタートボタンとAボタンを押していると、役満テンパイ寸前になる。
http://www1.odn.ne.jp/derbyshaker/famirycomputer%2...

この裏技はおそらく発売当時からよく知られているのですが、エミュレータで再現できたのは Mesen のみで、ほかは再現できませんでした.

簡単な解析

原因を調べたところ、ある程度の意味がわかりました.
  • スタートボタンとAボタンは 1P side のみ押す.
  • 電源投入とリセットボタンの区別はなく RAM はすべて初期化している.
    • 本体の RAM の未初期化値を乱数として利用していない
  • ボタン入力を含めた RAM の値を乱数更新に利用している.
  • スタートボタンとAボタンを押しているかなど特別な分岐はなく、意図的に強い配牌をプログラムとして用意していない.
    • 開発側は意図的に裏技は仕込んでいない.
    • この簡単な入力の組み合わせで偶然強力な配牌がでてくるだけ.
  • Disksystem の同じゲーム(型番:FMC-MJA)はどのエミュレータでも再現可能.

詳細な解析

麻雀(型番:HVC-MJ)の ROM のバージョンは複数あるようですがこの件ではどれでも同じものだと思われます.
Mesen version 0.9.7 と MAME 0.174 と RAM の内容を比較しました.
Mesen では Input Button Setup, Mame では breakpoint 経由(bp fd11,x==0,{a=90;go})でレジスタの書き換えをして 1P side のスタートボタンとAボタンを押し続けていることにしています.

乱数は CPU address $0023-$0042 付近にあり、起動後数秒は2つのエミュレータのRAMの内容は同じでした. NMI 同期の何かしらのカウントダウンするカウンタが CPU address $0098 にあり、ここの値が data $d1 までは同じで data $d0 で乱数の差異が出ました.



MAME の trace

CPU address $0098 の data $d1 から $d0 になる過程の命令の trace をしました.
FCD5: sec ;乱数更新ルーチン
   (loops for 7980 instructions)

CE4C: php ;NMIハンドラ開始
CE4D: pha
CE4E: txa
CE4F: pha
CE50: tya
CE51: pha
----中略----
CEC8: pla
CEC9: tay
CECA: pla
CECB: tax
CECC: pla
CECD: plp
CECE: rti
FCD3: beq $fcd6
FCD6: ror $23, x
FCD8: inx
FCD9: dey
FCDA: bne $fcd6
FCD6: ror $23, x

Mesen の trace

Trace 条件は MAME と同じです.
FCDA BNE $FCD6 = $76
FCD6 ROR $23,X @ $42 = $6E
FCD8 INX
FCD9 DEY
FCDA BNE $FCD6 = $76
FCDC RTS
[NMI - Cycle: 2856548]
CE4C PHP
CE4D PHA
CE4E TXA
CE4F PHA
CE50 TYA
CE51 PHA
----中略----
CECE RTI
CA91 LDA $4C = $00
CA93 BNE $CA8E = $20
CA95 JSR $FCDD = $20
FCDD JSR $FD2D = $A9

該当部の命令

NMI 待ちに関する命令を調べてみます.

CA8E: jsr $fcc2 ;乱数更新
CA91: lda $4c   ;NMIハンドラ完了フラグ
CA93: bne $ca8e ;NMIが発生してないなら乱数更新

FCC2: ldx #$00
FCC4: ldy #$20
FCC6: lda $23
FCC8: and #$02
FCCA: sta $00
FCCC: lda $24
FCCE: and #$02
FCD0: eor $00
FCD2: clc
FCD3: beq $fcd6
FCD5: sec
FCD6: ror $23, x ;MAME での RTI からの再開場所
FCD8: inx
FCD9: dey
FCDA: bne $fcd6
FCDC: rts ;Mesen での RTI からの再開場所

考察

PC:$CA91 の address $0041 は NMI ハンドラで 0 にされます. つまり NMI が起きるまでは乱数を更新し続けます. このプログラムの問題点は乱数更新とNMI発生フラグ確認のわずかな時間で NMI が発生することがあり、その場合に乱数がずれます. つまり Mesen の NMI の発生タイミングのエミュレーションの精度が高いのでちゃんと動くということがわかりました.

FMC-MJA の解析

memory map が違うので PC が異なってきますがやっていることは HVC-MJ と同じでした.

9CE3: sec

   (loops for 7961 instructions)

E18B: bit $0100
E18E: bpl $e198
E190: bvc $e195
E192: jmp ($dffa)
6E59: cli
6E5A: php
6E5B: pha
6E5C: txa
----中略----
6EDC: rti
9CE7: dey
9CE8: bne $9ce4
9CE4: ror $23, x

考察

プログラムで明確に異なることは NMI ハンドラを BIOS を介していることです. この4命令が増えた程度でNMIの発生タイミングの精度が違っても乱数更新のタイミングが安定するかは私では判断できません.
他に予想されることは、ROM と DISK で明確に異なるのはCPUリセットのあとの起動時間が DISK のほうが長く、 BIOS からの初期化が多いことが考えられます. ROM の場合は起動が早すぎるので何かしらの初期化を飛ばして動かしているかもしれないので NMI 発生タイミングに高い精度を求めてしまう動作になっているのかもしれません.

メンバーのみ編集できます