diff --git a/src/app.rs b/src/app.rs index 3fa2fb4..2ceaf4f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -23,7 +23,7 @@ use crate::{ vram::VramProcessor, window::{ AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GameWindow, - GdbServerWindow, InputWindow, ObjectWindow, WorldWindow, + GdbServerWindow, InputWindow, ObjectWindow, RegisterWindow, WorldWindow, }, }; @@ -233,6 +233,10 @@ impl ApplicationHandler for Application { let world = FrameBufferWindow::new(sim_id, &self.memory, &mut self.vram); self.open(event_loop, Box::new(world)); } + UserEvent::OpenRegisters(sim_id) => { + let registers = RegisterWindow::new(sim_id, &self.memory); + self.open(event_loop, Box::new(registers)); + } UserEvent::OpenDebugger(sim_id) => { let debugger = GdbServerWindow::new(sim_id, self.client.clone(), self.proxy.clone()); @@ -496,6 +500,7 @@ pub enum UserEvent { OpenObjects(SimId), OpenWorlds(SimId), OpenFrameBuffers(SimId), + OpenRegisters(SimId), OpenDebugger(SimId), OpenInput, OpenPlayer2, diff --git a/src/window.rs b/src/window.rs index 9353a64..2216004 100644 --- a/src/window.rs +++ b/src/window.rs @@ -3,7 +3,9 @@ use egui::{Context, ViewportBuilder, ViewportId}; pub use game::GameWindow; pub use gdb::GdbServerWindow; pub use input::InputWindow; -pub use vram::{BgMapWindow, CharacterDataWindow, FrameBufferWindow, ObjectWindow, WorldWindow}; +pub use vram::{ + BgMapWindow, CharacterDataWindow, FrameBufferWindow, ObjectWindow, RegisterWindow, WorldWindow, +}; use winit::event::KeyEvent; use crate::emulator::SimId; diff --git a/src/window/game.rs b/src/window/game.rs index e1072a9..8d91146 100644 --- a/src/window/game.rs +++ b/src/window/game.rs @@ -162,6 +162,12 @@ impl GameWindow { .unwrap(); ui.close_menu(); } + if ui.button("Registers").clicked() { + self.proxy + .send_event(UserEvent::OpenRegisters(self.sim_id)) + .unwrap(); + ui.close_menu(); + } }); ui.menu_button("Help", |ui| { if ui.button("About").clicked() { diff --git a/src/window/vram.rs b/src/window/vram.rs index 4899486..595ba88 100644 --- a/src/window/vram.rs +++ b/src/window/vram.rs @@ -2,6 +2,7 @@ mod bgmap; mod chardata; mod framebuffer; mod object; +mod registers; mod utils; mod world; @@ -9,4 +10,5 @@ pub use bgmap::*; pub use chardata::*; pub use framebuffer::*; pub use object::*; +pub use registers::*; pub use world::*; diff --git a/src/window/vram/registers.rs b/src/window/vram/registers.rs new file mode 100644 index 0000000..e001dc4 --- /dev/null +++ b/src/window/vram/registers.rs @@ -0,0 +1,195 @@ +use std::sync::Arc; + +use egui::{ + Align, CentralPanel, Context, Label, Layout, ScrollArea, TextEdit, Ui, ViewportBuilder, + ViewportId, +}; +use egui_extras::{Column, Size, StripBuilder, TableBuilder}; + +use crate::{ + emulator::SimId, + memory::{MemoryClient, MemoryView}, + window::{utils::UiExt, AppWindow}, +}; + +pub struct RegisterWindow { + sim_id: SimId, + memory: Arc, + registers: MemoryView, +} + +impl RegisterWindow { + pub fn new(sim_id: SimId, memory: &Arc) -> Self { + Self { + sim_id, + memory: memory.clone(), + registers: memory.watch(sim_id, 0x0005f800, 0x70), + } + } + + fn show_interrupts(&mut self, ui: &mut Ui) { + let row_height = ui.spacing().interact_size.y; + let registers = self.registers.borrow(); + let [mut raw_intpnd, mut raw_intenb] = registers.read::<[u16; 2]>(0); + let mut intenb = InterruptReg::parse(raw_intenb); + let mut intpnd = InterruptReg::parse(raw_intpnd); + ui.section("Interrupt", |ui| { + ui.vertical(|ui| { + TableBuilder::new(ui) + .id_salt("raw_values") + .column(Column::auto()) + .column(Column::remainder()) + .cell_layout(Layout::left_to_right(Align::Max)) + .body(|mut body| { + body.row(row_height, |mut row| { + row.col(|ui| { + ui.label("INTENB"); + }); + row.col(|ui| { + let mut text = format!("{raw_intenb:04x}"); + ui.add_enabled( + false, + TextEdit::singleline(&mut text).horizontal_align(Align::Max), + ); + }); + }); + body.row(row_height, |mut row| { + row.col(|ui| { + ui.label("INTPND"); + }); + row.col(|ui| { + let mut text = format!("{raw_intpnd:04x}"); + ui.add_enabled( + false, + TextEdit::singleline(&mut text).horizontal_align(Align::Max), + ); + }); + }); + }); + ui.add_space(8.0); + TableBuilder::new(ui) + .id_salt("flags") + .column(Column::auto()) + .columns(Column::remainder(), 2) + .cell_layout(Layout::left_to_right(Align::Center).with_main_align(Align::RIGHT)) + .body(|mut body| { + body.row(row_height, |mut row| { + row.col(|_ui| {}); + row.col(|ui| { + ui.add_sized([ui.available_width(), 0.0], Label::new("ENB")); + }); + row.col(|ui| { + ui.add_sized([ui.available_width(), 0.0], Label::new("PND")); + }); + }); + let mut add_row = |label: &str, enb: &mut bool, pnd: &mut bool| { + body.row(row_height, |mut row| { + row.col(|ui| { + ui.label(label); + }); + row.col(|ui| { + let space = + (ui.available_width() - ui.spacing().icon_width) / 2.0; + ui.add_space(space); + ui.checkbox(enb, ""); + }); + row.col(|ui| { + let space = + (ui.available_width() - ui.spacing().icon_width) / 2.0; + ui.add_space(space); + ui.checkbox(pnd, ""); + }); + }); + }; + add_row("TIMEERR", &mut intenb.timeerr, &mut intpnd.timeerr); + add_row("XPEND", &mut intenb.xpend, &mut intpnd.xpend); + add_row("SBHIT", &mut intenb.sbhit, &mut intpnd.sbhit); + add_row("FRAMESTART", &mut intenb.framestart, &mut intpnd.framestart); + add_row("GAMESTART", &mut intenb.gamestart, &mut intpnd.gamestart); + add_row("RFBEND", &mut intenb.rfbend, &mut intpnd.rfbend); + add_row("LFBEND", &mut intenb.lfbend, &mut intpnd.lfbend); + add_row("SCANERR", &mut intenb.scanerr, &mut intpnd.scanerr); + }); + }); + }); + if intpnd.update(&mut raw_intpnd) { + self.memory.write(self.sim_id, 0x0005f800, &raw_intpnd); + } + if intenb.update(&mut raw_intenb) { + self.memory.write(self.sim_id, 0x0005f802, &raw_intenb); + } + } +} + +impl AppWindow for RegisterWindow { + fn viewport_id(&self) -> ViewportId { + ViewportId::from_hash_of(format!("registers-{}", self.sim_id)) + } + + fn sim_id(&self) -> SimId { + self.sim_id + } + + fn initial_viewport(&self) -> ViewportBuilder { + ViewportBuilder::default() + .with_title(format!("Registers ({})", self.sim_id)) + .with_inner_size((640.0, 480.0)) + } + + fn show(&mut self, ctx: &Context) { + CentralPanel::default().show(ctx, |ui| { + ScrollArea::vertical().show(ui, |ui| { + ui.horizontal_top(|ui| { + StripBuilder::new(ui) + .sizes(Size::remainder(), 4) + .horizontal(|mut strip| { + strip.cell(|ui| { + self.show_interrupts(ui); + }) + }); + }); + }); + }); + } +} + +struct InterruptReg { + timeerr: bool, + xpend: bool, + sbhit: bool, + framestart: bool, + gamestart: bool, + rfbend: bool, + lfbend: bool, + scanerr: bool, +} + +impl InterruptReg { + fn parse(value: u16) -> Self { + Self { + timeerr: value & 0x8000 != 0, + xpend: value & 0x4000 != 0, + sbhit: value & 0x2000 != 0, + framestart: value & 0x0010 != 0, + gamestart: value & 0x0008 != 0, + rfbend: value & 0x0004 != 0, + lfbend: value & 0x0002 != 0, + scanerr: value & 0x0001 != 0, + } + } + + fn update(&self, value: &mut u16) -> bool { + let new_value = (*value & 0x1fe0) + | if self.timeerr { 0x8000 } else { 0x0000 } + | if self.xpend { 0x4000 } else { 0x0000 } + | if self.sbhit { 0x2000 } else { 0x0000 } + | if self.framestart { 0x0010 } else { 0x0000 } + | if self.gamestart { 0x0008 } else { 0x0000 } + | if self.rfbend { 0x0004 } else { 0x0000 } + | if self.lfbend { 0x0002 } else { 0x0000 } + | if self.scanerr { 0x0001 } else { 0x0000 }; + let changed = *value != new_value; + *value = new_value; + changed + } +}