我目前正在编写Gameboy模拟器,我发现了一些对我来说很奇怪的事情。
我的模拟器正在点击跳转指令0xCD
,例如CD B6 FF
,但我的理解是跳转应该只跳到盒式ROM中的地址(0x7FFF
最大值),因为我假设CPU只能执行ROM的指令,而不是RAM。有问题的ROM是马里奥博士,我希望它只能进行有效的操作。 0xFFB6
处于高RAM状态,这对我来说似乎很奇怪。
我的想法是否正确?如果我是,大概这意味着我的程序计数器以某种方式结束于错误的地址并且CB
实际上是另一个指令数据的一部分,而不是指令本身?
我要感谢一些澄清,谢谢。
供参考,我一直在使用Gameboy Opcodes和CPU docs来执行说明。我知道它们包含一些错误,我认为我已经解释了它们(例如,0xE2被列为双字节指令,当它只有一个时)
答案 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)
以下是我的分析:
在原始ROM中查找Unmarshal
我只能在内存的一个位置找到CD B6 FF
(十进制448)。
所以我决定反汇编ROM,看看它是否是有效的指令。
我使用gb-disasm来反汇编ROM。以下是0x01C0
(ROM开始)到地址0x150
的值。
0x201
[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
指令(JUMP
,JP
,JR
,CALL
等)。从那一刻起,程序的流程分成两部分,我们应该遵循这两条路径进行反汇编。这里要理解的是,如果我在ROM中向您显示随机存储位置,则无法告诉我它是数据还是指令。找出方法的唯一方法是遵循程序流程。我们需要定义在跳转目标中开始并以另一个跳转指令结束的代码块。
gb-disasm会跳过不在代码块内的任何内存位置。 RET
标志着一个块的结束。
[0x0000016C] 0x18 0xF6 JR $ F6; 0x164
下一个块从0x16C
开始。我们知道,因为它是位于0x1E8
的跳转的目标地址。
0x150
[0x00000150] 0xC3 0xE8 0x01 JP $01E8
到0x16E
的内存块不被视为代码块。这就是为什么你没有看到内存位置0x1E8
列为指令。所以,你很可能是以错误的方式解释说明。如果你想100%确定,你可以反汇编整个房间并检查是否有任何指令指向0x01C0
并将其作为原始数据读取,例如瓷砖或其他东西。
如果您同意分析,请发表评论。