diff --git a/snes-core/src/cpu/instructions.rs b/snes-core/src/cpu/instructions.rs index c102ff0..295a6a9 100644 --- a/snes-core/src/cpu/instructions.rs +++ b/snes-core/src/cpu/instructions.rs @@ -1098,7 +1098,7 @@ impl CPU { self.increment_cycles_test(addressing_mode); } - pub fn run(&mut self, bus: &mut Bus) { + pub fn tick(&mut self, bus: &mut Bus) { let opcode = bus.read(self.registers.get_pc_address()); self.execute_opcode(opcode, bus); } diff --git a/snes-core/src/cpu/registers.rs b/snes-core/src/cpu/registers.rs index d379d4f..d512d6a 100644 --- a/snes-core/src/cpu/registers.rs +++ b/snes-core/src/cpu/registers.rs @@ -16,7 +16,7 @@ pub struct Registers { impl Registers { pub fn new() -> Self { Self { - sp: 0, + sp: 0x01FC, x: 0, y: 0, a: 0, diff --git a/snes-core/src/emulator.rs b/snes-core/src/emulator.rs index e4f9f45..73f6f23 100644 --- a/snes-core/src/emulator.rs +++ b/snes-core/src/emulator.rs @@ -20,8 +20,8 @@ impl Emulator { } } - pub fn run(&mut self) { - self.cpu.run(&mut self.bus); + pub fn tick(&mut self) { + self.cpu.tick(&mut self.bus); self.bus.ppu.tick(self.cpu.cycles); self.cpu.cycles = 0; diff --git a/snes-frontend/Cargo.toml b/snes-frontend/Cargo.toml index c30145b..2cc42f5 100644 --- a/snes-frontend/Cargo.toml +++ b/snes-frontend/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] snes-core = { path = "../snes-core" } +regex = "1.8.1" winit = "0.26.1" imgui = "0.8.2" wgpu = "0.12.0" diff --git a/snes-frontend/src/main.rs b/snes-frontend/src/main.rs index c619739..1c7483a 100644 --- a/snes-frontend/src/main.rs +++ b/snes-frontend/src/main.rs @@ -18,6 +18,9 @@ extern crate snes_core; use snes_core::emulator::Emulator; use snes_frontend::ppu as ppu_render; + +// TODO: refactor this please + fn main() { // Windowing state let mut state = State::new(); @@ -78,7 +81,7 @@ fn main() { ); imgui.set_ini_filename(None); - let font_size = (12.0 * hidpi_factor) as f32; + let font_size = (14.0 * hidpi_factor) as f32; imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; imgui.fonts().add_font(&[FontSource::DefaultFontData { @@ -219,7 +222,7 @@ fn main() { { match emulator.bus.rom.load(&String::from(path.to_str().unwrap())) { Ok(_) => { - state.emulation.is_paused = false; + // state.emulation.is_paused = false; }, Err(err) => { state.error_message.show = true; @@ -247,11 +250,27 @@ fn main() { .size([300.0, 100.0], Condition::FirstUseEver) .opened(&mut state.debug_options.show_debug_window) .build(&ui, || { + ui.text("Controls:"); + let pause_text = if state.emulation.is_paused {"Resume"} else {"Pause"}; + if ui.button(pause_text) { + state.emulation.is_paused = !state.emulation.is_paused; + } + ui.disabled(!state.emulation.is_paused, || { + if ui.button("Tick") { + emulator.tick(); + } + }); + ui.separator(); + ui.checkbox( "Enable debugging", &mut state.debug_options.is_enabled, ); ui.separator(); + ui.checkbox( + "Show Memory Map", + &mut state.debug_options.memory_map.is_enabled , + ); ui.checkbox( "Show PPU debugging options", &mut state.ppudebug.is_enabled, @@ -295,16 +314,75 @@ fn main() { window .size([150.0, 200.0], Condition::FirstUseEver) .build(&ui, || { - ui.text(format!("SP: | {:#06X}", emulator.cpu.registers.sp)); - ui.text(format!("X: | {:#06X}", emulator.cpu.registers.x)); - ui.text(format!("Y: | {:#06X}", emulator.cpu.registers.y)); - ui.text(format!("A: | {:#06X}", emulator.cpu.registers.a)); - ui.text(format!("P: | {:#04X}", emulator.cpu.registers.p)); - ui.text(format!("D: | {:#06X}", emulator.cpu.registers.d)); - ui.text(format!("PBR: | {:#04X}", emulator.cpu.registers.pbr)); - ui.text(format!("DBR: | {:#04X}", emulator.cpu.registers.dbr)); - ui.text(format!("PC: | {:#06X}", emulator.cpu.registers.pc)); - ui.text(format!("EMU MODE: | {}", emulator.cpu.registers.emulation_mode)); + ui.text(format!("SP: | {:#06X}", emulator.cpu.registers.sp)); + ui.text(format!("X: | {:#06X}", emulator.cpu.registers.x)); + ui.text(format!("Y: | {:#06X}", emulator.cpu.registers.y)); + ui.text(format!("A: | {:#06X}", emulator.cpu.registers.a)); + ui.text(format!("P: | {:#04X}", emulator.cpu.registers.p)); + ui.text(format!("D: | {:#06X}", emulator.cpu.registers.d)); + ui.text(format!("PBR: | {:#04X}", emulator.cpu.registers.pbr)); + ui.text(format!("DBR: | {:#04X}", emulator.cpu.registers.dbr)); + ui.text(format!("PC: | {:#06X}", emulator.cpu.registers.pc)); + ui.text(format!("EMU MODE: | {}", emulator.cpu.registers.emulation_mode)); + }); + } + + if state.debug_options.memory_map.is_enabled { + let window = imgui::Window::new("Memory Map"); + window + .size([400.0, 400.0], Condition::FirstUseEver) + .build(&ui, || { + let page_start_input = ui.input_text( + "Page start", + &mut state.debug_options.memory_map.page_start_input + ); + page_start_input.build(); + + let page_end_input = ui.input_text( + "Page end", + &mut state.debug_options.memory_map.page_end_input + ); + page_end_input.build(); + + let address_start_input = ui.input_text( + "Address start", + &mut state.debug_options.memory_map.address_start_input + ); + address_start_input.build(); + + let address_end_input = ui.input_text( + "Address end", + &mut state.debug_options.memory_map.address_end_input, + ); + address_end_input.build(); + + if ui.button("Apply") { + state.debug_options.memory_map.set_values_from_inputs(); + } + + ui.separator(); + let page_start = state.debug_options.memory_map.page_start; + let page_end = state.debug_options.memory_map.page_end; + let address_start = state.debug_options.memory_map.address_start; + let address_end = state.debug_options.memory_map.address_end; + let mut header = String::from(" | "); + for page in (page_start..=page_end).rev() { + header = format!("{}{:02X} ", header, page); + } + ui.text(header); + let mut divider = String::from("-----|-"); + for _page in (page_start..=page_end).rev() { + divider = format!("{}---", divider); + } + ui.text(divider); + for address in (address_start..=address_end).rev() { + let mut address_row = format!("{:04X} | ", address); + for page in (page_start..=page_end).rev() { + let bus_address = ((page as u32) << 16) | (address as u32); + address_row = format!("{}{:02X} ", address_row, emulator.bus.read(bus_address)); + } + ui.text(address_row); + } }); } } @@ -314,7 +392,7 @@ fn main() { // We want to keep the emulator running // as long as we are not at the end of the frame while !emulator.is_frame_ending { - emulator.run() + emulator.tick() } // Reset the flag because otherwise the emulator will never continue running emulator.is_frame_ending = false diff --git a/snes-frontend/src/state.rs b/snes-frontend/src/state.rs index e83abe8..a637a9e 100644 --- a/snes-frontend/src/state.rs +++ b/snes-frontend/src/state.rs @@ -4,14 +4,86 @@ use snes_core::ppu::registers::{ MAX_BG_WIDTH, MAX_BG_HEIGHT, }; +use regex::Regex; +pub struct MemoryMap { + pub is_enabled: bool, + pub page_start: u8, + pub page_end: u8, + pub address_start: u16, + pub address_end: u16, + pub page_start_input: String, + pub page_end_input: String, + pub address_start_input: String, + pub address_end_input: String, +} + +impl MemoryMap { + pub fn new() -> Self { + Self { + is_enabled: true, + page_start: 0xF0, + page_end: 0xFF, + address_start: 0xFFF0, + address_end: 0xFFFF, + page_start_input: String::from("0xF0"), + page_end_input: String::from("0xFF"), + address_start_input: String::from("0xFFF0"), + address_end_input: String::from("0xFFFF"), + } + } + + fn validate_values(&mut self) { + if self.page_start > self.page_end { + self.page_start = self.page_end; + } + if self.page_end < self.page_start { + self.page_end = self.page_start; + } + if self.address_start > self.address_end { + self.address_start = self.address_end; + } + if self.address_end < self.address_start { + self.address_end = self.address_start; + } + } + + pub fn set_values_from_inputs(&mut self) { + let page_regex = Regex::new(r"^(0x)?[0-9a-fA-F]{2,2}$").unwrap(); + let address_regex = Regex::new(r"^(0x)?[0-9a-fA-F]{4,4}$").unwrap(); + + if !page_regex.is_match(&self.page_start_input) { + println!("Page start didn't match"); + self.page_start_input = String::from("0x00"); + } + if !page_regex.is_match(&self.page_end_input) { + println!("Page end didn't match"); + self.page_end_input = String::from("0x00"); + } + if !address_regex.is_match(&self.address_start_input) { + println!("Addr start didn't match"); + self.address_start_input = String::from("0x0000"); + } + if !address_regex.is_match(&self.address_end_input) { + println!("Addr end didn't match"); + self.address_end_input = String::from("0x0000"); + } + + self.page_start = u8::from_str_radix(&self.page_start_input.trim_start_matches("0x"), 16).unwrap(); + self.page_end = u8::from_str_radix(&self.page_end_input.trim_start_matches("0x"), 16).unwrap(); + self.address_start = u16::from_str_radix(&self.address_start_input.trim_start_matches("0x"), 16).unwrap(); + self.address_end = u16::from_str_radix(&self.address_end_input.trim_start_matches("0x"), 16).unwrap(); + self.validate_values() + } +} + pub struct DebugOptions { pub is_enabled: bool, pub show_debug_window: bool, pub show_cpu_registers: bool, pub show_spc700_registers: bool, - pub show_cpu_memory: bool, + pub memory_map: MemoryMap, } impl DebugOptions { @@ -21,7 +93,7 @@ impl DebugOptions { show_debug_window: true, show_cpu_registers: true, show_spc700_registers: true, - show_cpu_memory: true, + memory_map: MemoryMap::new(), } } } @@ -105,4 +177,4 @@ impl State { emulation: Emulation::new(), } } -} \ No newline at end of file +}