diff --git a/.forgejo/workflows/action.yml b/.forgejo/workflows/action.yml index 9f9f1de..1e27741 100644 --- a/.forgejo/workflows/action.yml +++ b/.forgejo/workflows/action.yml @@ -46,6 +46,10 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/daa.gb test -m 100000000 -s meowgb-tests/expected_output/daa.bin + - name: Run test ROM (mooneye-test-suite di_timing-GS) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/di_timing-GS.gb test -m 100000000 -s meowgb-tests/expected_output/di_timing-GS.bin + - name: Run test ROM (mooneye-test-suite div_timing) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/div_timing.gb test -m 100000000 -s meowgb-tests/expected_output/div_timing.bin @@ -66,6 +70,18 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/halt_ime0_ei.gb test -m 100000000 -s meowgb-tests/expected_output/halt_ime0_ei.bin + - name: Run test ROM (mooneye-test-suite halt_ime0_nointr_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/halt_ime0_nointr_timing.gb test -m 100000000 -s meowgb-tests/expected_output/halt_ime0_nointr_timing.bin + + - name: Run test ROM (mooneye-test-suite halt_ime1_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/halt_ime1_timing.gb test -m 100000000 -s meowgb-tests/expected_output/halt_ime1_timing.bin + + - name: Run test ROM (mooneye-test-suite halt_ime1_timing2-GS) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/halt_ime1_timing2-GS.gb test -m 100000000 -s meowgb-tests/expected_output/halt_ime1_timing2-GS.bin + - name: Run test ROM (mooneye-test-suite intr_1_2_timing-GS) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/intr_1_2_timing-GS.gb test -m 100000000 -s meowgb-tests/expected_output/intr_1_2_timing-GS.bin @@ -90,6 +106,14 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/oam_dma_timing.gb test -m 100000000 -s meowgb-tests/expected_output/oam_dma_timing.bin + - name: Run test ROM (mooneye-test-suite pop_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/pop_timing.gb test -m 100000000 -s meowgb-tests/expected_output/pop_timing.bin + + - name: Run test ROM (mooneye-test-suite push_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/push_timing.gb test -m 100000000 -s meowgb-tests/expected_output/push_timing.bin + - name: Run test ROM (mooneye-test-suite rapid_di_ei) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/rapid_di_ei.gb test -m 100000000 -s meowgb-tests/expected_output/rapid_di_ei.bin @@ -106,6 +130,10 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/reg_read.gb test -m 100000000 -s meowgb-tests/expected_output/reg_read.bin + - name: Run test ROM (mooneye-test-suite rst_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/rst_timing.gb test -m 100000000 -s meowgb-tests/expected_output/rst_timing.bin + - name: Run test ROM (mooneye-test-suite stat_irq_blocking) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/stat_irq_blocking.gb test -m 100000000 -s meowgb-tests/expected_output/stat_irq_blocking.bin diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 9f9f1de..1e27741 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -46,6 +46,10 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/daa.gb test -m 100000000 -s meowgb-tests/expected_output/daa.bin + - name: Run test ROM (mooneye-test-suite di_timing-GS) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/di_timing-GS.gb test -m 100000000 -s meowgb-tests/expected_output/di_timing-GS.bin + - name: Run test ROM (mooneye-test-suite div_timing) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/div_timing.gb test -m 100000000 -s meowgb-tests/expected_output/div_timing.bin @@ -66,6 +70,18 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/halt_ime0_ei.gb test -m 100000000 -s meowgb-tests/expected_output/halt_ime0_ei.bin + - name: Run test ROM (mooneye-test-suite halt_ime0_nointr_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/halt_ime0_nointr_timing.gb test -m 100000000 -s meowgb-tests/expected_output/halt_ime0_nointr_timing.bin + + - name: Run test ROM (mooneye-test-suite halt_ime1_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/halt_ime1_timing.gb test -m 100000000 -s meowgb-tests/expected_output/halt_ime1_timing.bin + + - name: Run test ROM (mooneye-test-suite halt_ime1_timing2-GS) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/halt_ime1_timing2-GS.gb test -m 100000000 -s meowgb-tests/expected_output/halt_ime1_timing2-GS.bin + - name: Run test ROM (mooneye-test-suite intr_1_2_timing-GS) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/intr_1_2_timing-GS.gb test -m 100000000 -s meowgb-tests/expected_output/intr_1_2_timing-GS.bin @@ -90,6 +106,14 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/oam_dma_timing.gb test -m 100000000 -s meowgb-tests/expected_output/oam_dma_timing.bin + - name: Run test ROM (mooneye-test-suite pop_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/pop_timing.gb test -m 100000000 -s meowgb-tests/expected_output/pop_timing.bin + + - name: Run test ROM (mooneye-test-suite push_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/push_timing.gb test -m 100000000 -s meowgb-tests/expected_output/push_timing.bin + - name: Run test ROM (mooneye-test-suite rapid_di_ei) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/rapid_di_ei.gb test -m 100000000 -s meowgb-tests/expected_output/rapid_di_ei.bin @@ -106,6 +130,10 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/reg_read.gb test -m 100000000 -s meowgb-tests/expected_output/reg_read.bin + - name: Run test ROM (mooneye-test-suite rst_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/rst_timing.gb test -m 100000000 -s meowgb-tests/expected_output/rst_timing.bin + - name: Run test ROM (mooneye-test-suite stat_irq_blocking) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/stat_irq_blocking.gb test -m 100000000 -s meowgb-tests/expected_output/stat_irq_blocking.bin diff --git a/meowgb-core/Cargo.toml b/meowgb-core/Cargo.toml index ab87a15..3ebd2f7 100644 --- a/meowgb-core/Cargo.toml +++ b/meowgb-core/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +instr-dbg = [] [dependencies] log = "0.4.14" diff --git a/meowgb-core/src/gameboy.rs b/meowgb-core/src/gameboy.rs index 7b76c9f..541b9f1 100644 --- a/meowgb-core/src/gameboy.rs +++ b/meowgb-core/src/gameboy.rs @@ -24,6 +24,8 @@ use self::{ serial::{Serial, SerialWriter}, sound::Sound, }; +#[cfg(feature = "instr-dbg")] +use crate::ringbuffer::RingBuffer; pub struct Gameboy { pub ppu: Ppu, @@ -43,6 +45,12 @@ pub struct Gameboy { pub stop: bool, pub tick_count: u8, + + pub last_read: Option<(u16, u8)>, + pub last_write: Option<(u16, u8)>, + + #[cfg(feature = "instr-dbg")] + pub pc_history: RingBuffer, } impl Gameboy { @@ -66,6 +74,10 @@ impl Gameboy { used_halt_bug: false, stop: false, tick_count: 0, + last_read: None, + last_write: None, + #[cfg(feature = "instr-dbg")] + pc_history: RingBuffer::new(), } } @@ -221,10 +233,7 @@ impl Gameboy { } 0xFF41 => self.ppu.set_stat(&mut self.interrupts, value), 0xFF42 => self.ppu.registers.scy = value, - 0xFF43 => { - // println!("Setting SCX to {} from {}", value, self.ppu.registers.scx); - self.ppu.registers.scx = value; - }, + 0xFF43 => self.ppu.registers.scx = value, 0xFF44 => {} // LY is read only 0xFF45 => self.ppu.set_lyc(&mut self.interrupts, value), 0xFF46 => self.dma.init_request(value), @@ -312,6 +321,10 @@ impl Gameboy { } pub fn cpu_read_u8(&mut self, address: u16) { + self.cpu_read_u8_internal(address, false); + } + + pub fn cpu_read_u8_internal(&mut self, address: u16, is_next_pc: bool) { assert!(!self.registers.mem_op_happened); assert!(self.registers.mem_read_hold.is_none()); self.registers.mem_op_happened = true; @@ -342,12 +355,16 @@ impl Gameboy { 0xFFFF => self.interrupts.interrupt_enable, }, }; + if !is_next_pc { + self.last_read = Some((address, value)); + } self.registers.mem_read_hold = Some(value); } pub fn cpu_write_u8(&mut self, address: u16, value: u8) { assert!(!self.registers.mem_op_happened); self.registers.mem_op_happened = true; + self.last_write = Some((address, value)); match self.ppu.dma_occuring { true => match address { diff --git a/meowgb-core/src/gameboy/cpu.rs b/meowgb-core/src/gameboy/cpu.rs index 0ebe4d2..b68ba2f 100644 --- a/meowgb-core/src/gameboy/cpu.rs +++ b/meowgb-core/src/gameboy/cpu.rs @@ -142,6 +142,8 @@ impl Registers { pub fn tick_cpu(state: &mut Gameboy) { state.registers.mem_op_happened = false; + state.last_read = None; + state.last_write = None; if state.joypad.interrupt_triggered { state.joypad.interrupt_triggered = false; @@ -162,7 +164,6 @@ pub fn tick_cpu(state: &mut Gameboy) { } } - // TODO: Interrupts if state.registers.cycle == 0 && state.interrupts.ime { if state.interrupts.read_ie_vblank() && state.interrupts.read_if_vblank() { state.registers.in_interrupt_vector = Some(0); @@ -244,7 +245,7 @@ pub fn tick_cpu(state: &mut Gameboy) { opcode } None => { - state.cpu_read_u8(state.registers.pc); + state.cpu_read_u8_internal(state.registers.pc, true); return; } }, @@ -533,7 +534,7 @@ pub fn tick_cpu(state: &mut Gameboy) { } if !state.registers.mem_op_happened { - state.cpu_read_u8(state.registers.pc); + state.cpu_read_u8_internal(state.registers.pc, true); } state.registers.cycle = 0; diff --git a/meowgb-core/src/gameboy/dma.rs b/meowgb-core/src/gameboy/dma.rs index b774e35..65fc66b 100644 --- a/meowgb-core/src/gameboy/dma.rs +++ b/meowgb-core/src/gameboy/dma.rs @@ -38,25 +38,25 @@ impl DmaState { if self.remaining_cycles > 0 { let offset = 0xA0 - self.remaining_cycles; + let read_address = ((self.original_base as usize) << 8) | offset as usize; let value = if self.original_base <= 0x7F { match cartridge { - Some(cart) => cart.read_rom_u8((self.base as u16) << 8 | offset as u16), + Some(cart) => cart.read_rom_u8(read_address as u16), None => 0xFF, } } else if self.original_base <= 0x9F { - let address = (((self.original_base as usize) << 8) | offset as usize) - 0x8000; + let address = read_address - 0x8000; ppu.vram[address] } else if self.original_base <= 0xDF { - let address = ((self.original_base as usize) << 8 | offset as usize) - 0xC000; + let address = read_address - 0xC000; memory.wram[address] } else if self.original_base <= 0xFD { - let address = ((self.original_base as usize) << 8 | offset as usize) - 0xE000; + let address = read_address - 0xE000; memory.wram[address] } else { 0xFF }; - ppu.dma_write_oam(offset, value); self.remaining_cycles -= 1; } diff --git a/meowgb-core/src/gameboy/mapper.rs b/meowgb-core/src/gameboy/mapper.rs index a762fc1..1d0931f 100644 --- a/meowgb-core/src/gameboy/mapper.rs +++ b/meowgb-core/src/gameboy/mapper.rs @@ -41,17 +41,15 @@ impl Mapper for NoMBC { fn write_rom_u8(&mut self, _address: u16, _value: u8) {} fn read_eram_u8(&self, address: u16) -> u8 { - let decoded_address = address - 0xA000; match &self.ram { - Some(ram) => ram[decoded_address as usize], + Some(ram) => ram[address as usize], None => 0, } } fn write_eram_u8(&mut self, address: u16, value: u8) { - let decoded_address = address - 0xA000; if let Some(ram) = &mut self.ram { - ram[decoded_address as usize] = value; + ram[address as usize] = value; } } } diff --git a/meowgb-core/src/gameboy/ppu.rs b/meowgb-core/src/gameboy/ppu.rs index 11b4b52..685dfed 100644 --- a/meowgb-core/src/gameboy/ppu.rs +++ b/meowgb-core/src/gameboy/ppu.rs @@ -550,8 +550,10 @@ impl Ppu { } fn set_scanline(&mut self, interrupts: &mut Interrupts, scanline: u8) { - // println!("LY incrementing: {} cycles since last incrementation. cycles since: {:?}", self.registers.cycles_since_last_ly_increment, self.registers.cycles_since_last_last_mode_start_increment.iter().enumerate().map(|(idx, value)| { - // let idx_enum = match idx { + // println!("LY incrementing: {} cycles since last incrementation. cycles since: + // {:?}", self.registers.cycles_since_last_ly_increment, + // self.registers.cycles_since_last_last_mode_start_increment.iter(). + // enumerate().map(|(idx, value)| { let idx_enum = match idx { // 0 => PPUMode::HBlank, // 1 => PPUMode::VBlank, // 2 => PPUMode::SearchingOAM, @@ -638,7 +640,6 @@ impl Ppu { self.total_dots += 1; if self.current_dot == self.dot_target { - // println!("Mode 3 DT: {}", self.dot_target); // assert_eq!(self.total_dots, match self.first_frame && self.first_line { // true => self.dot_target, // false => 80 + self.dot_target @@ -659,15 +660,14 @@ impl Ppu { if self.first_line && self.current_dot == 80 && self.dot_target == 0 { self.set_mode(interrupts, PPUMode::TransferringData); } else if self.dot_target != 0 && self.current_dot == self.dot_target { - // println!("Mode 0 DT: {}", self.dot_target); self.set_scanline(interrupts, self.registers.ly + 1); assert_eq!( self.total_dots, - 456 // match self.first_frame && self.first_line { - // true => 456 - (80 - 64), - // false => 456, - // } + 456 /* match self.first_frame && self.first_line { + * true => 456 - (80 - 64), + * false => 456, + * } */ ); self.total_dots = 0; self.first_line = false; @@ -729,10 +729,8 @@ impl Ppu { Some(state) => state, None => { let scrolling_delay = self.registers.scx % 8; - // println!("{}", scrolling_delay); self.dot_target += scrolling_delay as u16; if scrolling_delay != 0 { - // println!("Scrolling delay of {:#X} with scx of {}", scrolling_delay, self.registers.scx); LineDrawingState::BackgroundScrolling( scrolling_delay as usize, self.registers.scx, diff --git a/meowgb-core/src/lib.rs b/meowgb-core/src/lib.rs index 06dd4a4..eb33e04 100644 --- a/meowgb-core/src/lib.rs +++ b/meowgb-core/src/lib.rs @@ -1,4 +1,5 @@ pub mod gameboy; +#[cfg(feature = "instr-dbg")] pub mod ringbuffer; /// A helper for writing CPU tests in Rust, the emulator returned by this diff --git a/meowgb-core/src/ringbuffer.rs b/meowgb-core/src/ringbuffer.rs index 8442ce3..41647c5 100644 --- a/meowgb-core/src/ringbuffer.rs +++ b/meowgb-core/src/ringbuffer.rs @@ -68,3 +68,24 @@ fn test_ringbuffer() { &[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31] ); } + +impl std::fmt::Display + for RingBuffer +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut offset = self.read_ptr; + + f.write_str("[\n")?; + + for idx in 0..self.size { + f.write_fmt(format_args!(" {}: {:#X},\n", idx, self.buffer[offset]))?; + + offset += 1; + offset %= SIZE; + } + + f.write_str("]")?; + + Ok(()) + } +} diff --git a/meowgb-tests/expected_output/di_timing-GS.bin b/meowgb-tests/expected_output/di_timing-GS.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/di_timing-GS.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/halt_ime0_nointr_timing.bin b/meowgb-tests/expected_output/halt_ime0_nointr_timing.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/halt_ime0_nointr_timing.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/halt_ime1_timing.bin b/meowgb-tests/expected_output/halt_ime1_timing.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/halt_ime1_timing.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/halt_ime1_timing2-GS.bin b/meowgb-tests/expected_output/halt_ime1_timing2-GS.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/halt_ime1_timing2-GS.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/pop_timing.bin b/meowgb-tests/expected_output/pop_timing.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/pop_timing.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/push_timing.bin b/meowgb-tests/expected_output/push_timing.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/push_timing.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/rst_timing.bin b/meowgb-tests/expected_output/rst_timing.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/rst_timing.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb/Cargo.toml b/meowgb/Cargo.toml index dce249b..9381cd1 100644 --- a/meowgb/Cargo.toml +++ b/meowgb/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +debugger = [] +instr-dbg = ["meowgb-core/instr-dbg"] [dependencies] meowgb-core = { path = "../meowgb-core" } diff --git a/meowgb/src/main.rs b/meowgb/src/main.rs index e6a2f29..4421d64 100644 --- a/meowgb/src/main.rs +++ b/meowgb/src/main.rs @@ -17,8 +17,24 @@ use meowgb_core::gameboy::{ serial::SerialWriter, Gameboy, }; -use window::events::{EmulatorWindowEvent, GameboyEvent}; +use window::events::{EmulatorDebugEvent, EmulatorWindowEvent, GameboyEvent}; +#[cfg(feature = "debugger")] +#[derive(Debug, Parser)] +/// DMG Emulator +pub struct CliArgs { + /// bootrom path + #[clap(long)] + pub bootrom: Option, + /// game path + #[clap(long)] + pub rom: Option, + /// start the emulator in debug mode + #[clap(short, long)] + pub debug: bool, +} + +#[cfg(not(feature = "debugger"))] #[derive(Debug, Parser)] /// DMG Emulator pub struct CliArgs { @@ -60,7 +76,13 @@ fn real_main() -> Result<(), MeowGBError> { None => None, }; - let gameboy = Arc::new(RwLock::new(Gameboy::new(bootrom, std::io::stdout()))); + let mut gameboy = WrappedGameboy::new(Gameboy::new(bootrom, std::io::stdout())); + #[cfg(feature = "debugger")] + let dbg = args.debug; + #[cfg(not(feature = "debugger"))] + let dbg = false; + gameboy.debugging = dbg; + let gameboy = Arc::new(RwLock::new(gameboy)); let gameboy_2 = gameboy.clone(); let jh = std::thread::Builder::new() @@ -88,9 +110,21 @@ fn main() { } } +pub struct WrappedGameboy { + pub breakpoints: [[bool; 3]; 0x10000], + pub debugging: bool, + pub gameboy: Gameboy, +} + +impl WrappedGameboy { + pub fn new(gameboy: Gameboy) -> Self { + Self { breakpoints: [[false; 3]; 0x10000], debugging: false, gameboy } + } +} + pub fn run_gameboy( args: CliArgs, - gameboy_arc: Arc>>, + gameboy_arc: Arc>>, rx: Receiver, tx: Sender, ) -> Result<(), MeowGBError> { @@ -103,50 +137,94 @@ pub fn run_gameboy( let rom = std::fs::read(rom.as_path())?; - gameboy.load_cartridge(rom) + gameboy.gameboy.load_cartridge(rom) } drop(gameboy); let mut goal = time::OffsetDateTime::now_utc() + time::Duration::milliseconds(1000 / 60); let mut frame_counter = 0; + let mut debugging_tbf = None; 'outer: loop { - let mut gameboy = gameboy_arc.write().unwrap(); + let mut step = false; + let mut gameboy = gameboy_arc.write().unwrap(); while let Ok(event) = rx.try_recv() { match event { - EmulatorWindowEvent::AToggle => gameboy.joypad.invert_a(), - EmulatorWindowEvent::BToggle => gameboy.joypad.invert_b(), - EmulatorWindowEvent::SelectToggle => gameboy.joypad.invert_select(), - EmulatorWindowEvent::StartToggle => gameboy.joypad.invert_start(), - EmulatorWindowEvent::UpToggle => gameboy.joypad.invert_up(), - EmulatorWindowEvent::DownToggle => gameboy.joypad.invert_down(), - EmulatorWindowEvent::LeftToggle => gameboy.joypad.invert_left(), - EmulatorWindowEvent::RightToggle => gameboy.joypad.invert_right(), - EmulatorWindowEvent::PauseToggle => unimplemented!(), + EmulatorWindowEvent::AToggle => gameboy.gameboy.joypad.invert_a(), + EmulatorWindowEvent::BToggle => gameboy.gameboy.joypad.invert_b(), + EmulatorWindowEvent::SelectToggle => gameboy.gameboy.joypad.invert_select(), + EmulatorWindowEvent::StartToggle => gameboy.gameboy.joypad.invert_start(), + EmulatorWindowEvent::UpToggle => gameboy.gameboy.joypad.invert_up(), + EmulatorWindowEvent::DownToggle => gameboy.gameboy.joypad.invert_down(), + EmulatorWindowEvent::LeftToggle => gameboy.gameboy.joypad.invert_left(), + EmulatorWindowEvent::RightToggle => gameboy.gameboy.joypad.invert_right(), EmulatorWindowEvent::Exit => break 'outer, + EmulatorWindowEvent::Debug(EmulatorDebugEvent::ToggleBreakpoint(addr, breaks)) => { + gameboy.breakpoints[addr as usize] = breaks; + } + EmulatorWindowEvent::Debug(EmulatorDebugEvent::Continue) => { + gameboy.debugging = false; + if let Some(debugging_tbf) = debugging_tbf.take() { + let delta = time::OffsetDateTime::now_utc() - debugging_tbf; + goal += delta; + } + } + EmulatorWindowEvent::Debug(EmulatorDebugEvent::Step) => { + step = true; + } } } - let redraw_needed = gameboy.tick_4(); + if !gameboy.debugging || step { + let needs_redraw = gameboy.gameboy.tick_4(); + let bp_triggered = gameboy + .gameboy + .last_read + .map(|(addr, _)| gameboy.breakpoints[addr as usize][0]) + .unwrap_or_default() + || gameboy + .gameboy + .last_write + .map(|(addr, _)| gameboy.breakpoints[addr as usize][1]) + .unwrap_or_default() + || gameboy.breakpoints[gameboy.gameboy.registers.pc as usize][2]; + gameboy.debugging |= bp_triggered; - drop(gameboy); + if bp_triggered || step { + gameboy.debugging = bp_triggered; - if redraw_needed { - let now = time::OffsetDateTime::now_utc(); - frame_counter += 1; - tx.send(GameboyEvent::Framebuffer(gameboy_arc.read().unwrap().ppu.write_fb())).unwrap(); - let delta = goal - now; - let delta_ms = delta.whole_milliseconds(); - if delta_ms > 0 { - std::thread::sleep(std::time::Duration::from_millis(delta_ms as u64)); + let now = time::OffsetDateTime::now_utc(); + + if let Some(debugging_tbf) = debugging_tbf { + let delta = now - debugging_tbf; + goal += delta; + } + + debugging_tbf = Some(now); } - goal = goal + time::Duration::milliseconds(1000 / 60); - if frame_counter == 60 { - log::debug!("Rendered 60 frames"); - frame_counter = 0; + drop(gameboy); + + if needs_redraw { + let now = time::OffsetDateTime::now_utc(); + frame_counter += 1; + tx.send(GameboyEvent::Framebuffer( + gameboy_arc.read().unwrap().gameboy.ppu.write_fb(), + )) + .unwrap(); + let delta = goal - now; + let delta_ms = delta.whole_milliseconds(); + if delta_ms > 0 { + std::thread::sleep(std::time::Duration::from_millis(delta_ms as u64)); + } + goal = goal + time::Duration::milliseconds(1000 / 60); + + if frame_counter == 60 { + log::debug!("Rendered 60 frames"); + frame_counter = 0; + } } } } diff --git a/meowgb/src/window.rs b/meowgb/src/window.rs index f645fd3..ed571a7 100644 --- a/meowgb/src/window.rs +++ b/meowgb/src/window.rs @@ -1,4 +1,5 @@ pub mod events; +#[cfg(feature = "debugger")] mod overlay; use std::sync::{ @@ -6,7 +7,10 @@ use std::sync::{ Arc, RwLock, }; -use meowgb_core::gameboy::{serial::SerialWriter, Gameboy}; +use events::{EmulatorWindowEvent, GameboyEvent, Keymap}; +use meowgb_core::gameboy::serial::SerialWriter; +#[cfg(feature = "debugger")] +use overlay::Framework; use pixels::{Pixels, SurfaceTexture}; use winit::{ event::Event, @@ -15,11 +19,7 @@ use winit::{ }; use winit_input_helper::WinitInputHelper; -use self::{ - events::{EmulatorWindowEvent, GameboyEvent, Keymap}, - overlay::Framework, -}; -use crate::config::MeowGBConfig; +use crate::{config::MeowGBConfig, WrappedGameboy}; macro_rules! define_keypress { ($input:ident, $config:ident, $keymap:ident, $tx:ident, $key:ident, $event:ident) => { @@ -42,10 +42,13 @@ macro_rules! define_keypress { pub fn run_window( rom_name: &str, config: MeowGBConfig, - gameboy: Arc>>, + gameboy: Arc>>, rx: Receiver, tx: Sender, ) { + #[cfg(not(feature = "debugger"))] + drop(gameboy); + let event_loop = EventLoop::new(); let mut input = WinitInputHelper::new(); @@ -53,26 +56,27 @@ pub fn run_window( WindowBuilder::new().with_title(format!("Meow - {}", rom_name)).build(&event_loop).unwrap() }; - let (mut pixels, mut framework) = { - let window_size = window.inner_size(); - let scale_factor = window.scale_factor() as f32; - let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); - let pixels = Pixels::new( - meowgb_core::gameboy::ppu::FB_WIDTH, - meowgb_core::gameboy::ppu::FB_HEIGHT, - surface_texture, - ) - .unwrap(); - let framework = Framework::new( - &event_loop, - window_size.width, - window_size.height, - scale_factor, - &pixels, - &gameboy.read().unwrap(), - ); - (pixels, framework) - }; + let window_size = window.inner_size(); + #[cfg(feature = "debugger")] + let scale_factor = window.scale_factor() as f32; + let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); + let mut pixels = Pixels::new( + meowgb_core::gameboy::ppu::FB_WIDTH, + meowgb_core::gameboy::ppu::FB_HEIGHT, + surface_texture, + ) + .unwrap(); + + #[cfg(feature = "debugger")] + let mut framework = Framework::new( + &event_loop, + window_size.width, + window_size.height, + scale_factor, + &pixels, + &gameboy.read().unwrap(), + tx.clone(), + ); let mut redraw_happened = true; let mut fb: Option> = None; @@ -87,19 +91,34 @@ pub fn run_window( return; } - if input.key_pressed(config.bindings.pause) { - tx.send(EmulatorWindowEvent::PauseToggle).unwrap(); - } - + // if input.key_pressed(config.bindings.pause) { + // tx.send(EmulatorWindowEvent::PauseToggle).unwrap(); + // } + #[cfg(feature = "debugger")] if let Some(debug_menu) = config.bindings.debug_menu { if input.key_pressed(debug_menu) { - framework.gui.window_open = !framework.gui.window_open; + if !framework.gui.state.any_open() { + if let Some(old_state) = framework.gui.state_restore.take() { + framework.gui.state = old_state; + } + framework.gui.state.window_open = true; + } else { + framework.gui.state_restore = Some(framework.gui.state); + framework.gui.state.close_all(); + } + redraw_happened = true; } } if input.key_pressed(config.bindings.pause) {} + #[cfg(feature = "debugger")] if let Some(scale_factor) = input.scale_factor() { framework.scale_factor(scale_factor); + redraw_happened = true; + } + #[cfg(not(feature = "debugger"))] + { + redraw_happened |= input.scale_factor().is_some(); } define_keypress!(input, config, keymap, tx, a, AToggle); @@ -113,8 +132,9 @@ pub fn run_window( } match event { + #[cfg(feature = "debugger")] Event::WindowEvent { event, .. } => { - framework.handle_event(&event); + redraw_happened |= framework.handle_event(&event); } Event::RedrawRequested(_) => { let frame = pixels.frame_mut(); @@ -130,12 +150,14 @@ pub fn run_window( } } + #[cfg(feature = "debugger")] framework.prepare(&window, &gameboy.read().unwrap()); let render_result = pixels.render_with(|encoder, render_target, context| { // Render the world texture context.scaling_renderer.render(encoder, render_target); + #[cfg(feature = "debugger")] // Render egui framework.render(encoder, render_target, context); @@ -154,22 +176,23 @@ pub fn run_window( if let Some(size) = input.window_resized() { pixels.resize_surface(size.width, size.height).unwrap(); + #[cfg(feature = "debugger")] framework.resize(size.width, size.height); - window.request_redraw(); - redraw_happened = false; + redraw_happened = true; } while let Ok(event) = rx.try_recv() { match event { GameboyEvent::Framebuffer(buf) => { fb = Some(buf); - - if redraw_happened { - window.request_redraw(); - redraw_happened = false; - } + redraw_happened = true; } } } + + if redraw_happened { + window.request_redraw(); + redraw_happened = false; + } }); } diff --git a/meowgb/src/window/events.rs b/meowgb/src/window/events.rs index 7ec3c72..51530bc 100644 --- a/meowgb/src/window/events.rs +++ b/meowgb/src/window/events.rs @@ -12,10 +12,17 @@ pub enum EmulatorWindowEvent { DownToggle, LeftToggle, RightToggle, - PauseToggle, + Debug(EmulatorDebugEvent), Exit, } +#[derive(Debug, Clone, Copy)] +pub enum EmulatorDebugEvent { + Step, + Continue, + ToggleBreakpoint(u16, [bool; 3]), +} + #[derive(Debug)] pub enum GameboyEvent { Framebuffer(Vec), diff --git a/meowgb/src/window/overlay.rs b/meowgb/src/window/overlay.rs index 8356fe1..59ecbe7 100644 --- a/meowgb/src/window/overlay.rs +++ b/meowgb/src/window/overlay.rs @@ -1,11 +1,14 @@ /// Provides an [egui] based overlay for debugigng the emulator whilst it is /// running -use egui::{ClippedPrimitive, Context, TexturesDelta}; +use egui::{ClippedPrimitive, Context, Grid, TexturesDelta}; use egui_wgpu::renderer::{Renderer, ScreenDescriptor}; -use meowgb_core::gameboy::{serial::SerialWriter, Gameboy}; +use meowgb_core::gameboy::serial::SerialWriter; use pixels::{wgpu, PixelsContext}; use winit::{event_loop::EventLoopWindowTarget, window::Window}; +use super::events::{EmulatorDebugEvent, EmulatorWindowEvent}; +use crate::WrappedGameboy; + pub(crate) struct Framework { egui_ctx: Context, egui_state: egui_winit::State, @@ -16,13 +19,55 @@ pub(crate) struct Framework { pub gui: Gui, } -pub struct Gui { +#[derive(Debug, Clone, Copy)] +pub struct GuiWindowState { pub window_open: bool, pub register_window_open: bool, pub ppu_register_window_open: bool, + pub debugger_window_open: bool, + pub wram_window_open: bool, + pub oam_window_open: bool, + pub hram_window_open: bool, +} +impl GuiWindowState { + pub fn close_all(&mut self) { + self.ppu_register_window_open = false; + self.register_window_open = false; + self.window_open = false; + self.debugger_window_open = false; + self.wram_window_open = false; + self.oam_window_open = false; + self.hram_window_open = false; + } + + pub fn any_open(&self) -> bool { + self.window_open + || self.register_window_open + || self.ppu_register_window_open + || self.debugger_window_open + || self.wram_window_open + || self.oam_window_open + || self.hram_window_open + } +} + +pub struct Gui { + pub state: GuiWindowState, + pub state_restore: Option, pub registers: meowgb_core::gameboy::cpu::Registers, pub ppu_registers: meowgb_core::gameboy::ppu::PpuRegisters, + pub wram: [u8; 0x2000], + pub hram: [u8; 0xAF], + // pub vram: [u8; 0x2000], + pub oam: [u8; 0xA0], + pub bp_string: String, + pub bp_read_checkbox: bool, + pub bp_write_checkbox: bool, + pub bp_execute_checkbox: bool, + pub is_debugging: bool, + pub breakpoints: [[bool; 3]; 0x10000], + pub sender: std::sync::mpsc::Sender, } impl Framework { @@ -32,7 +77,8 @@ impl Framework { height: u32, scale_factor: f32, pixels: &pixels::Pixels, - gameboy: &Gameboy, + gameboy: &WrappedGameboy, + sender: std::sync::mpsc::Sender, ) -> Self { let max_texture_size = pixels.device().limits().max_texture_dimension_2d as usize; @@ -44,7 +90,7 @@ impl Framework { ScreenDescriptor { size_in_pixels: [width, height], pixels_per_point: scale_factor }; let renderer = Renderer::new(pixels.device(), pixels.render_texture_format(), None, 1); let textures = TexturesDelta::default(); - let gui = Gui::new(gameboy); + let gui = Gui::new(gameboy, sender); Self { egui_ctx, @@ -57,8 +103,8 @@ impl Framework { } } - pub(crate) fn handle_event(&mut self, event: &winit::event::WindowEvent) { - let _ = self.egui_state.on_event(&self.egui_ctx, event); + pub(crate) fn handle_event(&mut self, event: &winit::event::WindowEvent) -> bool { + self.egui_state.on_event(&self.egui_ctx, event).repaint } pub(crate) fn resize(&mut self, width: u32, height: u32) { @@ -71,9 +117,13 @@ impl Framework { self.screen_descriptor.pixels_per_point = scale_factor as f32; } - pub(crate) fn prepare(&mut self, window: &Window, gameboy: &Gameboy) { - self.gui.registers = gameboy.registers; - self.gui.ppu_registers = gameboy.ppu.registers; + pub(crate) fn prepare(&mut self, window: &Window, gameboy: &WrappedGameboy) { + self.gui.registers = gameboy.gameboy.registers; + self.gui.ppu_registers = gameboy.gameboy.ppu.registers; + self.gui.is_debugging = gameboy.debugging; + self.gui.oam = gameboy.gameboy.ppu.oam; + self.gui.hram = gameboy.gameboy.memory.hram; + self.gui.wram = gameboy.gameboy.memory.wram; // Run the egui frame and create all paint jobs to prepare for rendering. let raw_input = self.egui_state.take_egui_input(window); @@ -126,62 +176,194 @@ impl Framework { } impl Gui { - fn new(gameboy: &Gameboy) -> Self { + fn new( + gameboy: &WrappedGameboy, + sender: std::sync::mpsc::Sender, + ) -> Self { Self { - window_open: false, - register_window_open: false, - ppu_register_window_open: false, - registers: gameboy.registers, - ppu_registers: gameboy.ppu.registers, + state: GuiWindowState { + window_open: gameboy.debugging, + register_window_open: false, + ppu_register_window_open: false, + debugger_window_open: gameboy.debugging, + wram_window_open: false, + oam_window_open: false, + hram_window_open: false, + }, + state_restore: None, + registers: gameboy.gameboy.registers, + ppu_registers: gameboy.gameboy.ppu.registers, + bp_string: String::with_capacity(16), + breakpoints: [[false, false, false]; 0x10000], + bp_read_checkbox: false, + bp_write_checkbox: false, + bp_execute_checkbox: false, + sender, + is_debugging: gameboy.debugging, + wram: gameboy.gameboy.memory.wram, + hram: gameboy.gameboy.memory.hram, + oam: gameboy.gameboy.ppu.oam, } } fn ui(&mut self, ctx: &Context) { - egui::Window::new("MeowGB Debugger").open(&mut self.window_open).show(ctx, |ui| { + egui::Window::new("MeowGB Debugger").open(&mut self.state.window_open).show(ctx, |ui| { + if ui.button("Toggle Debugger Window").clicked() { + self.state.debugger_window_open = !self.state.debugger_window_open; + } + if ui.button("Toggle Register Window").clicked() { - self.register_window_open = !self.register_window_open; + self.state.register_window_open = !self.state.register_window_open; } if ui.button("Toggle PPU Window").clicked() { - self.ppu_register_window_open = !self.ppu_register_window_open; - } - - if ui.button("Toggle OAM Window").clicked() { - self.ppu_register_window_open = !self.ppu_register_window_open; + self.state.ppu_register_window_open = !self.state.ppu_register_window_open; } if ui.button("Toggle BG Tiles Window").clicked() { - self.ppu_register_window_open = !self.ppu_register_window_open; + self.state.ppu_register_window_open = !self.state.ppu_register_window_open; + } + + if ui.button("Toggle WRAM Window").clicked() { + self.state.wram_window_open = !self.state.wram_window_open; + } + + if ui.button("Toggle HRAM Window").clicked() { + self.state.hram_window_open = !self.state.hram_window_open; + } + + if ui.button("Toggle OAM Window").clicked() { + self.state.oam_window_open = !self.state.oam_window_open; } }); - egui::Window::new("Register State").open(&mut self.register_window_open).show(ctx, |ui| { - ui.label(format!("AF: {:04X}", self.registers.get_af())); - ui.label(format!("BC: {:04X}", self.registers.get_bc())); - ui.label(format!("DE: {:04X}", self.registers.get_de())); - ui.label(format!("HL: {:04X}", self.registers.get_hl())); - ui.label(format!("SP: {:04X}", self.registers.get_sp())); - ui.label(format!("PC: {:04X}", self.registers.pc)); + egui::Window::new("Register State").open(&mut self.state.register_window_open).show( + ctx, + |ui| { + ui.label(format!("AF: {:04X}", self.registers.get_af())); + ui.label(format!("BC: {:04X}", self.registers.get_bc())); + ui.label(format!("DE: {:04X}", self.registers.get_de())); + ui.label(format!("HL: {:04X}", self.registers.get_hl())); + ui.label(format!("SP: {:04X}", self.registers.get_sp())); + ui.label(format!("PC: {:04X}", self.registers.pc)); + }, + ); + + egui::Window::new("Debugger").open(&mut self.state.debugger_window_open).show(ctx, |ui| { + if ui.button("Step").clicked() { + let _ = self.sender.send(EmulatorWindowEvent::Debug(EmulatorDebugEvent::Step)); + } + if ui.button("Continue").clicked() { + let _ = self.sender.send(EmulatorWindowEvent::Debug(EmulatorDebugEvent::Continue)); + } + ui.label("Toggle Breakpoint"); + ui.text_edit_singleline(&mut self.bp_string); + self.bp_string.retain(|x| x.is_ascii_hexdigit()); + if let Some((fourth_index, _)) = self.bp_string.char_indices().nth(4) { + self.bp_string.truncate(fourth_index); + } + Grid::new("debugger_bp_select_grid").show(ui, |ui| { + let address = u16::from_str_radix(self.bp_string.as_str(), 16).unwrap_or_default(); + ui.label(format!("({:#X}) ", address)); + let [read, write, execute] = &mut self.breakpoints[address as usize]; + let mut changed = ui.checkbox(read, "Read").clicked(); + changed |= ui.checkbox(write, "Write").clicked(); + changed |= ui.checkbox(execute, "Execute").clicked(); + if changed { + let _ = self.sender.send(EmulatorWindowEvent::Debug( + EmulatorDebugEvent::ToggleBreakpoint( + address, + self.breakpoints[address as usize], + ), + )); + } + }); + + Grid::new("debugger_bp_list_grid").show(ui, |ui| { + ui.heading("Enabled BPs"); + ui.end_row(); + for (idx, [read, write, execute]) in self.breakpoints.iter_mut().enumerate() { + if *read || *write || *execute { + ui.label(format!("{:#X}: ", idx)); + let mut changed = ui.checkbox(read, "Read").clicked(); + changed |= ui.checkbox(write, "Write").clicked(); + changed |= ui.checkbox(execute, "Execute").clicked(); + if changed { + let _ = self.sender.send(EmulatorWindowEvent::Debug( + EmulatorDebugEvent::ToggleBreakpoint( + idx as u16, + [*read, *write, *execute], + ), + )); + } + ui.end_row(); + } + } + }); }); - egui::Window::new("PPU State").open(&mut self.ppu_register_window_open).show(ctx, |ui| { - ui.label(format!("Mode: {:?}", self.ppu_registers.mode)); - ui.label(format!("LCDC: {:02X}", self.ppu_registers.lcdc)); - ui.label(format!( - "Stat: {:02X}", - (1 << 7) - | self.ppu_registers.stat_flags.flag_bits() - | ((match (self.ppu_registers.lcdc >> 7) == 1 { - true => self.ppu_registers.ly == self.ppu_registers.lyc, - false => self.ppu_registers.ly_lyc, - } as u8) << 2) | self.ppu_registers.mode.mode_flag() - )); - ui.label(format!("SCY: {:02X}", self.ppu_registers.scy)); - ui.label(format!("SCX: {:02X}", self.ppu_registers.scx)); - ui.label(format!("LY: {:02X}", self.ppu_registers.ly)); - ui.label(format!("LYC: {:02X}", self.ppu_registers.lyc)); - ui.label(format!("WY: {:02X}", self.ppu_registers.wy)); - ui.label(format!("WX: {:02X}", self.ppu_registers.wx)); + egui::Window::new("PPU State").open(&mut self.state.ppu_register_window_open).show( + ctx, + |ui| { + ui.label(format!("Mode: {:?}", self.ppu_registers.mode)); + ui.label(format!("LCDC: {:02X}", self.ppu_registers.lcdc)); + ui.label(format!( + "Stat: {:02X}", + (1 << 7) + | self.ppu_registers.stat_flags.flag_bits() + | ((match (self.ppu_registers.lcdc >> 7) == 1 { + true => self.ppu_registers.ly == self.ppu_registers.lyc, + false => self.ppu_registers.ly_lyc, + } as u8) << 2) | self.ppu_registers.mode.mode_flag() + )); + ui.label(format!("SCY: {:02X}", self.ppu_registers.scy)); + ui.label(format!("SCX: {:02X}", self.ppu_registers.scx)); + ui.label(format!("LY: {:02X}", self.ppu_registers.ly)); + ui.label(format!("LYC: {:02X}", self.ppu_registers.lyc)); + ui.label(format!("WY: {:02X}", self.ppu_registers.wy)); + ui.label(format!("WX: {:02X}", self.ppu_registers.wx)); + }, + ); + + egui::Window::new("WRAM").vscroll(true).open(&mut self.state.wram_window_open).show(ctx, |ui| { + egui::Grid::new("memory_ov_wram").show(ui, |ui| { + ui.monospace(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"); + for mem_row_idx in 0xC00..(0xE00) { + let row_base = (mem_row_idx * 0x10) - 0xC000; + ui.end_row(); + ui.monospace(format!("{:X}: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", mem_row_idx * 0x10, self.wram[row_base + 0], self.wram[row_base + 1], self.wram[row_base + 2], self.wram[row_base + 3], self.wram[row_base + 4], self.wram[row_base + 5], self.wram[row_base + 6], self.wram[row_base + 7], self.wram[row_base + 8], self.wram[row_base + 9], self.wram[row_base + 10], self.wram[row_base + 11], self.wram[row_base + 12], self.wram[row_base + 13], self.wram[row_base + 14], self.wram[row_base + 15])); + } + }); + }); + + egui::Window::new("HRAM").vscroll(true).open(&mut self.state.hram_window_open).show(ctx, |ui| { + egui::Grid::new("memory_ov_hram").show(ui, |ui| { + ui.label("ROW: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"); + for mem_row_idx in 0x0..0xA { + let row_base = mem_row_idx * 0x10; + ui.end_row(); + ui.monospace(format!("{:X}: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", 0xFF80 + row_base, self.hram[row_base + 0], self.hram[row_base + 1], self.hram[row_base + 2], self.hram[row_base + 3], self.hram[row_base + 4], self.hram[row_base + 5], self.hram[row_base + 6], self.hram[row_base + 7], self.hram[row_base + 8], self.hram[row_base + 9], self.hram[row_base + 10], self.hram[row_base + 11], self.hram[row_base + 12], self.hram[row_base + 13], self.hram[row_base + 14], self.hram[row_base + 15])); + } + + let row_base = 0xA0; + ui.end_row(); + ui.monospace(format!("FFF0: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} ??", self.hram[row_base + 0], self.hram[row_base + 1], self.hram[row_base + 2], self.hram[row_base + 3], self.hram[row_base + 4], self.hram[row_base + 5], self.hram[row_base + 6], self.hram[row_base + 7], self.hram[row_base + 8], self.hram[row_base + 9], self.hram[row_base + 10], self.hram[row_base + 11], self.hram[row_base + 12], self.hram[row_base + 13], self.hram[row_base + 14])); + }); + }); + + egui::Window::new("OAM").vscroll(true).open(&mut self.state.oam_window_open).show(ctx, |ui| { + egui::Grid::new("memory_ov_oam").show(ui, |ui| { + ui.label("ROW: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"); + for mem_row_idx in 0xC00..(0xC0A) { + let row_base = (mem_row_idx * 0x10) - 0xC000; + ui.end_row(); + ui.label(format!("{:X}: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", mem_row_idx * 0x10, self.hram[row_base + 0], self.hram[row_base + 1], self.hram[row_base + 2], self.hram[row_base + 3], self.hram[row_base + 4], self.hram[row_base + 5], self.hram[row_base + 6], self.hram[row_base + 7], self.hram[row_base + 8], self.hram[row_base + 9], self.hram[row_base + 10], self.hram[row_base + 11], self.hram[row_base + 12], self.hram[row_base + 13], self.hram[row_base + 14], self.hram[row_base + 15])); + } + + let row_base = 0xA0; + ui.end_row(); + ui.label(format!("CF00: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} ??", self.hram[row_base + 0], self.hram[row_base + 1], self.hram[row_base + 2], self.hram[row_base + 3], self.hram[row_base + 4], self.hram[row_base + 5], self.hram[row_base + 6], self.hram[row_base + 7], self.hram[row_base + 8], self.hram[row_base + 9], self.hram[row_base + 10], self.hram[row_base + 11], self.hram[row_base + 12], self.hram[row_base + 13], self.hram[row_base + 14])); + }); }); } } diff --git a/run-test-roms.sh b/run-test-roms.sh index e315aca..7626947 100755 --- a/run-test-roms.sh +++ b/run-test-roms.sh @@ -77,6 +77,16 @@ else echo "Failed: $res" fi +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/di_timing-GS.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/di_timing-GS.gb test -m 100000000 -s meowgb-tests/expected_output/di_timing-GS.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + echo "Running test ROM ./test-roms/mooneye-test-suite/roms/div_timing.gb" TEST_TOTAL=$((TEST_TOTAL + 1)) @@ -127,6 +137,36 @@ else echo "Failed: $res" fi +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/halt_ime0_nointr_timing.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/halt_ime0_nointr_timing.gb test -m 100000000 -s meowgb-tests/expected_output/halt_ime0_nointr_timing.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/halt_ime1_timing.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/halt_ime1_timing.gb test -m 100000000 -s meowgb-tests/expected_output/halt_ime1_timing.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/halt_ime1_timing2-GS.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/halt_ime1_timing2-GS.gb test -m 100000000 -s meowgb-tests/expected_output/halt_ime1_timing2-GS.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + echo "Running test ROM ./test-roms/mooneye-test-suite/roms/intr_1_2_timing-GS.gb" TEST_TOTAL=$((TEST_TOTAL + 1)) @@ -187,6 +227,26 @@ else echo "Failed: $res" fi +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/pop_timing.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/pop_timing.gb test -m 100000000 -s meowgb-tests/expected_output/pop_timing.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/push_timing.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/push_timing.gb test -m 100000000 -s meowgb-tests/expected_output/push_timing.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + echo "Running test ROM ./test-roms/mooneye-test-suite/roms/rapid_di_ei.gb" TEST_TOTAL=$((TEST_TOTAL + 1)) @@ -227,6 +287,16 @@ else echo "Failed: $res" fi +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/rst_timing.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/rst_timing.gb test -m 100000000 -s meowgb-tests/expected_output/rst_timing.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + echo "Running test ROM ./test-roms/mooneye-test-suite/roms/stat_irq_blocking.gb" TEST_TOTAL=$((TEST_TOTAL + 1)) diff --git a/test-roms/mooneye-test-suite/roms/di_timing-GS.gb b/test-roms/mooneye-test-suite/roms/di_timing-GS.gb new file mode 100644 index 0000000..9c3dfd5 Binary files /dev/null and b/test-roms/mooneye-test-suite/roms/di_timing-GS.gb differ diff --git a/test-roms/mooneye-test-suite/roms/halt_ime0_nointr_timing.gb b/test-roms/mooneye-test-suite/roms/halt_ime0_nointr_timing.gb new file mode 100644 index 0000000..159dd1c Binary files /dev/null and b/test-roms/mooneye-test-suite/roms/halt_ime0_nointr_timing.gb differ diff --git a/test-roms/mooneye-test-suite/roms/halt_ime1_timing.gb b/test-roms/mooneye-test-suite/roms/halt_ime1_timing.gb new file mode 100644 index 0000000..680e2dc Binary files /dev/null and b/test-roms/mooneye-test-suite/roms/halt_ime1_timing.gb differ diff --git a/test-roms/mooneye-test-suite/roms/halt_ime1_timing2-GS.gb b/test-roms/mooneye-test-suite/roms/halt_ime1_timing2-GS.gb new file mode 100644 index 0000000..810c2cf Binary files /dev/null and b/test-roms/mooneye-test-suite/roms/halt_ime1_timing2-GS.gb differ diff --git a/test-roms/mooneye-test-suite/roms/pop_timing.gb b/test-roms/mooneye-test-suite/roms/pop_timing.gb new file mode 100644 index 0000000..499a33d Binary files /dev/null and b/test-roms/mooneye-test-suite/roms/pop_timing.gb differ diff --git a/test-roms/mooneye-test-suite/roms/push_timing.gb b/test-roms/mooneye-test-suite/roms/push_timing.gb new file mode 100644 index 0000000..5ceb270 Binary files /dev/null and b/test-roms/mooneye-test-suite/roms/push_timing.gb differ diff --git a/test-roms/mooneye-test-suite/roms/rst_timing.gb b/test-roms/mooneye-test-suite/roms/rst_timing.gb new file mode 100644 index 0000000..c6a5cb6 Binary files /dev/null and b/test-roms/mooneye-test-suite/roms/rst_timing.gb differ diff --git a/tests.md b/tests.md index ade1e25..7432d2e 100644 --- a/tests.md +++ b/tests.md @@ -12,21 +12,28 @@ * boot_hwio-dmgABCmgb.gb - [ROM](./test-roms/mooneye-test-suite/roms/boot_hwio-dmgABCmgb.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/boot_hwio-dmgABCmgb.bin) * boot_regs-dmgABC.gb - [ROM](./test-roms/mooneye-test-suite/roms/boot_regs-dmgABC.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/boot_regs-dmgABC.bin) * daa.gb - [ROM](./test-roms/mooneye-test-suite/roms/daa.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/daa.bin) +* di_timing-GS.gb - [ROM](./test-roms/mooneye-test-suite/roms/di_timing-GS.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/di_timing-GS.bin) * div_timing.gb - [ROM](./test-roms/mooneye-test-suite/roms/div_timing.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/div_timing.bin) * div_write.gb - [ROM](./test-roms/mooneye-test-suite/roms/div_write.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/div_write.bin) * ei_sequence.gb - [ROM](./test-roms/mooneye-test-suite/roms/ei_sequence.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/ei_sequence.bin) * ei_timing.gb - [ROM](./test-roms/mooneye-test-suite/roms/ei_timing.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/ei_timing.bin) * halt_ime0_ei.gb - [ROM](./test-roms/mooneye-test-suite/roms/halt_ime0_ei.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/halt_ime0_ei.bin) +* halt_ime0_nointr_timing.gb - [ROM](./test-roms/mooneye-test-suite/roms/halt_ime0_nointr_timing.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/halt_ime0_nointr_timing.bin) +* halt_ime1_timing.gb - [ROM](./test-roms/mooneye-test-suite/roms/halt_ime1_timing.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/halt_ime1_timing.bin) +* halt_ime1_timing2-GS.gb - [ROM](./test-roms/mooneye-test-suite/roms/halt_ime1_timing2-GS.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/halt_ime1_timing2-GS.bin) * intr_1_2_timing-GS.gb - [ROM](./test-roms/mooneye-test-suite/roms/intr_1_2_timing-GS.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/intr_1_2_timing-GS.bin) * intr_2_0_timing.gb - [ROM](./test-roms/mooneye-test-suite/roms/intr_2_0_timing.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/intr_2_0_timing.bin) * mem_oam.gb - [ROM](./test-roms/mooneye-test-suite/roms/mem_oam.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/mem_oam.bin) * oam_dma_restart.gb - [ROM](./test-roms/mooneye-test-suite/roms/oam_dma_restart.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/oam_dma_restart.bin) * oam_dma_start.gb - [ROM](./test-roms/mooneye-test-suite/roms/oam_dma_start.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/oam_dma_start.bin) * oam_dma_timing.gb - [ROM](./test-roms/mooneye-test-suite/roms/oam_dma_timing.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/oam_dma_timing.bin) +* pop_timing.gb - [ROM](./test-roms/mooneye-test-suite/roms/pop_timing.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/pop_timing.bin) +* push_timing.gb - [ROM](./test-roms/mooneye-test-suite/roms/push_timing.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/push_timing.bin) * rapid_di_ei.gb - [ROM](./test-roms/mooneye-test-suite/roms/rapid_di_ei.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/rapid_di_ei.bin) * rapid_toggle.gb - [ROM](./test-roms/mooneye-test-suite/roms/rapid_toggle.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/rapid_toggle.bin) * reg_f.gb - [ROM](./test-roms/mooneye-test-suite/roms/reg_f.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/reg_f.bin) * reg_read.gb - [ROM](./test-roms/mooneye-test-suite/roms/reg_read.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/reg_read.bin) +* rst_timing.gb - [ROM](./test-roms/mooneye-test-suite/roms/rst_timing.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/rst_timing.bin) * stat_irq_blocking.gb - [ROM](./test-roms/mooneye-test-suite/roms/stat_irq_blocking.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/stat_irq_blocking.bin) * stat_lyc_onoff.gb - [ROM](./test-roms/mooneye-test-suite/roms/stat_lyc_onoff.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/stat_lyc_onoff.bin) * tim00.gb - [ROM](./test-roms/mooneye-test-suite/roms/tim00.gb) - [Expected Serial Output](./meowgb-tests/expected_output/MBC1/tim00.bin)