1mod m68kdebug; 2mod psgdebug; 3mod sh2debug; 4mod widgets; 5mod ym2612debug; 6mod z80debug; 7 8use crate::genesis::m68kdebug::{ 9 Genesis68kMemoryMap, M68kBreakCommand, M68kDebugWindowState, Main68kInterruptBreakpoints, 10 SegaCdSubMemoryMap, Sub68kInterruptBreakpoints, 11}; 12use crate::genesis::sh2debug::Sh2DebugWindowState; 13use crate::genesis::ym2612debug::Ym2612DebugWindowState; 14use crate::genesis::z80debug::{GenesisZ80MemoryMap, Z80BreakCommand, Z80DebugWindowState}; 15use crate::memviewer::MemoryViewerState; 16use crate::process::{DebuggerProcesses, RunTillNextResult}; 17use crate::{DebugRenderContext, DebuggerMainProcess, DebuggerRunnerProcess, memviewer}; 18use egui::scroll_area::ScrollBarVisibility; 19use egui::{ 20 CentralPanel, Color32, CornerRadius, Grid, Image, Panel, Popup, Pos2, Rect, ScrollArea, Sense, 21 Stroke, StrokeKind, Ui, UiKind, Vec2, Window, 22}; 23use egui_extras::{Column, TableBuilder}; 24use genesis_components::debug::{CopySpriteAttributesResult, CramEntry, SpriteAttributeEntry}; 25use genesis_components::vdp::{ColorModifier, DataPortLocation}; 26use genesis_config::GenesisInputs; 27use genesis_core::GenesisEmulator; 28use genesis_core::api::debug::{ 29 GenesisDebugCommand, GenesisDebugState, GenesisDebugger, GenesisDebuggerHandle, 30 GenesisMemoryArea, 31}; 32use jgenesis_common::debug::{DebugMemoryView, DebugViewWithWriteHook, Endian}; 33use jgenesis_common::frontend::{ 34 AudioOutput, Color, InputPoller, Renderer, SaveWriter, TickEffect, 35}; 36use jgenesis_common::num::GetBit; 37use jgenesis_common::sync::{SharedVarReceiver, SharedVarSender}; 38use s32x_core::WhichCpu; 39use s32x_core::api::debug::{S32XMemoryArea, Sega32XDebugState}; 40use segacd_core::api::debug::SegaCdMemoryArea; 41use sh2_emu::{CacheMode, DmaAddressMode, DmaTransferUnit, Sh2}; 42use std::collections::HashMap; 43use std::error::Error; 44use std::hash::Hash; 45use std::iter; 46 47const CRAM_WINDOW_TITLE: &str = "CRAM"; 48const VRAM_WINDOW_TITLE: &str = "VRAM"; 49const H_SCROLL_WINDOW_TITLE: &str = "H Scroll Table"; 50const SPRITE_ATTRIBUTES_WINDOW_TITLE: &str = "Sprite Attribute Table"; 51const S32X_PALETTE_WINDOW_TITLE: &str = "32X Palette RAM"; 52 53const VDP_REGISTERS_WINDOW_TITLE: &str = "VDP Registers"; 54const VDP_STATE_WINDOW_TITLE: &str = "VDP State"; 55const S32X_SYSTEM_REGISTERS_WINDOW_TITLE: &str = "32X System Registers"; 56const S32X_VDP_REGISTERS_WINDOW_TITLE: &str = "32X VDP Registers"; 57const S32X_PWM_REGISTERS_WINDOW_TITLE: &str = "32X PWM Registers"; 58 59const MASTER_SH2_REGISTERS_WINDOW_TITLE: &str = "Master SH-2 Registers"; 60const SLAVE_SH2_REGISTERS_WINDOW_TITLE: &str = "Slave SH-2 Registers"; 61 62const CRAM_ROWS: usize = 4; 63const CRAM_COLS: usize = 16; 64 65// Manually position open-by-default windows so they don't spawn over the top panel 66const CRAM_DEFAULT_POS: [f32; 2] = [16.0, 38.0]; 67const VRAM_DEFAULT_POS: [f32; 2] = [16.0, 327.0]; 68 69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 70pub(crate) enum MemoryArea { 71 Genesis(GenesisMemoryArea), 72 SegaCd(SegaCdMemoryArea), 73 Sega32X(S32XMemoryArea), 74} 75 76impl MemoryArea { 77 const ALL: &'static [Self] = &[ 78 Self::Genesis(GenesisMemoryArea::CartridgeRom), 79 Self::Genesis(GenesisMemoryArea::WorkingRam), 80 Self::Genesis(GenesisMemoryArea::AudioRam), 81 Self::Genesis(GenesisMemoryArea::Vram), 82 Self::Genesis(GenesisMemoryArea::Cram), 83 Self::Genesis(GenesisMemoryArea::Vsram), 84 Self::SegaCd(SegaCdMemoryArea::BiosRom), 85 Self::SegaCd(SegaCdMemoryArea::PrgRam), 86 Self::SegaCd(SegaCdMemoryArea::WordRam), 87 Self::SegaCd(SegaCdMemoryArea::PcmRam), 88 Self::SegaCd(SegaCdMemoryArea::CdcRam), 89 Self::Sega32X(S32XMemoryArea::Sdram), 90 Self::Sega32X(S32XMemoryArea::MasterSh2Cache), 91 Self::Sega32X(S32XMemoryArea::SlaveSh2Cache), 92 Self::Sega32X(S32XMemoryArea::FrameBuffer0), 93 Self::Sega32X(S32XMemoryArea::FrameBuffer1), 94 Self::Sega32X(S32XMemoryArea::PaletteRam), 95 ]; 96 97 fn name(self) -> &'static str { 98 match self { 99 Self::Genesis(area) => match area { 100 GenesisMemoryArea::CartridgeRom => "Cartridge ROM", 101 GenesisMemoryArea::WorkingRam => "Working RAM", 102 GenesisMemoryArea::AudioRam => "Audio RAM", 103 GenesisMemoryArea::Vram => "VRAM", 104 GenesisMemoryArea::Cram => "CRAM", 105 GenesisMemoryArea::Vsram => "VSRAM", 106 }, 107 Self::SegaCd(area) => match area { 108 SegaCdMemoryArea::BiosRom => "BIOS ROM", 109 SegaCdMemoryArea::PrgRam => "PRG RAM", 110 SegaCdMemoryArea::WordRam => "Word RAM", 111 SegaCdMemoryArea::PcmRam => "PCM Waveform RAM", 112 SegaCdMemoryArea::CdcRam => "CDC Buffer RAM", 113 }, 114 Self::Sega32X(area) => match area { 115 S32XMemoryArea::Sdram => "32X SDRAM", 116 S32XMemoryArea::MasterSh2Cache => "Master SH-2 Cache", 117 S32XMemoryArea::SlaveSh2Cache => "Slave SH-2 Cache", 118 S32XMemoryArea::FrameBuffer0 => "32X Frame Buffer 0", 119 S32XMemoryArea::FrameBuffer1 => "32X Frame Buffer 1", 120 S32XMemoryArea::PaletteRam => "32X Palette RAM", 121 }, 122 } 123 } 124 125 fn default_file_name(self) -> &'static str { 126 match self { 127 Self::Genesis(area) => match area { 128 GenesisMemoryArea::CartridgeRom => "rom.bin", 129 GenesisMemoryArea::WorkingRam => "wram.bin", 130 GenesisMemoryArea::AudioRam => "audioram.bin", 131 GenesisMemoryArea::Vram => "vram.bin", 132 GenesisMemoryArea::Cram => "cram.bin", 133 GenesisMemoryArea::Vsram => "vsram.bin", 134 }, 135 Self::SegaCd(area) => match area { 136 SegaCdMemoryArea::BiosRom => "bios.bin", 137 SegaCdMemoryArea::PrgRam => "prgram.bin", 138 SegaCdMemoryArea::WordRam => "wordram.bin", 139 SegaCdMemoryArea::PcmRam => "pcmram.bin", 140 SegaCdMemoryArea::CdcRam => "cdcram.bin", 141 }, 142 Self::Sega32X(area) => match area { 143 S32XMemoryArea::Sdram => "sdram.bin", 144 S32XMemoryArea::MasterSh2Cache => "mcache.bin", 145 S32XMemoryArea::SlaveSh2Cache => "scache.bin", 146 S32XMemoryArea::FrameBuffer0 => "fb0.bin", 147 S32XMemoryArea::FrameBuffer1 => "fb1.bin", 148 S32XMemoryArea::PaletteRam => "paletteram.bin", 149 }, 150 } 151 } 152 153 fn new_states() -> HashMap<Self, MemoryViewerState> { 154 Self::ALL 155 .iter() 156 .map(|&area| { 157 ( 158 area, 159 MemoryViewerState::new(area.name(), Endian::Big) 160 .with_default_file_name(area.default_file_name().into()) 161 .with_editable(), 162 ) 163 }) 164 .collect() 165 } 166} 167 168struct CramWindowState { 169 open: bool, 170 modifier: ColorModifier, 171 selected_color: usize, 172 popup_contains_pointer: bool, 173 entry_buffer: Box<[CramEntry; CRAM_ROWS * CRAM_COLS]>, 174 color_buffer: Box<[Color; CRAM_ROWS * CRAM_COLS]>, 175 texture: Option<egui::TextureId>, 176} 177 178impl CramWindowState { 179 fn new() -> Self { 180 Self { 181 open: true, 182 modifier: ColorModifier::None, 183 selected_color: 0, 184 popup_contains_pointer: false, 185 entry_buffer: vec![CramEntry::default(); CRAM_ROWS * CRAM_COLS] 186 .into_boxed_slice() 187 .try_into() 188 .unwrap(), 189 color_buffer: vec![Color::default(); CRAM_ROWS * CRAM_COLS] 190 .into_boxed_slice() 191 .try_into() 192 .unwrap(), 193 texture: None, 194 } 195 } 196} 197 198struct VramWindowState { 199 open: bool, 200 palette: u8, 201 buffer: Box<[Color; 2048 * 64]>, 202 texture: Option<egui::TextureId>, 203} 204 205impl VramWindowState { 206 fn new() -> Self { 207 Self { 208 open: true, 209 palette: 0, 210 buffer: vec![Color::default(); 2048 * 64].into_boxed_slice().try_into().unwrap(), 211 texture: None, 212 } 213 } 214} 215 216struct HScrollWindowState { 217 open: bool, 218 buffer: Box<[(u16, u16); 256]>, 219} 220 221impl HScrollWindowState { 222 fn new() -> Self { 223 Self { open: false, buffer: vec![(0, 0); 256].into_boxed_slice().try_into().unwrap() } 224 } 225} 226 227struct SpriteAttributesWindowState { 228 open: bool, 229 buffer: Box<[SpriteAttributeEntry; 80]>, 230 adjust_coordinates: bool, 231} 232 233impl SpriteAttributesWindowState { 234 fn new() -> Self { 235 Self { 236 open: false, 237 buffer: vec![SpriteAttributeEntry::default(); 80] 238 .into_boxed_slice() 239 .try_into() 240 .unwrap(), 241 adjust_coordinates: false, 242 } 243 } 244} 245 246struct S32XPaletteRamState { 247 open: bool, 248 selected_color: usize, 249 popup_contains_pointer: bool, 250 entry_buffer: Box<[CramEntry; 256]>, 251 color_buffer: Box<[Color; 256]>, 252 texture: Option<egui::TextureId>, 253} 254 255impl S32XPaletteRamState { 256 fn new() -> Self { 257 Self { 258 open: false, 259 selected_color: 0, 260 popup_contains_pointer: false, 261 entry_buffer: vec![CramEntry::default(); 256].into_boxed_slice().try_into().unwrap(), 262 color_buffer: vec![Color::default(); 256].into_boxed_slice().try_into().unwrap(), 263 texture: None, 264 } 265 } 266} 267 268struct State { 269 memory_viewers: HashMap<MemoryArea, MemoryViewerState>, 270 memory_edit_hook: Box<dyn FnMut(MemoryArea, usize, u8)>, 271 cram: CramWindowState, 272 vram: VramWindowState, 273 h_scroll: HScrollWindowState, 274 sprite_attributes: SpriteAttributesWindowState, 275 s32x_palette: S32XPaletteRamState, 276 z80: Z80DebugWindowState, 277 m68k: M68kDebugWindowState, 278 m68k_sub: M68kDebugWindowState, 279 sh2_master: Sh2DebugWindowState, 280 sh2_slave: Sh2DebugWindowState, 281 vdp_registers_open: bool, 282 vdp_state_open: bool, 283 ym2612: Ym2612DebugWindowState, 284 psg_open: bool, 285 master_sh2_registers_open: bool, 286 slave_sh2_registers_open: bool, 287 s32x_system_registers_open: bool, 288 s32x_vdp_registers_open: bool, 289 s32x_pwm_registers_open: bool, 290} 291 292impl State { 293 fn new(memory_edit_hook: Box<dyn FnMut(MemoryArea, usize, u8)>) -> Self { 294 Self { 295 memory_viewers: MemoryArea::new_states(), 296 memory_edit_hook, 297 cram: CramWindowState::new(), 298 vram: VramWindowState::new(), 299 h_scroll: HScrollWindowState::new(), 300 sprite_attributes: SpriteAttributesWindowState::new(), 301 s32x_palette: S32XPaletteRamState::new(), 302 z80: Z80DebugWindowState::new(), 303 m68k: M68kDebugWindowState::new_default_titles() 304 .with_interrupt_breakpoints(Box::new(Main68kInterruptBreakpoints::default())), 305 m68k_sub: M68kDebugWindowState::new_with_titles( 306 "Sub 68000 Disassembly", 307 "Sub 68000 Breakpoints", 308 ) 309 .with_interrupt_breakpoints(Box::new(Sub68kInterruptBreakpoints::default())), 310 sh2_master: Sh2DebugWindowState::new(WhichCpu::Master), 311 sh2_slave: Sh2DebugWindowState::new(WhichCpu::Slave), 312 vdp_registers_open: false, 313 vdp_state_open: false, 314 ym2612: Ym2612DebugWindowState::new(), 315 psg_open: false, 316 master_sh2_registers_open: false, 317 slave_sh2_registers_open: false, 318 s32x_system_registers_open: false, 319 s32x_vdp_registers_open: false, 320 s32x_pwm_registers_open: false, 321 } 322 } 323} 324 325pub(crate) struct GenesisDebuggerFrontendState<'a> { 326 state: &'a mut GenesisDebugState, 327 debugger_handle: &'a GenesisDebuggerHandle, 328} 329 330impl GenesisDebuggerFrontendState<'_> { 331 fn has_memory(&self, memory_area: MemoryArea) -> bool { 332 match memory_area { 333 MemoryArea::Genesis(GenesisMemoryArea::CartridgeRom) => self.state.cartridge.is_some(), 334 MemoryArea::Genesis(_) => true, 335 MemoryArea::SegaCd(_) => self.state.sega_cd.is_some(), 336 MemoryArea::Sega32X(_) => self.state.sega_32x.is_some(), 337 } 338 } 339 340 fn debug_memory_view( 341 &mut self, 342 memory_area: MemoryArea, 343 ) -> Option<Box<dyn DebugMemoryView + '_>> { 344 match memory_area { 345 MemoryArea::Genesis(memory_area) => Some(self.state.memory_view(memory_area)), 346 MemoryArea::SegaCd(memory_area) => { 347 self.state.sega_cd.as_mut().map(|sega_cd| sega_cd.scd_memory_view(memory_area)) 348 } 349 MemoryArea::Sega32X(memory_area) => { 350 self.state.sega_32x.as_mut().map(|sega_32x| sega_32x.s32x_memory_view(memory_area)) 351 } 352 } 353 } 354} 355 356pub(crate) type GenesisDebugRenderFn = 357 dyn FnMut(DebugRenderContext<'_>, GenesisDebuggerFrontendState<'_>); 358 359pub(crate) fn render_fn( 360 memory_edit_hook: Box<dyn FnMut(MemoryArea, usize, u8)>, 361) -> Box<GenesisDebugRenderFn> { 362 let mut state = State::new(memory_edit_hook); 363 Box::new(move |ctx, mut emu_state| render(ctx, &mut emu_state, &mut state)) 364} 365 366fn render( 367 ctx: DebugRenderContext<'_>, 368 debug_state: &mut GenesisDebuggerFrontendState<'_>, 369 state: &mut State, 370) { 371 Panel::top("gen_debug_top").show_inside(ctx.egui_ui, |ui| { 372 egui::MenuBar::new().ui(ui, |ui| { 373 ui.menu_button("Memory Viewers", |ui| { 374 for &memory_area in MemoryArea::ALL { 375 if !debug_state.has_memory(memory_area) { 376 continue; 377 } 378 379 if ui.button(memory_area.name()).clicked() { 380 if let Some(memviewer_state) = state.memory_viewers.get_mut(&memory_area) { 381 memviewer_state.open_window(ui); 382 } 383 ui.close_kind(UiKind::Menu); 384 } 385 } 386 }); 387 388 ui.menu_button("Register Viewers", |ui| { 389 if ui.button("VDP Registers").clicked() { 390 state.vdp_registers_open = true; 391 crate::move_to_top(ui, VDP_REGISTERS_WINDOW_TITLE); 392 ui.close_kind(UiKind::Menu); 393 } 394 395 if ui.button("VDP State").clicked() { 396 state.vdp_state_open = true; 397 crate::move_to_top(ui, VDP_STATE_WINDOW_TITLE); 398 ui.close_kind(UiKind::Menu); 399 } 400 401 if ui.button("YM2612").clicked() { 402 state.ym2612.open_window(ui); 403 ui.close_kind(UiKind::Menu); 404 } 405 406 if ui.button("SN76489").clicked() { 407 state.psg_open = true; 408 crate::move_to_top(ui, psgdebug::WINDOW_TITLE); 409 ui.close_kind(UiKind::Menu); 410 } 411 412 if debug_state.state.sega_32x.is_some() { 413 if ui.button("Master SH-2 Registers").clicked() { 414 state.master_sh2_registers_open = true; 415 crate::move_to_top(ui, MASTER_SH2_REGISTERS_WINDOW_TITLE); 416 ui.close_kind(UiKind::Menu); 417 } 418 419 if ui.button("Slave SH-2 Registers").clicked() { 420 state.slave_sh2_registers_open = true; 421 crate::move_to_top(ui, SLAVE_SH2_REGISTERS_WINDOW_TITLE); 422 ui.close_kind(UiKind::Menu); 423 } 424 425 if ui.button("32X System Registers").clicked() { 426 state.s32x_system_registers_open = true; 427 crate::move_to_top(ui, S32X_SYSTEM_REGISTERS_WINDOW_TITLE); 428 ui.close_kind(UiKind::Menu); 429 } 430 431 if ui.button("32X VDP").clicked() { 432 state.s32x_vdp_registers_open = true; 433 crate::move_to_top(ui, S32X_VDP_REGISTERS_WINDOW_TITLE); 434 ui.close_kind(UiKind::Menu); 435 } 436 437 if ui.button("32X PWM").clicked() { 438 state.s32x_pwm_registers_open = true; 439 crate::move_to_top(ui, S32X_PWM_REGISTERS_WINDOW_TITLE); 440 ui.close_kind(UiKind::Menu); 441 } 442 } 443 }); 444 445 ui.menu_button("Video Memory", |ui| { 446 if ui.button("CRAM").clicked() { 447 state.cram.open = true; 448 crate::move_to_top(ui, CRAM_WINDOW_TITLE); 449 ui.close_kind(UiKind::Menu); 450 } 451 452 if ui.button("VRAM").clicked() { 453 state.vram.open = true; 454 crate::move_to_top(ui, VRAM_WINDOW_TITLE); 455 ui.close_kind(UiKind::Menu); 456 } 457 458 if ui.button("Sprite Attributes").clicked() { 459 state.sprite_attributes.open = true; 460 crate::move_to_top(ui, SPRITE_ATTRIBUTES_WINDOW_TITLE); 461 ui.close_kind(UiKind::Menu); 462 } 463 464 if ui.button("H Scroll Table").clicked() { 465 state.h_scroll.open = true; 466 crate::move_to_top(ui, H_SCROLL_WINDOW_TITLE); 467 ui.close_kind(UiKind::Menu); 468 } 469 470 if debug_state.state.sega_32x.is_some() && ui.button("32X Palette RAM").clicked() { 471 state.s32x_palette.open = true; 472 crate::move_to_top(ui, S32X_PALETTE_WINDOW_TITLE); 473 ui.close_kind(UiKind::Menu); 474 } 475 }); 476 477 ui.menu_button("CPU Debuggers", |ui| { 478 if ui.button("68000").clicked() { 479 state.m68k.open_disassembly_window(ui); 480 ui.close_kind(UiKind::Menu); 481 } 482 483 if debug_state.state.sega_cd.is_some() && ui.button("Sub 68000").clicked() { 484 state.m68k_sub.open_disassembly_window(ui); 485 ui.close_kind(UiKind::Menu); 486 } 487 488 if ui.button("Z80").clicked() { 489 state.z80.open_disassembly_window(ui); 490 ui.close_kind(UiKind::Menu); 491 } 492 493 if debug_state.state.sega_32x.is_some() { 494 if ui.button("SH-2 Master").clicked() { 495 state.sh2_master.open_disassembly_window(ui); 496 ui.close_kind(UiKind::Menu); 497 } 498 499 if ui.button("SH-2 Slave").clicked() { 500 state.sh2_slave.open_disassembly_window(ui); 501 ui.close_kind(UiKind::Menu); 502 } 503 } 504 }); 505 }); 506 }); 507 508 render_memory_viewer_windows( 509 ctx.egui_ui, 510 debug_state, 511 &mut state.memory_viewers, 512 &mut state.memory_edit_hook, 513 ); 514 515 render_vdp_registers_window(ctx.egui_ui, debug_state, &mut state.vdp_registers_open); 516 517 render_vdp_state_window(ctx.egui_ui, debug_state, &mut state.vdp_state_open); 518 519 ym2612debug::render_debug_window( 520 ctx.egui_ui, 521 debug_state.state.ym2612.debug_view(), 522 &mut state.ym2612, 523 ); 524 525 psgdebug::render_debug_window(ctx.egui_ui, &debug_state.state.psg, &mut state.psg_open); 526 527 let screen_width = crate::screen_width(ctx.egui_ui); 528 529 render_cram_window(ctx.egui_ui, screen_width, debug_state, &mut state.cram); 530 render_vram_window(ctx.egui_ui, screen_width, debug_state, &mut state.vram); 531 render_h_scroll_window(ctx.egui_ui, debug_state, &mut state.h_scroll); 532 render_sprite_attributes_window(ctx.egui_ui, debug_state, &mut state.sprite_attributes); 533 534 render_m68k_debug_windows(ctx.egui_ui, debug_state, state); 535 render_z80_debug_windows(ctx.egui_ui, debug_state, state); 536 537 if let Some(sega_32x) = &mut debug_state.state.sega_32x { 538 render_sh2_registers_window( 539 ctx.egui_ui, 540 &mut sega_32x.sh2_master, 541 MASTER_SH2_REGISTERS_WINDOW_TITLE, 542 &mut state.master_sh2_registers_open, 543 ); 544 render_sh2_registers_window( 545 ctx.egui_ui, 546 &mut sega_32x.sh2_slave, 547 SLAVE_SH2_REGISTERS_WINDOW_TITLE, 548 &mut state.slave_sh2_registers_open, 549 ); 550 551 render_32x_palette_window(ctx.egui_ui, sega_32x, &mut state.s32x_palette); 552 render_32x_system_registers_window( 553 ctx.egui_ui, 554 sega_32x, 555 &mut state.s32x_system_registers_open, 556 ); 557 render_32x_vdp_registers_window(ctx.egui_ui, sega_32x, &mut state.s32x_vdp_registers_open); 558 render_32x_pwm_registers_window(ctx.egui_ui, sega_32x, &mut state.s32x_pwm_registers_open); 559 560 let debugger_handle = debug_state.debugger_handle; 561 let break_status = debugger_handle.sh2_break_status(); 562 563 sh2debug::render_disassembly_window( 564 ctx.egui_ui, 565 debug_state.state, 566 &mut state.sh2_master, 567 &debugger_handle.command_sender, 568 break_status.get(WhichCpu::Master), 569 ); 570 sh2debug::render_disassembly_window( 571 ctx.egui_ui, 572 debug_state.state, 573 &mut state.sh2_slave, 574 &debugger_handle.command_sender, 575 break_status.get(WhichCpu::Slave), 576 ); 577 578 sh2debug::render_breakpoints_window( 579 ctx.egui_ui, 580 &mut state.sh2_master, 581 &debugger_handle.command_sender, 582 ); 583 sh2debug::render_breakpoints_window( 584 ctx.egui_ui, 585 &mut state.sh2_slave, 586 &debugger_handle.command_sender, 587 ); 588 } 589} 590 591fn render_m68k_debug_windows( 592 ctx: &egui::Context, 593 debug_state: &mut GenesisDebuggerFrontendState<'_>, 594 state: &mut State, 595) { 596 let debugger_handle = debug_state.debugger_handle; 597 598 { 599 let main_memory_map = Genesis68kMemoryMap::new(debug_state.state); 600 601 m68kdebug::render_disassembly_window( 602 ctx, 603 &debug_state.state.m68k, 604 &main_memory_map, 605 &mut state.m68k, 606 debugger_handle.m68k_break_status(), 607 Some(|command| { 608 let genesis_cmd = match command { 609 M68kBreakCommand::Pause => GenesisDebugCommand::BreakPause68k, 610 M68kBreakCommand::Resume => GenesisDebugCommand::BreakResume, 611 M68kBreakCommand::Step => GenesisDebugCommand::BreakStep68k, 612 }; 613 let _ = debugger_handle.send_command(genesis_cmd); 614 }), 615 ); 616 617 m68kdebug::render_breakpoints_window(ctx, &mut state.m68k, |breakpoints| { 618 let _ = debugger_handle 619 .send_command(GenesisDebugCommand::Update68kBreakpoints(breakpoints)); 620 }); 621 } 622 623 if let Some(sega_cd) = &mut debug_state.state.sega_cd { 624 let sub_memory_map = SegaCdSubMemoryMap::new(sega_cd); 625 let sub_cpu = sega_cd.sub_cpu(); 626 m68kdebug::render_disassembly_window( 627 ctx, 628 sub_cpu, 629 &sub_memory_map, 630 &mut state.m68k_sub, 631 debugger_handle.sub_cpu_break_status(), 632 Some(|command| { 633 let scd_command = match command { 634 M68kBreakCommand::Resume => GenesisDebugCommand::BreakResume, 635 M68kBreakCommand::Pause => GenesisDebugCommand::BreakPauseSub68k, 636 M68kBreakCommand::Step => GenesisDebugCommand::BreakStepSub68k, 637 }; 638 let _ = debugger_handle.send_command(scd_command); 639 }), 640 ); 641 642 m68kdebug::render_breakpoints_window(ctx, &mut state.m68k_sub, |breakpoints| { 643 let _ = debugger_handle 644 .send_command(GenesisDebugCommand::UpdateSub68kBreakpoints(breakpoints)); 645 }); 646 } 647} 648 649fn render_z80_debug_windows( 650 ctx: &egui::Context, 651 debug_state: &mut GenesisDebuggerFrontendState<'_>, 652 state: &mut State, 653) { 654 let memory_map = Genesis68kMemoryMap::new(debug_state.state); 655 656 let debugger_handle = debug_state.debugger_handle; 657 658 z80debug::render_disassembly_window( 659 ctx, 660 &debug_state.state.z80, 661 GenesisZ80MemoryMap::new(debug_state.state, &memory_map), 662 &mut state.z80, 663 debugger_handle.z80_break_status(), 664 Some(|command| { 665 let genesis_command = match command { 666 Z80BreakCommand::Pause => GenesisDebugCommand::BreakPauseZ80, 667 Z80BreakCommand::Resume => GenesisDebugCommand::BreakResume, 668 Z80BreakCommand::Step => GenesisDebugCommand::BreakStepZ80, 669 }; 670 let _ = debugger_handle.send_command(genesis_command); 671 }), 672 ); 673 674 z80debug::render_breakpoints_window(ctx, &mut state.z80, |breakpoints| { 675 let _ = 676 debugger_handle.send_command(GenesisDebugCommand::UpdateZ80Breakpoints(breakpoints)); 677 }); 678} 679 680fn render_memory_viewer_windows( 681 egui_ctx: &egui::Context, 682 emu_state: &mut GenesisDebuggerFrontendState<'_>, 683 memory_viewer_states: &mut HashMap<MemoryArea, MemoryViewerState>, 684 memory_edit_hook: &mut dyn FnMut(MemoryArea, usize, u8), 685) { 686 for (&memory_area, state) in memory_viewer_states.iter_mut() { 687 if let Some(memory) = emu_state.debug_memory_view(memory_area) { 688 let mut memory = DebugViewWithWriteHook::new( 689 memory, 690 Box::new(|address, value| memory_edit_hook(memory_area, address, value)), 691 ); 692 memviewer::render(egui_ctx, &mut memory, state); 693 } 694 } 695} 696 697fn render_color_info_grid( 698 id: impl Hash, 699 ui: &mut Ui, 700 palette: usize, 701 color_idx: usize, 702 entry: CramEntry, 703) { 704 Grid::new(id).show(ui, |ui| { 705 ui.label("Palette"); 706 ui.label(palette.to_string()); 707 ui.end_row(); 708 709 ui.label("Index"); 710 ui.label(color_idx.to_string()); 711 ui.end_row(); 712 713 ui.label("Color"); 714 ui.label(format!("0x{:03X}", entry.value)); 715 ui.end_row(); 716 717 ui.label("R / G / B"); 718 ui.label(format!( 719 "{} / {} / {}", 720 (entry.value >> 1) & 7, 721 (entry.value >> 5) & 7, 722 (entry.value >> 9) & 7 723 )); 724 ui.end_row(); 725 726 ui.label("Display"); 727 ui.label(format_display_color(entry.color)); 728 ui.end_row(); 729 }); 730} 731 732fn format_display_color(color: Color) -> String { 733 format!("#{:02X}{:02X}{:02X}", color.r, color.g, color.b) 734} 735 736fn render_cram_window( 737 ctx: &egui::Context, 738 screen_width: f32, 739 emu_state: &mut GenesisDebuggerFrontendState<'_>, 740 state: &mut CramWindowState, 741) { 742 Window::new(CRAM_WINDOW_TITLE) 743 .open(&mut state.open) 744 .constrain(false) 745 .default_pos(CRAM_DEFAULT_POS) 746 .default_size([screen_width * 0.95, 225.0]) 747 .show(ctx, |ui| { 748 emu_state.state.vdp.copy_cram(state.entry_buffer.as_mut_slice(), state.modifier); 749 for (entry, color) in 750 iter::zip(state.entry_buffer.iter(), state.color_buffer.iter_mut()) 751 { 752 *color = entry.color; 753 } 754 755 Panel::right("cram_right_panel").resizable(false).min_size(125.0).show_inside( 756 ui, 757 |ui| { 758 let cram_entry = state.entry_buffer[state.selected_color]; 759 760 paint_rect_and_advance_cursor( 761 ui, 762 ui.max_rect().min, 763 Vec2::new(50.0, 50.0), 764 cram_entry.color, 765 ); 766 767 render_color_info_grid( 768 "cram_side_panel_grid", 769 ui, 770 state.selected_color / 16, 771 state.selected_color % 16, 772 cram_entry, 773 ); 774 }, 775 ); 776 777 CentralPanel::default().show_inside(ui, |ui| { 778 let modifier_resp = ui.horizontal(|ui| { 779 ui.radio_value(&mut state.modifier, ColorModifier::None, "Normal"); 780 ui.radio_value(&mut state.modifier, ColorModifier::Shadow, "Shadowed"); 781 ui.radio_value(&mut state.modifier, ColorModifier::Highlight, "Highlighted"); 782 }); 783 784 let mut image_size = 785 ui.max_rect().size() - Vec2::new(0.0, modifier_resp.response.rect.height()); 786 if image_size.y > image_size.x * 0.25 { 787 image_size.y = image_size.x * 0.25; 788 } else { 789 image_size.x = image_size.y * 4.0; 790 } 791 792 let texture = crate::update_egui_texture( 793 ctx, 794 [CRAM_COLS, CRAM_ROWS], 795 state.color_buffer.as_slice(), 796 &mut state.texture, 797 ); 798 let image_resp = ui.add(Image::new((texture, image_size)).sense(Sense::click())); 799 800 let mut clicked = image_resp.clicked(); 801 let contains_pointer = 802 image_resp.contains_pointer() || state.popup_contains_pointer; 803 if (clicked || contains_pointer) 804 && let Some(pos) = ui.input(|i| i.pointer.latest_pos()) 805 { 806 let normalized_pos = crate::normalize_position(pos, image_resp.interact_rect); 807 let row = (normalized_pos.y * (CRAM_ROWS as f32)) as usize; 808 let col = (normalized_pos.x * (CRAM_COLS as f32)) as usize; 809 810 if row < CRAM_ROWS && col < CRAM_COLS { 811 let buffer_idx = CRAM_COLS * row + col; 812 813 if contains_pointer { 814 let cram_entry = state.entry_buffer[buffer_idx]; 815 816 let popup_resp = Popup::from_response(&image_resp) 817 .at_pointer() 818 .gap(5.0) 819 .show(|ui| { 820 paint_rect_and_advance_cursor( 821 ui, 822 ui.max_rect().min + Vec2::new(35.0, 0.0), 823 Vec2::new(25.0, 25.0), 824 cram_entry.color, 825 ); 826 827 render_color_info_grid( 828 "cram_popup_grid", 829 ui, 830 row, 831 col, 832 cram_entry, 833 ); 834 }); 835 clicked |= 836 popup_resp.as_ref().is_some_and(|resp| resp.response.clicked()); 837 state.popup_contains_pointer = 838 popup_resp.is_some_and(|resp| resp.response.contains_pointer()); 839 } 840 841 if clicked { 842 state.selected_color = buffer_idx; 843 } 844 } 845 } 846 }); 847 }); 848} 849 850fn paint_rect_and_advance_cursor(ui: &mut Ui, min: Pos2, size: Vec2, fill_color: Color) { 851 let rect = Rect { min, max: min + size }; 852 ui.painter().rect( 853 rect, 854 CornerRadius::ZERO, 855 Color32::from_rgb(fill_color.r, fill_color.g, fill_color.b), 856 Stroke::new(1.0_f32, ui.visuals().text_color()), 857 StrokeKind::Middle, 858 ); 859 ui.advance_cursor_after_rect(rect); 860} 861 862fn render_vram_window( 863 ctx: &egui::Context, 864 screen_width: f32, 865 emu_state: &mut GenesisDebuggerFrontendState<'_>, 866 state: &mut VramWindowState, 867) { 868 Window::new(VRAM_WINDOW_TITLE) 869 .open(&mut state.open) 870 .constrain(false) 871 .default_pos(VRAM_DEFAULT_POS) 872 .default_width(screen_width * 0.95) 873 .show(ctx, |ui| { 874 ui.horizontal(|ui| { 875 ui.label("Palette"); 876 877 for i in 0..4 { 878 ui.radio_value(&mut state.palette, i, format!("{i}")); 879 } 880 }); 881 882 emu_state.state.vdp.copy_vram(state.buffer.as_mut_slice(), state.palette, 64); 883 884 let mut height = ui.available_width() * 0.45; 885 if height > ui.available_height() { 886 height = ui.available_height(); 887 } 888 let width = height * 2.0; 889 890 let texture = crate::update_egui_texture( 891 ctx, 892 [64 * 8, 32 * 8], 893 state.buffer.as_slice(), 894 &mut state.texture, 895 ); 896 ui.image((texture, Vec2::new(width, height))); 897 }); 898} 899 900fn render_h_scroll_window( 901 ctx: &egui::Context, 902 emu_state: &mut GenesisDebuggerFrontendState<'_>, 903 state: &mut HScrollWindowState, 904) { 905 Window::new(H_SCROLL_WINDOW_TITLE) 906 .open(&mut state.open) 907 .constrain(false) 908 .default_pos(crate::rand_window_pos()) 909 .default_width(200.0) 910 .show(ctx, |ui| { 911 emu_state.state.vdp.copy_h_scroll(state.buffer.as_mut_slice()); 912 913 crate::brighten_faint_bg_color(ui); 914 915 TableBuilder::new(ui) 916 .scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible) 917 .column(Column::auto().at_least(50.0)) 918 .columns(Column::auto(), 2) 919 .column(Column::remainder()) 920 .striped(true) 921 .header(20.0, |mut header| { 922 header.col(|ui| { 923 ui.heading("Line"); 924 }); 925 header.col(|ui| { 926 ui.heading("Plane A"); 927 }); 928 header.col(|ui| { 929 ui.heading("Plane B"); 930 }); 931 header.col(|_ui| {}); 932 }) 933 .body(|body| { 934 body.rows(18.0, 256, |mut row| { 935 let line = row.index(); 936 let (h_scroll_a, h_scroll_b) = state.buffer[line]; 937 938 row.col(|ui| { 939 ui.label(line.to_string()); 940 }); 941 row.col(|ui| { 942 ui.label(h_scroll_a.to_string()); 943 }); 944 row.col(|ui| { 945 ui.label(h_scroll_b.to_string()); 946 }); 947 row.col(|_ui| {}); 948 }); 949 }); 950 }); 951} 952 953fn render_sprite_attributes_window( 954 ctx: &egui::Context, 955 emu_state: &mut GenesisDebuggerFrontendState<'_>, 956 state: &mut SpriteAttributesWindowState, 957) { 958 Window::new(SPRITE_ATTRIBUTES_WINDOW_TITLE) 959 .open(&mut state.open) 960 .constrain(false) 961 .default_pos(crate::rand_window_pos()) 962 .default_width(500.0) 963 .show(ctx, |ui| { 964 let CopySpriteAttributesResult { sprite_table_len, top_left_x, top_left_y } = 965 emu_state.state.vdp.copy_sprite_attributes(state.buffer.as_mut_slice()); 966 967 ui.checkbox(&mut state.adjust_coordinates, "Shift coordinates to top-left of screen"); 968 969 let (x_offset, y_offset) = if state.adjust_coordinates { 970 (-i32::from(top_left_x), -i32::from(top_left_y)) 971 } else { 972 (0, 0) 973 }; 974 975 crate::brighten_faint_bg_color(ui); 976 977 TableBuilder::new(ui) 978 .scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible) 979 .columns(Column::auto().at_least(50.0), 11) 980 .column(Column::remainder()) 981 .striped(true) 982 .header(20.0, |mut header| { 983 for heading in [ 984 "Index", 985 "Tile", 986 "X", 987 "Y", 988 "Cell Size", 989 "Palette", 990 "Priority", 991 "H Flip", 992 "V Flip", 993 "Link", 994 ] { 995 header.col(|ui| { 996 ui.heading(heading); 997 }); 998 } 999 header.col(|_ui| {}); 1000 }) 1001 .body(|body| { 1002 body.rows(18.0, sprite_table_len as usize, |mut row| { 1003 let idx = row.index(); 1004 let sprite = state.buffer[idx]; 1005 1006 for value in [ 1007 idx.to_string(), 1008 sprite.tile_number.to_string(), 1009 (i32::from(sprite.x) + x_offset).to_string(), 1010 (i32::from(sprite.y) + y_offset).to_string(), 1011 format!("{}x{}", sprite.h_cells, sprite.v_cells), 1012 sprite.palette.to_string(), 1013 u8::from(sprite.priority).to_string(), 1014 u8::from(sprite.h_flip).to_string(), 1015 u8::from(sprite.v_flip).to_string(), 1016 sprite.link.to_string(), 1017 ] { 1018 row.col(|ui| { 1019 ui.label(value); 1020 }); 1021 } 1022 row.col(|_ui| {}); 1023 }); 1024 }); 1025 }); 1026} 1027 1028fn render_32x_color_grid(id: impl Hash, ui: &mut Ui, index: usize, entry: CramEntry) { 1029 Grid::new(id).show(ui, |ui| { 1030 ui.label("Index"); 1031 ui.label(format!("0x{index:02X} ({index})")); 1032 ui.end_row(); 1033 1034 ui.label("Color"); 1035 ui.label(format!("0x{:04X}", entry.value)); 1036 ui.end_row(); 1037 1038 let fmt_color = |component: u16| format!("0x{component:02X} ({component})"); 1039 1040 ui.label("Red"); 1041 ui.label(fmt_color(entry.value & 0x1F)); 1042 ui.end_row(); 1043 1044 ui.label("Green"); 1045 ui.label(fmt_color((entry.value >> 5) & 0x1F)); 1046 ui.end_row(); 1047 1048 ui.label("Blue"); 1049 ui.label(fmt_color((entry.value >> 10) & 0x1F)); 1050 ui.end_row(); 1051 1052 ui.label("Priority"); 1053 ui.label(if entry.value.bit(15) { "1" } else { "0" }); 1054 ui.end_row(); 1055 1056 ui.label("Display"); 1057 ui.label(format_display_color(entry.color)); 1058 ui.end_row(); 1059 }); 1060} 1061 1062fn render_sh2_registers_window( 1063 ctx: &egui::Context, 1064 sh2: &mut Sh2, 1065 window_title: &str, 1066 open: &mut bool, 1067) { 1068 // Roughly 23.01 MHz 1069 const SH2_CLOCK_RATE: f64 = genesis_components::NTSC_GENESIS_MCLK_FREQUENCY 1070 / (genesis_config::NATIVE_M68K_DIVIDER as f64) 1071 * (genesis_config::NATIVE_SH2_MULTIPLIER as f64); 1072 1073 Window::new(window_title) 1074 .open(open) 1075 .constrain(false) 1076 .default_pos(crate::rand_window_pos()) 1077 .show(ctx, |ui| { 1078 ScrollArea::vertical().show(ui, |ui| { 1079 let state = sh2.sh7604_debug_state(); 1080 1081 crate::render_registers_table( 1082 ui, 1083 "Cache Control", 1084 &[ 1085 ("Enabled", &state.cache.enabled.to_string()), 1086 ( 1087 "Instruction replacement", 1088 &state.cache.instruction_replacement_enabled.to_string(), 1089 ), 1090 ("Data replacement", &state.cache.data_replacement_enabled.to_string()), 1091 ("Mode", cache_mode_str(state.cache.mode)), 1092 ], 1093 ); 1094 1095 ui.separator(); 1096 1097 crate::render_registers_table( 1098 ui, 1099 "Internal Interrupts", 1100 &[ 1101 ("WDT interrupt level", &state.interrupts.wdt_priority.to_string()), 1102 ("WDT vector number", &state.interrupts.wdt_vector.to_string()), 1103 ("DMAC interrupt level", &state.interrupts.dmac_priority.to_string()), 1104 ("DMA0 vector number", &state.interrupts.dma0_vector.to_string()), 1105 ("DMA1 vector number", &state.interrupts.dma1_vector.to_string()), 1106 ("SCI interrupt level", &state.interrupts.sci_priority.to_string()), 1107 ("SCI vector number", &state.interrupts.sci_vector.to_string()), 1108 ], 1109 ); 1110 1111 ui.separator(); 1112 1113 let wdt_frequency = SH2_CLOCK_RATE / (state.wdt.system_clock_divider as f64); 1114 1115 crate::render_registers_table( 1116 ui, 1117 "Watchdog Timer (WDT)", 1118 &[ 1119 ("Enabled", &state.wdt.enabled.to_string()), 1120 ("Clock divider", &format!("{}", state.wdt.system_clock_divider)), 1121 ("Frequency", &format!("{} Hz", wdt_frequency.round())), 1122 ("Counter", &state.wdt.counter.to_string()), 1123 ("Overflow flag", &state.wdt.overflow_flag.to_string()), 1124 ], 1125 ); 1126 1127 for (i, channel) in state.dmac.channels.iter().enumerate() { 1128 ui.separator(); 1129 1130 crate::render_registers_table( 1131 ui, 1132 &format!("DMA Channel {i} (DMAC)"), 1133 &[ 1134 ( 1135 "DMA master enabled", 1136 &state.dmac.operation.dma_master_enabled.to_string(), 1137 ), 1138 ("Channel enabled", &channel.control.dma_enabled.to_string()), 1139 ("Auto request", &channel.control.auto_request.to_string()), 1140 ("Transfer unit", transfer_unit_str(channel.control.transfer_size)), 1141 ( 1142 "Source address mode", 1143 dma_address_mode_str(channel.control.source_address_mode), 1144 ), 1145 ( 1146 "Destination address mode", 1147 dma_address_mode_str(channel.control.destination_address_mode), 1148 ), 1149 ("Source address", &format!("${:08X}", channel.source_address)), 1150 ( 1151 "Destination address", 1152 &format!("${:08X}", channel.destination_address), 1153 ), 1154 ("Length", &format!("0x{:08X}", channel.transfer_count)), 1155 ("Interrupt enabled", &channel.control.interrupt_enabled.to_string()), 1156 ("DMA complete flag", &channel.control.dma_complete.to_string()), 1157 ], 1158 ); 1159 } 1160 }); 1161 }); 1162} 1163 1164fn cache_mode_str(mode: CacheMode) -> &'static str { 1165 match mode { 1166 CacheMode::FourWay => "4-way", 1167 CacheMode::TwoWay => "2-way", 1168 } 1169} 1170 1171fn transfer_unit_str(unit: DmaTransferUnit) -> &'static str { 1172 match unit { 1173 DmaTransferUnit::Byte => "Byte", 1174 DmaTransferUnit::Word => "Word", 1175 DmaTransferUnit::Longword => "Longword", 1176 DmaTransferUnit::SixteenByte => "16-byte", 1177 } 1178} 1179 1180fn dma_address_mode_str(mode: DmaAddressMode) -> &'static str { 1181 match mode { 1182 DmaAddressMode::Fixed => "Fixed", 1183 DmaAddressMode::AutoIncrement => "Increment", 1184 DmaAddressMode::AutoDecrement => "Decrement", 1185 DmaAddressMode::Invalid => "Invalid", 1186 } 1187} 1188 1189fn render_32x_palette_window( 1190 ctx: &egui::Context, 1191 emu_state: &mut Sega32XDebugState, 1192 state: &mut S32XPaletteRamState, 1193) { 1194 Window::new(S32X_PALETTE_WINDOW_TITLE) 1195 .open(&mut state.open) 1196 .constrain(false) 1197 .default_pos(crate::rand_window_pos()) 1198 .default_size([700.0, 550.0]) 1199 .show(ctx, |ui| { 1200 emu_state.copy_palette(state.entry_buffer.as_mut_slice()); 1201 for (entry, color) in 1202 iter::zip(state.entry_buffer.iter(), state.color_buffer.iter_mut()) 1203 { 1204 *color = entry.color; 1205 } 1206 1207 Panel::right("32x_palette_side_panel").resizable(false).min_size(135.0).show_inside( 1208 ui, 1209 |ui| { 1210 let selected_entry = state.entry_buffer[state.selected_color]; 1211 1212 paint_rect_and_advance_cursor( 1213 ui, 1214 ui.max_rect().min, 1215 Vec2::new(50.0, 50.0), 1216 selected_entry.color, 1217 ); 1218 1219 render_32x_color_grid( 1220 "32x_palette_side_panel_grid", 1221 ui, 1222 state.selected_color, 1223 selected_entry, 1224 ); 1225 }, 1226 ); 1227 1228 CentralPanel::default().show_inside(ui, |ui| { 1229 let mut image_size = ui.max_rect().size(); 1230 if image_size.y > image_size.x { 1231 image_size.y = image_size.x; 1232 } else { 1233 image_size.x = image_size.y; 1234 } 1235 1236 let texture = crate::update_egui_texture( 1237 ctx, 1238 [16, 16], 1239 state.color_buffer.as_slice(), 1240 &mut state.texture, 1241 ); 1242 let image_resp = ui.add(Image::new((texture, image_size)).sense(Sense::click())); 1243 1244 let mut clicked = image_resp.clicked(); 1245 let contains_pointer = 1246 image_resp.contains_pointer() || state.popup_contains_pointer; 1247 if (clicked || contains_pointer) 1248 && let Some(pos) = ui.input(|i| i.pointer.latest_pos()) 1249 { 1250 let normalized_pos = crate::normalize_position(pos, image_resp.interact_rect); 1251 let row = (normalized_pos.y * 16.0) as usize; 1252 let col = (normalized_pos.x * 16.0) as usize; 1253 1254 if row < 16 && col < 16 { 1255 let buffer_idx = 16 * row + col; 1256 1257 if contains_pointer { 1258 let entry = state.entry_buffer[buffer_idx]; 1259 1260 let popup_resp = Popup::from_response(&image_resp) 1261 .at_pointer() 1262 .gap(5.0) 1263 .show(|ui| { 1264 paint_rect_and_advance_cursor( 1265 ui, 1266 ui.max_rect().min + Vec2::new(45.0, 0.0), 1267 Vec2::new(25.0, 25.0), 1268 entry.color, 1269 ); 1270 1271 render_32x_color_grid( 1272 "32x_palette_popup_color_grid", 1273 ui, 1274 buffer_idx, 1275 entry, 1276 ); 1277 }); 1278 clicked |= 1279 popup_resp.as_ref().is_some_and(|resp| resp.response.clicked()); 1280 state.popup_contains_pointer = 1281 popup_resp.is_some_and(|resp| resp.response.contains_pointer()); 1282 } 1283 1284 if clicked { 1285 state.selected_color = buffer_idx; 1286 } 1287 } 1288 } 1289 }); 1290 }); 1291} 1292 1293fn render_vdp_registers_window( 1294 ctx: &egui::Context, 1295 emu_state: &mut GenesisDebuggerFrontendState<'_>, 1296 open: &mut bool, 1297) { 1298 crate::render_registers_window(ctx, "VDP Registers", open, |ui| { 1299 emu_state.state.vdp.dump_registers(crate::dump_registers_callback(ui)); 1300 }); 1301} 1302 1303fn render_vdp_state_window( 1304 ctx: &egui::Context, 1305 emu_state: &mut GenesisDebuggerFrontendState<'_>, 1306 open: &mut bool, 1307) { 1308 Window::new(VDP_STATE_WINDOW_TITLE) 1309 .open(open) 1310 .constrain(false) 1311 .default_pos(crate::rand_window_pos()) 1312 .show(ctx, |ui| { 1313 ScrollArea::vertical().show(ui, |ui| { 1314 let state = emu_state.state.vdp.internal_state(); 1315 1316 crate::render_registers_table( 1317 ui, 1318 "Timing", 1319 &[ 1320 ("Scanline", &state.scanline.to_string()), 1321 ("Scanline MCLK cycles", &state.scanline_mclk.to_string()), 1322 ("Scanline pixel", &state.pixel.to_string()), 1323 ( 1324 "Active display pixel range", 1325 &format!( 1326 "{}-{}", 1327 state.active_display_pixels.start, 1328 state.active_display_pixels.end - 1 1329 ), 1330 ), 1331 ("VBlank flag", ["0", "1"][usize::from(state.vblank_flag)]), 1332 ("H (Internal)", &format!("0x{:03X}", state.internal_h)), 1333 ("H (External)", &format!("0x{:02X}", state.external_h)), 1334 ("V", &format!("0x{:02X}", state.v)), 1335 ], 1336 ); 1337 1338 ui.separator(); 1339 1340 crate::render_registers_table( 1341 ui, 1342 "Interrupts", 1343 &[ 1344 ( 1345 "Vertical interrupt pending", 1346 ["0", "1"][usize::from(state.v_interrupt_pending)], 1347 ), 1348 ( 1349 "Horizontal interrupt pending", 1350 ["0", "1"][usize::from(state.h_interrupt_pending)], 1351 ), 1352 ("Horizontal interrupt counter", &state.h_interrupt_counter.to_string()), 1353 ], 1354 ); 1355 1356 ui.separator(); 1357 1358 crate::render_registers_table( 1359 ui, 1360 "Control Port", 1361 &[ 1362 ("Mode", &format!("{:?}", state.data_port_mode)), 1363 ("Location", data_port_location_str(state.data_port_location)), 1364 ("Address", &format!("${:05X}", state.data_port_address)), 1365 ("Write flag", &format!("{:?}", state.write_flag)), 1366 ("DMA active", ["0", "1"][usize::from(state.dma_active)]), 1367 ], 1368 ); 1369 }); 1370 }); 1371} 1372 1373fn data_port_location_str(location: DataPortLocation) -> &'static str { 1374 match location { 1375 DataPortLocation::Vram => "VRAM", 1376 DataPortLocation::Vram8Bit => "VRAM (8-bit)", 1377 DataPortLocation::Cram => "CRAM", 1378 DataPortLocation::Vsram => "VSRAM", 1379 DataPortLocation::Invalid => "Invalid", 1380 } 1381} 1382 1383fn render_32x_system_registers_window( 1384 ctx: &egui::Context, 1385 emu_state: &mut Sega32XDebugState, 1386 open: &mut bool, 1387) { 1388 crate::render_registers_window(ctx, "32X System Registers", open, |ui| { 1389 emu_state.dump_32x_system_registers(crate::dump_registers_callback(ui)); 1390 }); 1391} 1392 1393fn render_32x_vdp_registers_window( 1394 ctx: &egui::Context, 1395 emu_state: &mut Sega32XDebugState, 1396 open: &mut bool, 1397) { 1398 crate::render_registers_window(ctx, "32X VDP Registers", open, |ui| { 1399 emu_state.dump_32x_vdp_registers(crate::dump_registers_callback(ui)); 1400 }); 1401} 1402 1403fn render_32x_pwm_registers_window( 1404 ctx: &egui::Context, 1405 emu_state: &mut Sega32XDebugState, 1406 open: &mut bool, 1407) { 1408 crate::render_registers_window(ctx, "32X PWM Registers", open, |ui| { 1409 emu_state.dump_pwm_registers(crate::dump_registers_callback(ui)); 1410 }); 1411} 1412 1413struct GenesisDebugRunnerProcess { 1414 state_sender: SharedVarSender<GenesisDebugState>, 1415 debugger: GenesisDebugger, 1416} 1417 1418impl<R, A, I, S> DebuggerRunnerProcess<GenesisEmulator, R, A, I, S> for GenesisDebugRunnerProcess 1419where 1420 R: Renderer, 1421 A: AudioOutput, 1422 I: InputPoller<GenesisInputs>, 1423 S: SaveWriter, 1424{ 1425 fn run( 1426 &mut self, 1427 emulator: &mut GenesisEmulator, 1428 ) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { 1429 let mut debug_view = emulator.as_debug_view(); 1430 self.debugger.process_commands(&mut debug_view); 1431 self.state_sender.update(debug_view.to_debug_state()); 1432 1433 Ok(()) 1434 } 1435 1436 fn run_emulator_till_next_frame( 1437 &mut self, 1438 emulator: &mut GenesisEmulator, 1439 renderer: &mut R, 1440 audio_output: &mut A, 1441 input_poller: &mut I, 1442 save_writer: &mut S, 1443 ) -> RunTillNextResult<GenesisEmulator, R::Err, A::Err, S::Err> { 1444 while emulator.debug_tick( 1445 renderer, 1446 audio_output, 1447 input_poller, 1448 save_writer, 1449 &mut self.debugger, 1450 )? != TickEffect::FrameRendered 1451 {} 1452 1453 Ok(()) 1454 } 1455} 1456 1457struct GenesisDebugMainProcess { 1458 debugger_handle: GenesisDebuggerHandle, 1459 state_receiver: SharedVarReceiver<GenesisDebugState>, 1460 render_fn: Box<GenesisDebugRenderFn>, 1461} 1462 1463impl DebuggerMainProcess for GenesisDebugMainProcess { 1464 fn run( 1465 &mut self, 1466 ctx: DebugRenderContext<'_>, 1467 ) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { 1468 let Some(state) = self.state_receiver.get() else { return Ok(()) }; 1469 (self.render_fn)( 1470 ctx, 1471 GenesisDebuggerFrontendState { state, debugger_handle: &self.debugger_handle }, 1472 ); 1473 1474 Ok(()) 1475 } 1476} 1477 1478#[must_use] 1479pub fn genesis_debug_fn<R, A, I, S>() -> DebuggerProcesses<GenesisEmulator, R, A, I, S> 1480where 1481 R: Renderer, 1482 A: AudioOutput, 1483 I: InputPoller<GenesisInputs>, 1484 S: SaveWriter, 1485{ 1486 let (state_sender, state_receiver) = jgenesis_common::sync::new_shared_var(); 1487 let (debugger, debugger_handle) = GenesisDebugger::new(state_sender.clone()); 1488 1489 let memory_edit_hook = { 1490 let command_sender = debugger_handle.command_sender.clone(); 1491 1492 Box::new(move |memory_area, address, value| { 1493 if let MemoryArea::Genesis(memory_area) = memory_area { 1494 let _ = command_sender.send(GenesisDebugCommand::EditMemory( 1495 memory_area, 1496 address, 1497 value, 1498 )); 1499 } 1500 }) 1501 }; 1502 1503 let runner_process = GenesisDebugRunnerProcess { state_sender, debugger }; 1504 let main_process = GenesisDebugMainProcess { 1505 debugger_handle, 1506 state_receiver, 1507 render_fn: render_fn(memory_edit_hook), 1508 }; 1509 1510 (Box::new(runner_process), Box::new(main_process)) 1511}