電源を入れたらリセットボタンを押し続け、スタートボタンと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 で乱数の差異が出ました.
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 で乱数の差異が出ました.
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
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 の発生タイミングのエミュレーションの精度が高いのでちゃんと動くということがわかりました.
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