移動速度が速いゲームとしてこれを思い出した。これをエミュレータで起動したところ、セーブデータ欄が名前が空欄でレベル1となっていて、これを再開すると船に乗っていて変。
対処方法は異常なセーブデータを消すと、ゲームが最初から開始できる。
ROM の redump をしていただいたところ、CRC は database と一致して、不具合はやはり再現したので原因はエミュレータの実装不備とした。
エミュレータでは CPU address 0x6000-0x7fff は基本的に RAM としていて*1、セーブデータに使うような保存された RAM イメージがある場合は .sav ファイルの中身を展開し、RAM イメージがない場合は data を 0 で埋める。
この手のセーブデータの処理は RAM 上の checksum を算出し、不正な場合はソフトから初期化する。シルヴァ・サーガの場合はセーブデータの領域のデータが全部 0 だと checksum 算出が正常として判断し、本当は不正なセーブデータを読み込んでゲームを再開してしまう。気になる人はプログラムコードを確認して欲しい。
ソフトウェア上で RAM の扱いをする場合は下記の基本的な約束事がある。
全部 0 で通過してしまうシルヴァ・サーガのプログラム実装も悪いのだが、エミュレータではターゲットシステム上の RAM はホストからはソフトウェアとしての変数領域だが、ターゲットからはハードウェアの SRAM となる。約束事はソフトウェアとして使う場合なので守らなくてよい。
エミュレータでのソフトのライブラリが気を利かせて、RAM が 0 で初期化していてくれることは実際にありえるし、いつもの癖で memset で初期化するエミュレータのコードもたくさんある。しかし、ターゲット上の RAM を厳密に再現する場合には意図的に不定値を書き込んでおく必要がある、ということになる。
RAM の電源投入直後の初期値は RAM の種類が SRAM か DRAM でも変わるし、初期値なんて不定なんだから再現のしようがないかもしれない。1つ聞いた噂によると SRAM の初期値は当然不定値だが、各 data bit の 0 と 1 の割合は 1:1 になるらしい。
ゲームの中身
移動速度が速く、メッセージも最速ででてキビキビとした動作はプログラマが気を利かせてくれたいいユーザーインタフェースである。
このゲームの致命的な欠点はシナリオが微妙、戦闘システムが雑となっていてユーザーの心に残らないのが、ドラゴンクエストシリーズと異なる点である。
考えさせられる。
プログラム
アセンブラではここら辺の処理。
d8c9: jsr $d918; initial save data pointer $29.$28 sty $20 ;16bit checksum $21.$20 = 0x0000 sty $21 lda #$00 sta $18 ;xor checksum jsr $d74a ldx #$00 ;carry count d8d9: clc lda $20 adc ($28),y sta $20 bcc $d8e4 inc $21 d8e4: lda $18 eor ($28),y sta $18 iny bne $d8f2 inc $29 inx bne $d8d9 d8f2: cpy #$fd bne $d8d9 cpx #$03 bcc $d8d9 d8fa: lda ($28),y cmp $18 bne $d911 iny lda ($28),y cmp $20 bne $d911 iny lda ($28),y cmp $21 bne $d911 clc bcc $d912 d911: sec d912: jsr $d754 ldy #$fd rts
C で人力逆コンパイルするとこんな感じ。
/* save data start address 0x7400,0x7800,0x7c00, length is 0x400 offset length 0x000 0x3fd game data 0x3fd 1 xor each byte data 0x3fe 2 16bit add sum data */ carry d8c9() { uint16_t sum = 0; //$21.$20 uint8_t xor = 0; uint8_t *savedata = func_d918(); //$29.28 uint8_t x; //0x100 byte unit count uint8_t y = 0; //1 byte count / pointer index while(1){ do{ sum += savedata[y]; xor ^= savedata[y]; y++; if(x >= 3 && y == 0xfd){ goto d8fa; } }while(y != 0); savedata += 0x100; } d8fa: if(savedata[y++] != xor){ return 0; } if(savedata[y++] != (checksum & 0xff)){ return 0; } if(savedata[y] != (checksum >> 8)){ return 0; } return 1; }