Gameboy模拟 - CD指令上的澄清需求

时间:2017-02-04 16:17:28

标签: cpu emulation gameboy

我目前正在编写Gameboy模拟器,我发现了一些对我来说很奇怪的事情。

我的模拟器正在点击跳转指令0xCD,例如CD B6 FF,但我的理解是跳转应该只跳到盒式ROM中的地址(0x7FFF最大值),因为我假设CPU只能执行ROM的指令,而不是RAM。有问题的ROM是马里奥博士,我希望它只能进行有效的操作。 0xFFB6处于高RAM状态,这对我来说似乎很奇怪。

我的想法是否正确?如果我是,大概这意味着我的程序计数器以某种方式结束于错误的地址并且CB实际上是另一个指令数据的一部分,而不是指令本身?

我要感谢一些澄清,谢谢。

供参考,我一直在使用Gameboy OpcodesCPU docs来执行说明。我知道它们包含一些错误,我认为我已经解释了它们(例如,0xE2被列为双字节指令,当它只有一个时)

2 个答案:

答案 0 :(得分:3)

刚检查了Mario 1.1博士,它在启动时复制了hlaB6上的VBlank int例程,然后当VBlank发生时,调用0:01A6的例程,调用OAM DMA传输例程。

在OAM DMA传输期间,CPU只能访问HRAM,因此需要在HRAM中写入等待传输完成的短例程。 OAM DMA传输需要160μs,因此您通常会在指定OAM传输源后进行等待这段时间的循环。

这是启动时运行的初始化例程的一部分,它将DMA传输例程复制到HRAM:

...
ROM0:027E 0E B6            ld   c,B6             ;destination hFFB6
ROM0:0280 06 0A            ld   b,0A             ;length 0xA
ROM0:0282 21 86 23         ld   hl,2386          ;source 0:2386
ROM0:0285 2A               ldi  a,(hl)           ;copy OAM DMA transfer routine from source
ROM0:0286 E2               ld   (ff00+c),a       ;paste to destination
ROM0:0287 0C               inc  c                ;destination++
ROM0:0288 05               dec  b                ;length--
ROM0:0289 20 FA            jr   nz,0285          ;loop until DMA transfer routine is copied
...

当VBlank发生时,它会跳转到0:01A6的例程:

ROM0:0040 C3 A6 01         jp   01A6

其中包含对我们的OAM DMA传输例程的调用,等待DMA完成:

ROM0:01A6 F5               push af
ROM0:01A7 C5               push bc
ROM0:01A8 D5               push de
ROM0:01A9 E5               push hl
ROM0:01AA F0 B1            ld   a,(ff00+B1)
ROM0:01AC A7               and  a
ROM0:01AD 28 0B            jr   z,01BA
ROM0:01AF FA F1 C4         ld   a,(C4F1)
ROM0:01B2 A7               and  a
ROM0:01B3 28 05            jr   z,01BA
ROM0:01B5 F0 EF            ld   a,(ff00+EF)
ROM0:01B7 A7               and  a
ROM0:01B8 20 09            jr   nz,01C3
ROM0:01BA F0 E1            ld   a,(ff00+E1)
ROM0:01BC FE 03            cp   a,03
ROM0:01BE 28 03            jr   z,01C3
ROM0:01C0 CD B6 FF         call FFB6             ;OAM DMA transfer routine is in HRAM
...

OAM DMA传输例程:

HRAM:FFB6 3E C0            ld   a,C0
HRAM:FFB8 E0 46            ld   (ff00+46),a      ;source is wC000
HRAM:FFBA 3E 28            ld   a,28             ;loop start
HRAM:FFBC 3D               dec  a
HRAM:FFBD 20 FD            jr   nz,FFBC          ;wait for the OAM DMA to be completed
HRAM:FFBF C9               ret                   ;ret to 0:01C3

答案 1 :(得分:0)

以下是我的分析:

  1. 在原始ROM中查找Unmarshal我只能在内存的一个位置找到CD B6 FF(十进制448)。

  2. 所以我决定反汇编ROM,看看它是否是有效的指令。

  3. 我使用gb-disasm来反汇编ROM。以下是0x01C0(ROM开始)到地址0x150的值。

    0x201
    1. 我们必须按照说明流程拆解ROM。例如,我们知道主程序从位置[0x00000100] 0x00 NOP [0x00000101] 0xC3 0x50 0x01 JP $0150 [0x00000150] 0xC3 0xE8 0x01 JP $01E8 [0x00000153] 0x01 0x0E 0xD0 LD BC,$D00E [0x00000156] 0x0A LD A,[BC] [0x00000157] 0xA7 AND A [0x00000158] 0x20 0x0D JR NZ,$0D ; 0x167 [0x0000015A] 0xF0 0xCF LDH A,[$CF] ; HIMEM [0x0000015C] 0xFE 0xFE CP $FE [0x0000015E] 0x20 0x04 JR NZ,$04 ; 0x164 [0x00000160] 0x3E 0x01 LD A,$01 [0x00000162] 0x18 0x01 JR $01 ; 0x165 [0x00000164] 0xAF XOR A [0x00000165] 0x02 LD [BC],A [0x00000166] 0xC9 RET [0x00000167] 0xFA 0x46 0xD0 LD A,[$D046] [0x0000016A] 0xE0 0x01 LDH [$01],A ; SB [0x0000016C] 0x18 0xF6 JR $F6 ; 0x164 [0x000001E8] 0xAF XOR A [0x000001E9] 0x21 0xFF 0xDF LD HL,$DFFF [0x000001EC] 0x0E 0x10 LD C,$10 [0x000001EE] 0x06 0x00 LD B,$00 [0x000001F0] 0x32 LD [HLD],A [0x000001F1] 0x05 DEC B [0x000001F2] 0x20 0xFC JR NZ,$FC ; 0x1F0 [0x000001F4] 0x0D DEC C [0x000001F5] 0x20 0xF9 JR NZ,$F9 ; 0x1F0 [0x000001F7] 0x3E 0x0D LD A,$0D [0x000001F9] 0xF3 DI [0x000001FA] 0xE0 0x0F LDH [$0F],A ; IF [0x000001FC] 0xE0 0xFF LDH [$FF],A ; IE [0x000001FE] 0xAF XOR A [0x000001FF] 0xE0 0x42 LDH [$42],A ; SCY [0x00000201] 0xE0 0x43 LDH [$43],A ; SCX 开始。所以我们应该开始在那里拆解。然后我们按照指令进行操作,直到我们点击任何0x150指令(JUMPJPJRCALL等)。从那一刻起,程序的流程分成两部分,我们应该遵循这两条路径进行反汇编。
    2. 这里要理解的是,如果我在ROM中向您显示随机存储位置,则无法告诉我它是数据还是指令。找出方法的唯一方法是遵循程序流程。我们需要定义在跳转目标中开始并以另一个跳转指令结束的代码块。

      1. gb-disasm会跳过不在代码块内的任何内存位置。 RET标志着一个块的结束。

        [0x0000016C] 0x18 0xF6 JR $ F6; 0x164

      2. 下一个块从0x16C开始。我们知道,因为它是位于0x1E8的跳转的目标地址。

        0x150
        1. [0x00000150] 0xC3 0xE8 0x01 JP $01E8 0x16E的内存块不被视为代码块。这就是为什么你没有看到内存位置0x1E8列为指令。
        2. 所以,你很可能是以错误的方式解释说明。如果你想100%确定,你可以反汇编整个房间并检查是否有任何指令指向0x01C0并将其作为原始数据读取,例如瓷砖或其他东西。

          如果您同意分析,请发表评论。