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 被列为双字节指令,而实际上只有一个)
这是我的分析:
在原始 ROM 中寻找 CD B6 FF
我只能在内存的一个地方找到它 0x01C0
(十进制为 448)。
所以我决定反汇编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
- 我们必须按照指令流程来反汇编 ROM。比如我们知道主程序是从
0x150
位置开始的。所以我们应该从那里开始拆解。然后我们逐个指令执行,直到我们命中任何 JUMP
指令(JP
、JR
、CALL
、RET
等)。从那一刻起,程序的流程就分成了两部分,我们应该按照这两条路径进行反汇编。
这里要理解的是,如果我给你看一个ROM中的随机内存位置,你不能告诉我它是数据还是指令。找出答案的唯一方法是遵循程序流程。我们需要定义以跳转目的地开始并以另一个跳转指令结束的代码块。
gb-disasm 跳过任何不在代码块内的内存位置。 0x16C
标记一个块的结束。
[0x0000016C] 0x18 0xF6 JR $F6 ; 0x164
下一个区块从 0x1E8
开始。我们知道,因为它是位于 0x150
.
上的跳转的目标地址
[0x00000150] 0xC3 0xE8 0x01 JP E8
- 从
0x16E
到 0x1E8
的内存块不被视为代码块。这就是为什么您看不到内存位置 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
我目前正在编写一个 Gameboy 模拟器,我注意到一些对我来说似乎很奇怪的东西。
我的模拟器正在执行跳转指令 0xCD
,例如 CD B6 FF
,但我的理解是跳转应该只跳转到盒式 ROM 中的地址(0x7FFF
最大值), 因为我假设 CPU 只能执行来自 ROM 的指令,而不是 RAM。有问题的 ROM 是马里奥博士,我希望它只会执行有效的操作。 0xFFB6
在高 RAM 中,这对我来说很奇怪。
我的想法正确吗?如果我是,大概这意味着我的程序计数器以某种方式结束在错误的地址并且 CB
实际上是另一条指令数据的一部分,而不是指令本身?
非常感谢您的澄清,谢谢。
作为参考,我一直在使用 Gameboy Opcodes and CPU docs 来执行指令。我知道它们包含一些错误,我想我已经解决了这些错误(例如,0xE2 被列为双字节指令,而实际上只有一个)
这是我的分析:
在原始 ROM 中寻找
CD B6 FF
我只能在内存的一个地方找到它0x01C0
(十进制为 448)。所以我决定反汇编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
- 我们必须按照指令流程来反汇编 ROM。比如我们知道主程序是从
0x150
位置开始的。所以我们应该从那里开始拆解。然后我们逐个指令执行,直到我们命中任何JUMP
指令(JP
、JR
、CALL
、RET
等)。从那一刻起,程序的流程就分成了两部分,我们应该按照这两条路径进行反汇编。
这里要理解的是,如果我给你看一个ROM中的随机内存位置,你不能告诉我它是数据还是指令。找出答案的唯一方法是遵循程序流程。我们需要定义以跳转目的地开始并以另一个跳转指令结束的代码块。
gb-disasm 跳过任何不在代码块内的内存位置。
0x16C
标记一个块的结束。[0x0000016C] 0x18 0xF6 JR $F6 ; 0x164
下一个区块从 0x1E8
开始。我们知道,因为它是位于 0x150
.
[0x00000150] 0xC3 0xE8 0x01 JP E8
- 从
0x16E
到0x1E8
的内存块不被视为代码块。这就是为什么您看不到内存位置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