Gameboy emulation - 需要对 CD 说明进行说明

Gameboy emulation - Clarification need on CD instruction

我目前正在编写一个 Gameboy 模拟器,我注意到一些对我来说似乎很奇怪的东西。

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

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

非常感谢您的澄清,谢谢。

作为参考,我一直在使用 Gameboy Opcodes and CPU docs 来执行指令。我知道它们包含一些错误,我想我已经解决了这些错误(例如,0xE2 被列为双字节指令,而实际上只有一个)

这是我的分析:

  1. 在原始 ROM 中寻找 CD B6 FF 我只能在内存的一个地方找到它 0x01C0(十进制为 448)。

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

我用gb-disasm拆了ROM。这是从 0x150(ROM 开始)到地址 0x201.

的值
[0x00000100] 0x00           NOP
[0x00000101] 0xC3 0x50 0x01 JP 50
[0x00000150] 0xC3 0xE8 0x01 JP E8
[0x00000153] 0x01 0x0E 0xD0 LD BC,$D00E
[0x00000156] 0x0A           LD A,[BC]
[0x00000157] 0xA7           AND A
[0x00000158] 0x20 0x0D      JR NZ,[=10=]D ; 0x167
[0x0000015A] 0xF0 0xCF      LDH A,[$CF] ; HIMEM
[0x0000015C] 0xFE 0xFE      CP $FE
[0x0000015E] 0x20 0x04      JR NZ, ; 0x164
[0x00000160] 0x3E 0x01      LD A,
[0x00000162] 0x18 0x01      JR  ; 0x165
[0x00000164] 0xAF           XOR A
[0x00000165] 0x02           LD [BC],A
[0x00000166] 0xC9           RET
[0x00000167] 0xFA 0x46 0xD0 LD A,[$D046]
[0x0000016A] 0xE0 0x01      LDH [],A ; SB
[0x0000016C] 0x18 0xF6      JR $F6 ; 0x164
[0x000001E8] 0xAF           XOR A
[0x000001E9] 0x21 0xFF 0xDF LD HL,$DFFF
[0x000001EC] 0x0E 0x10      LD C,
[0x000001EE] 0x06 0x00      LD B,[=10=]
[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,[=10=]D
[0x000001F9] 0xF3           DI
[0x000001FA] 0xE0 0x0F      LDH [[=10=]F],A ; IF
[0x000001FC] 0xE0 0xFF      LDH [$FF],A ; IE
[0x000001FE] 0xAF           XOR A
[0x000001FF] 0xE0 0x42      LDH [],A ; SCY
[0x00000201] 0xE0 0x43      LDH [],A ; SCX
  1. 我们必须按照指令流程来反汇编 ROM。比如我们知道主程序是从0x150位置开始的。所以我们应该从那里开始拆解。然后我们逐个指令执行,直到我们命中任何 JUMP 指令(JPJRCALLRET 等)。从那一刻起,程序的流程就分成了两部分,我们应该按照这两条路径进行反汇编。

这里要理解的是,如果我给你看一个ROM中的随机内存位置,你不能告诉我它是数据还是指令。找出答案的唯一方法是遵循程序流程。我们需要定义以跳转目的地开始并以另一个跳转指令结束的代码块。

  1. gb-disasm 跳过任何不在代码块内的内存位置。 0x16C 标记一个块的结束。

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

下一个区块从 0x1E8 开始。我们知道,因为它是位于 0x150.

上的跳转的目标地址
[0x00000150] 0xC3 0xE8 0x01 JP E8
  1. 0x16E0x1E8 的内存块不被视为代码块。这就是为什么您看不到内存位置 0x01C0 作为指令列出的原因。

所以,您很可能以错误的方式解释了说明。如果你想百分百确定,你可以拆开整个房间,检查是否有任何指令指向0x16E-0x1E8并将其读取为原始数据,例如瓷砖或其他东西。

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

刚刚查看了Dr. Mario 1.1,它在启动时复制hFFB6处的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