diff --git a/Core/display.c b/Core/display.c index 577d1b2..6dc7a25 100644 --- a/Core/display.c +++ b/Core/display.c @@ -465,34 +465,55 @@ void GB_lcd_off(GB_gameboy_t *gb) } } +static inline uint8_t oam_read(GB_gameboy_t *gb, uint8_t addr) +{ + if (unlikely(gb->oam_ppu_blocked)) { + return 0xFF; + } + if (unlikely(gb->dma_current_dest <= 0xa0 && gb->dma_current_dest > 0)) { // TODO: what happens in the last and first M cycles? + return gb->oam[((gb->dma_current_dest - 1 + (gb->halted || gb->stopped)) & ~1) | (addr & 1)]; + } + return gb->oam[addr]; +} + static void add_object_from_index(GB_gameboy_t *gb, unsigned index) { if (gb->n_visible_objs == 10) return; /* TODO: It appears that DMA blocks PPU access to OAM, but it needs verification. */ - if (GB_is_dma_active(gb)) { - return; + if (unlikely(GB_is_dma_active(gb))) { + if (!gb->halted && !gb->stopped) { + return; + } + if (gb->model < GB_MODEL_CGB_E) { + return; + } + /* CGB-0 to CGB-D: Halted DMA still blocks Mode 2; + Pre-CGB: Unit specific behavior, some units read FFs, some units read using + several different corruption pattterns. For simplicity, we emulate + FFs. */ } - if (gb->oam_ppu_blocked) { + if (unlikely(gb->oam_ppu_blocked)) { return; } /* This reverse sorts the visible objects by location and priority */ - object_t *objects = (object_t *) &gb->oam; + uint8_t oam_y = oam_read(gb, index * 4); + uint8_t oam_x = oam_read(gb, index * 4) + 1; bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; - signed y = objects[index].y - 16; + signed y = oam_y - 16; if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) { unsigned j = 0; for (; j < gb->n_visible_objs; j++) { - if (gb->objects_x[j] <= objects[index].x) break; + if (gb->objects_x[j] <= oam_x) break; } memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j); memmove(gb->objects_x + j + 1, gb->objects_x + j, gb->n_visible_objs - j); memmove(gb->objects_y + j + 1, gb->objects_y + j, gb->n_visible_objs - j); gb->visible_objs[j] = index; - gb->objects_x[j] = objects[index].x; - gb->objects_y[j] = objects[index].y; + gb->objects_x[j] = oam_y; + gb->objects_y[j] = oam_y; gb->n_visible_objs++; } } @@ -680,24 +701,25 @@ static inline uint8_t vram_read(GB_gameboy_t *gb, uint16_t addr) /* TODO: AGS has its own, very different pattern, but AGS is not currently a supported model */ /* TODO: Research this when researching odd modes */ /* TODO: probably not 100% on the first few reads during halt/stop modes*/ + unsigned offset = 1 - (gb->halted || gb->stopped); if (GB_is_cgb(gb)) { if (gb->dma_ppu_vram_conflict) { addr = (gb->dma_ppu_vram_conflict_addr & 0x1FFF) | (addr & 0x2000); } else if (gb->dma_cycles_modulo && !gb->halted && !gb->stopped) { addr &= 0x2000; - addr |= ((gb->dma_current_src - 1) & 0x1FFF); + addr |= ((gb->dma_current_src - offset) & 0x1FFF); } else { - addr &= 0x2000 | ((gb->dma_current_src - 1) & 0x1FFF); + addr &= 0x2000 | ((gb->dma_current_src - offset) & 0x1FFF); gb->dma_ppu_vram_conflict_addr = addr; gb->dma_ppu_vram_conflict = !gb->halted && !gb->stopped; } } else { - addr |= ((gb->dma_current_src - 1) & 0x1FFF); + addr |= ((gb->dma_current_src - offset) & 0x1FFF); } - gb->oam[gb->dma_current_dest - 1] = gb->vram[(addr & 0x1FFF) | (gb->cgb_vram_bank? 0x2000 : 0)]; + gb->oam[gb->dma_current_dest - offset] = gb->vram[(addr & 0x1FFF) | (gb->cgb_vram_bank? 0x2000 : 0)]; } return gb->vram[addr]; } @@ -875,17 +897,6 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles) } } -static inline uint8_t oam_read(GB_gameboy_t *gb, uint8_t addr) -{ - if (unlikely(gb->oam_ppu_blocked)) { - return 0xFF; - } - if (unlikely(gb->dma_current_dest <= 0xa0 && gb->dma_current_dest > 0)) { // TODO: what happens in the last and first M cycles? - return gb->oam[((gb->dma_current_dest - 1) & ~1) | (addr & 1)]; - } - return gb->oam[addr]; -} - static uint16_t get_object_line_address(GB_gameboy_t *gb, uint8_t y, uint8_t tile, uint8_t flags) { bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */ diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 1915705..e3668d0 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -354,6 +354,9 @@ static void enter_stop_mode(GB_gameboy_t *gb) static void leave_stop_mode(GB_gameboy_t *gb) { gb->stopped = false; + // TODO: verify this + gb->dma_cycles = 4; + GB_dma_run(gb); gb->oam_ppu_blocked = false; gb->vram_ppu_blocked = false; gb->cgb_palettes_ppu_blocked = false; @@ -373,8 +376,6 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) // When entering with IF&IE, the 2nd byte of STOP is actually executed if (!exit_by_joyp) { if (!immediate_exit) { - // TODO: verify this! - gb->dma_cycles = 4; GB_dma_run(gb); } enter_stop_mode(gb); @@ -419,8 +420,6 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) if (immediate_exit) { leave_stop_mode(gb); if (!interrupt_pending) { - // TODO: verify this! - gb->dma_cycles = 4; GB_dma_run(gb); gb->halted = true; gb->just_halted = true; @@ -1024,9 +1023,6 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode) } } else { - // TODO: verify the timing! - gb->dma_cycles = 4; - GB_dma_run(gb); gb->halted = true; } gb->just_halted = true; @@ -1625,12 +1621,17 @@ void GB_cpu_run(GB_gameboy_t *gb) /* Wake up from HALT mode without calling interrupt code. */ if (gb->halted && !effective_ime && interrupt_queue) { gb->halted = false; + gb->dma_cycles = 4; + GB_dma_run(gb); gb->speed_switch_halt_countdown = 0; } /* Call interrupt */ else if (effective_ime && interrupt_queue) { gb->halted = false; + // TODO: verify the timing! + gb->dma_cycles = 4; + GB_dma_run(gb); gb->speed_switch_halt_countdown = 0; uint16_t call_addr = gb->pc;