fix: OBJ white/transparency confusion, implement interrupt dispatch TOCTTOU found in hardware

This commit is contained in:
EliseZeroTwo 2024-01-26 09:10:52 -07:00
parent 8e47afa1bc
commit d60de54d89
Signed by: elise
GPG key ID: FA8F56FFFE6E8B3E
3 changed files with 61 additions and 80 deletions

View file

@ -383,9 +383,9 @@ impl<S: SerialWriter> Gameboy<S> {
0xFF44 => {} // LY is read only 0xFF44 => {} // LY is read only
0xFF45 => self.ppu.set_lyc(&mut self.interrupts, value), 0xFF45 => self.ppu.set_lyc(&mut self.interrupts, value),
0xFF46 => self.dma.init_request(value), 0xFF46 => self.dma.init_request(value),
0xFF47 => self.ppu.bgp.write_bgp(value), 0xFF47 => self.ppu.bgp.write(value),
0xFF48 => self.ppu.obp[0].write_obp(value), 0xFF48 => self.ppu.obp[0].write(value),
0xFF49 => self.ppu.obp[1].write_obp(value), 0xFF49 => self.ppu.obp[1].write(value),
0xFF4A => self.ppu.registers.wy = value, 0xFF4A => self.ppu.registers.wy = value,
0xFF4B => self.ppu.registers.wx = value, 0xFF4B => self.ppu.registers.wx = value,
0xFF4C..=0xFF4E => {} // Unused 0xFF4C..=0xFF4E => {} // Unused

View file

@ -64,7 +64,7 @@ pub struct Registers {
pub current_prefixed_opcode: Option<u8>, pub current_prefixed_opcode: Option<u8>,
pub mem_read_hold: Option<u8>, pub mem_read_hold: Option<u8>,
pub mem_op_happened: bool, pub mem_op_happened: bool,
pub in_interrupt_vector: Option<u8>, pub in_interrupt_vector: bool,
} }
impl PartialEq for Registers { impl PartialEq for Registers {
@ -162,26 +162,14 @@ pub fn tick_cpu(state: &mut Gameboy<impl SerialWriter>) {
} }
if state.registers.cycle == 0 && state.interrupts.ime { if state.registers.cycle == 0 && state.interrupts.ime {
if state.interrupts.read_ie_vblank() && state.interrupts.read_if_vblank() { state.registers.in_interrupt_vector = (state.interrupts.read_ie_vblank()
state.registers.in_interrupt_vector = Some(0); && state.interrupts.read_if_vblank())
|| (state.interrupts.read_ie_lcd_stat() && state.interrupts.read_if_lcd_stat())
|| (state.interrupts.read_ie_timer() && state.interrupts.read_if_timer())
|| (state.interrupts.read_ie_serial() && state.interrupts.read_if_serial())
|| (state.interrupts.read_ie_joypad() && state.interrupts.read_if_joypad());
if state.registers.in_interrupt_vector {
state.interrupts.ime = false; state.interrupts.ime = false;
state.interrupts.write_if_vblank(false);
} else if state.interrupts.read_ie_lcd_stat() && state.interrupts.read_if_lcd_stat() {
state.registers.in_interrupt_vector = Some(1);
state.interrupts.ime = false;
state.interrupts.write_if_lcd_stat(false);
} else if state.interrupts.read_ie_timer() && state.interrupts.read_if_timer() {
state.registers.in_interrupt_vector = Some(2);
state.interrupts.ime = false;
state.interrupts.write_if_timer(false);
} else if state.interrupts.read_ie_serial() && state.interrupts.read_if_serial() {
state.registers.in_interrupt_vector = Some(3);
state.interrupts.ime = false;
state.interrupts.write_if_serial(false);
} else if state.interrupts.read_ie_joypad() && state.interrupts.read_if_joypad() {
state.registers.in_interrupt_vector = Some(4);
state.interrupts.ime = false;
state.interrupts.write_if_joypad(false);
} }
} }
@ -196,7 +184,7 @@ pub fn tick_cpu(state: &mut Gameboy<impl SerialWriter>) {
state.registers.pc = state.registers.pc.overflowing_sub(1).0; state.registers.pc = state.registers.pc.overflowing_sub(1).0;
} }
let result = if let Some(idx) = state.registers.in_interrupt_vector { let result = if state.registers.in_interrupt_vector {
match state.registers.cycle { match state.registers.cycle {
0 => { 0 => {
// Invalidate prefetch if present // Invalidate prefetch if present
@ -214,22 +202,47 @@ pub fn tick_cpu(state: &mut Gameboy<impl SerialWriter>) {
} }
4 => { 4 => {
let original_pc = state.registers.pc; let original_pc = state.registers.pc;
let idx;
if state.interrupts.read_ie_vblank() && state.interrupts.read_if_vblank() {
idx = 0;
state.interrupts.write_if_vblank(false);
} else if state.interrupts.read_ie_lcd_stat() && state.interrupts.read_if_lcd_stat()
{
idx = 1;
state.interrupts.write_if_lcd_stat(false);
} else if state.interrupts.read_ie_timer() && state.interrupts.read_if_timer() {
idx = 2;
state.interrupts.write_if_timer(false);
} else if state.interrupts.read_ie_serial() && state.interrupts.read_if_serial() {
idx = 3;
state.interrupts.write_if_serial(false);
} else if state.interrupts.read_ie_joypad() && state.interrupts.read_if_joypad() {
idx = 4;
state.interrupts.write_if_joypad(false);
} else {
idx = 5;
println!("IRQ disabled!");
}
// assert_eq!(vector, idx);
state.registers.pc = match idx { state.registers.pc = match idx {
0 => 0x40, 0 => 0x40,
1 => 0x48, 1 => 0x48,
2 => 0x50, 2 => 0x50,
3 => 0x58, 3 => 0x58,
4 => 0x60, 4 => 0x60,
5 => 0x00,
_ => unreachable!(), _ => unreachable!(),
}; };
state.registers.in_interrupt_vector = None; state.registers.in_interrupt_vector = false;
state.registers.opcode_bytecount = Some(0);
log::debug!( log::debug!(
"Triggering interrupt to {:#X} from {:#X}", "Triggering interrupt to {:#X} from {:#X}",
state.registers.pc, state.registers.pc,
original_pc original_pc
); );
CycleResult::Finished CycleResult::FinishedKeepPc
} }
_ => unreachable!(), _ => unreachable!(),
} }

View file

@ -46,21 +46,14 @@ impl Palette {
} }
pub fn new_obp() -> Self { pub fn new_obp() -> Self {
Self { id0: Color::Transparent, id1: Color::LGray, id2: Color::DGray, id3: Color::Black } Self { id0: Color::White, id1: Color::LGray, id2: Color::DGray, id3: Color::Black }
} }
pub fn write_bgp(&mut self, value: u8) { pub fn write(&mut self, value: u8) {
self.id0 = Color::from_bg_2bit(value); self.id0 = Color::from_2bit(value);
self.id1 = Color::from_bg_2bit(value >> 2); self.id1 = Color::from_2bit(value >> 2);
self.id2 = Color::from_bg_2bit(value >> 4); self.id2 = Color::from_2bit(value >> 4);
self.id3 = Color::from_bg_2bit(value >> 6); self.id3 = Color::from_2bit(value >> 6);
}
pub fn write_obp(&mut self, value: u8) {
self.id0 = Color::from_obj_2bit(value);
self.id1 = Color::from_obj_2bit(value >> 2);
self.id2 = Color::from_obj_2bit(value >> 4);
self.id3 = Color::from_obj_2bit(value >> 6);
} }
pub fn value(&self) -> u8 { pub fn value(&self) -> u8 {
@ -111,7 +104,6 @@ pub enum Color {
LGray, LGray,
DGray, DGray,
Black, Black,
Transparent,
} }
impl Color { impl Color {
@ -119,14 +111,12 @@ impl Color {
const LGRAY: [u8; PIXEL_SIZE] = [0x88, 0xc0, 0x70, 0xFF]; const LGRAY: [u8; PIXEL_SIZE] = [0x88, 0xc0, 0x70, 0xFF];
const DGRAY: [u8; PIXEL_SIZE] = [0x34, 0x68, 0x56, 0xFF]; const DGRAY: [u8; PIXEL_SIZE] = [0x34, 0x68, 0x56, 0xFF];
const BLACK: [u8; PIXEL_SIZE] = [0x08, 0x18, 0x20, 0xFF]; const BLACK: [u8; PIXEL_SIZE] = [0x08, 0x18, 0x20, 0xFF];
const TRANSPARENT: [u8; PIXEL_SIZE] = [0x00, 0x00, 0x00, 0x00];
pub fn rgba(self) -> &'static [u8; PIXEL_SIZE] { pub fn rgba(self) -> &'static [u8; PIXEL_SIZE] {
match self { match self {
Color::White => &Self::WHITE, Color::White => &Self::WHITE,
Color::LGray => &Self::LGRAY, Color::LGray => &Self::LGRAY,
Color::DGray => &Self::DGRAY, Color::DGray => &Self::DGRAY,
Color::Black => &Self::BLACK, Color::Black => &Self::BLACK,
Color::Transparent => &Self::TRANSPARENT,
} }
} }
@ -137,12 +127,11 @@ impl Color {
Self::LGRAY => Some(Self::LGray), Self::LGRAY => Some(Self::LGray),
Self::DGRAY => Some(Self::DGray), Self::DGRAY => Some(Self::DGray),
Self::BLACK => Some(Self::Black), Self::BLACK => Some(Self::Black),
Self::TRANSPARENT => Some(Self::Transparent),
_ => None, _ => None,
} }
} }
pub fn from_bg_2bit(value: u8) -> Self { pub fn from_2bit(value: u8) -> Self {
match value & 0b11 { match value & 0b11 {
0 => Self::White, 0 => Self::White,
1 => Self::LGray, 1 => Self::LGray,
@ -152,23 +141,12 @@ impl Color {
} }
} }
pub fn from_obj_2bit(value: u8) -> Self {
match value & 0b11 {
0 => Self::Transparent,
1 => Self::LGray,
2 => Self::DGray,
3 => Self::Black,
_ => unreachable!(),
}
}
pub fn to_2bit(&self) -> u8 { pub fn to_2bit(&self) -> u8 {
match self { match self {
Color::White => 0, Color::White => 0,
Color::LGray => 1, Color::LGray => 1,
Color::DGray => 2, Color::DGray => 2,
Color::Black => 3, Color::Black => 3,
Color::Transparent => 0,
} }
} }
@ -226,8 +204,8 @@ impl OAMEntry {
(self.flags >> 5) & 0b1 == 1 (self.flags >> 5) & 0b1 == 1
} }
pub fn palette_number(&self) -> bool { pub fn palette_number(&self) -> usize {
(self.flags >> 4) & 0b1 == 1 (self.flags >> 4) as usize & 0b1
} }
} }
@ -540,17 +518,11 @@ impl Ppu {
(self.registers.lcdc >> 7) == 1 (self.registers.lcdc >> 7) == 1
} }
pub fn set_mode_next(&mut self, mode: PPUMode) { pub fn set_mode(&mut self, mode: PPUMode) {
self.last_mode = Some(self.mode()); self.last_mode = Some(self.mode());
self.registers.mode = mode; self.registers.mode = mode;
} }
pub fn set_mode_now(&mut self, interrupts: &mut Interrupts, mode: PPUMode) {
let last_mode = self.mode();
self.registers.mode = mode;
self.update_mode(interrupts, last_mode)
}
fn update_mode(&mut self, interrupts: &mut Interrupts, last_mode: PPUMode) { fn update_mode(&mut self, interrupts: &mut Interrupts, last_mode: PPUMode) {
let mode = self.mode(); let mode = self.mode();
self.registers.cycles_since_last_last_mode_start_increment[mode.mode_flag() as usize] = 0; self.registers.cycles_since_last_last_mode_start_increment[mode.mode_flag() as usize] = 0;
@ -657,7 +629,7 @@ impl Ppu {
self.total_dots += 1; self.total_dots += 1;
if self.current_dot == 80 { if self.current_dot == 80 {
self.set_mode_next(PPUMode::TransferringData); self.set_mode(PPUMode::TransferringData);
assert_eq!(self.total_dots, 80); assert_eq!(self.total_dots, 80);
} else { } else {
assert!(self.current_dot < 80); assert!(self.current_dot < 80);
@ -696,12 +668,8 @@ impl Ppu {
self.dot_target, self.dot_target,
self.current_draw_state self.current_draw_state
); );
// assert_eq!(self.total_dots, match self.first_frame && self.first_line {
// true => self.dot_target,
// false => 80 + self.dot_target
// });
assert_eq!(self.current_draw_state, Some(LineDrawingState::Finished)); assert_eq!(self.current_draw_state, Some(LineDrawingState::Finished));
self.set_mode_next(PPUMode::HBlank); self.set_mode(PPUMode::HBlank);
} }
false false
@ -714,7 +682,7 @@ impl Ppu {
assert_ne!(self.dot_target, 0); assert_ne!(self.dot_target, 0);
} }
if self.first_line && self.current_dot == 76 && self.dot_target == 0 { if self.first_line && self.current_dot == 76 && self.dot_target == 0 {
self.set_mode_next(PPUMode::TransferringData); self.set_mode(PPUMode::TransferringData);
} else if self.dot_target != 0 && self.current_dot == self.dot_target { } else if self.dot_target != 0 && self.current_dot == self.dot_target {
self.set_scanline(interrupts, self.registers.ly + 1); self.set_scanline(interrupts, self.registers.ly + 1);
@ -734,7 +702,7 @@ impl Ppu {
false => PPUMode::SearchingOAM, false => PPUMode::SearchingOAM,
}; };
self.set_mode_next(next_mode); self.set_mode(next_mode);
} }
false false
@ -746,7 +714,7 @@ impl Ppu {
if self.current_dot % 456 == 0 { if self.current_dot % 456 == 0 {
if self.registers.ly >= 153 { if self.registers.ly >= 153 {
self.set_scanline(interrupts, 0); self.set_scanline(interrupts, 0);
self.set_mode_next(PPUMode::SearchingOAM); self.set_mode(PPUMode::SearchingOAM);
self.first_frame = false; self.first_frame = false;
true true
} else { } else {
@ -932,14 +900,14 @@ impl Ppu {
assert!(sprite_y_idx < 8); assert!(sprite_y_idx < 8);
} }
let color_id = let palette_color_idx =
self.read_obj_tile_colour_id(tile_idx, sprite_x_idx, sprite_y_idx); self.read_obj_tile_colour_id(tile_idx, sprite_x_idx, sprite_y_idx); // If the index is 0, it is just treated as being transparent
let palette = &self.obp[sprite.palette_number() as usize];
let sprite_color = palette.color_from_2bit(color_id);
let sprite_covered = sprite.covered_by_bg_window() && bg_color_id != 0; let sprite_covered = sprite.covered_by_bg_window() && bg_color_id != 0;
if color_id != 0 && !sprite_covered { if palette_color_idx != 0 && !sprite_covered {
let palette = &self.obp[sprite.palette_number()];
let sprite_color = palette.color_from_2bit(palette_color_idx);
let [r, g, b, a] = *sprite_color.rgba(); let [r, g, b, a] = *sprite_color.rgba();
self.sprite_framebuffer[framebuffer_offset + 0] = r; self.sprite_framebuffer[framebuffer_offset + 0] = r;