diff --git a/emu/src/cpu/arm7tdmi.rs b/emu/src/cpu/arm7tdmi.rs index 42d7a1c..8800d9b 100644 --- a/emu/src/cpu/arm7tdmi.rs +++ b/emu/src/cpu/arm7tdmi.rs @@ -1,9 +1,9 @@ use std::convert::TryInto; +use serde::{Deserialize, Serialize}; + #[cfg(feature = "logger")] use logger::log; - -use serde::{Deserialize, Serialize}; #[cfg(feature = "disassembler")] use vecfixed::VecFixed; @@ -38,6 +38,8 @@ pub struct Arm7tdmi { decoded_arm: Option, fetched_thumb: Option, decoded_thumb: Option, + + pub current_cycle: u128, } #[derive(Copy, Clone)] @@ -124,6 +126,7 @@ impl Default for Arm7tdmi { decoded_arm: None, fetched_thumb: None, decoded_thumb: None, + current_cycle: u128::default(), }; // Setting ARM mode at startup @@ -449,6 +452,7 @@ impl Arm7tdmi { } pub fn step(&mut self) { + self.current_cycle += 1; match self.cpsr.cpu_state() { CpuState::Thumb => { let to_execute = self.decoded_thumb; @@ -705,11 +709,12 @@ impl std::fmt::Display for HalfwordTransferKind { #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; + use crate::cpu::condition::Condition; use crate::cpu::flags::{HalfwordDataTransferOffsetKind, Indexing, LoadStoreKind, Offsetting}; use crate::cpu::registers::{REG_LR, REG_PROGRAM_COUNTER, REG_SP}; use crate::cpu::thumb::instruction::ThumbModeInstruction; - use pretty_assertions::assert_eq; use super::*; diff --git a/ui/src/cpu_handler.rs b/ui/src/cpu_handler.rs index 47ab487..c53f9b1 100644 --- a/ui/src/cpu_handler.rs +++ b/ui/src/cpu_handler.rs @@ -1,20 +1,24 @@ -use emu::gba::Gba; - -use crate::ui_traits::UiTool; - use std::collections::BTreeSet; +use std::ops::{Deref, DerefMut, Range}; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; use std::thread; +use egui::text_selection::text_cursor_state::byte_index_from_char_index; +use egui::{TextBuffer, TextEdit}; + +use emu::gba::Gba; + +use crate::ui_traits::UiTool; + pub struct CpuHandler { gba: Arc>, play: Arc, thread_handle: Option>, breakpoints: Arc>>, - b_address: String, - + b_address: UpperHexString, breakpoint_combo: BreakpointType, + cycle_to_skip_custom_value: u64, } impl CpuHandler { @@ -24,8 +28,9 @@ impl CpuHandler { play: Arc::new(AtomicBool::new(false)), thread_handle: None, breakpoints: Arc::new(Mutex::new(BTreeSet::new())), - b_address: String::default(), + b_address: UpperHexString::default(), breakpoint_combo: BreakpointType::Equal, + cycle_to_skip_custom_value: 5000, } } } @@ -42,6 +47,56 @@ enum BreakpointType { Greater, } +#[derive(Default)] +struct UpperHexString(String); + +impl Deref for UpperHexString { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for UpperHexString { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl TextBuffer for UpperHexString { + fn is_mutable(&self) -> bool { + true + } + + fn as_str(&self) -> &str { + &self.0 + } + + fn insert_text(&mut self, text: &str, char_index: usize) -> usize { + let mut text_string = text.to_string(); + text_string.retain(|c| c.is_ascii_hexdigit()); + text_string.make_ascii_uppercase(); + + let byte_idx = byte_index_from_char_index(self.as_str(), char_index); + + self.insert_str(byte_idx, text_string.as_str()); + + text.chars().count() + } + + fn delete_char_range(&mut self, char_range: Range) { + assert!(char_range.start <= char_range.end); + + // Get both byte indices + let byte_start = byte_index_from_char_index(self.as_str(), char_range.start); + let byte_end = byte_index_from_char_index(self.as_str(), char_range.end); + + // Then drain all characters within this range + self.drain(byte_start..byte_end); + } +} + impl UiTool for CpuHandler { fn name(&self) -> &'static str { "Cpu Handler" @@ -118,89 +173,128 @@ impl UiTool for CpuHandler { self.play.swap(false, std::sync::atomic::Ordering::Relaxed); self.thread_handle = None; } + }); + + ui.collapsing("CPU Advanced controls", |ui| { + ui.label(format!( + "Current CPU cycle: {}", + &mut self.gba.lock().unwrap().cpu.current_cycle + )); - if ui.button("⏭x1").clicked() { - if let Ok(mut gba) = self.gba.lock() { - gba.step() + ui.horizontal(|ui| { + ui.label("Step CPU cycles:"); + + if ui.button("⏭x1").clicked() { + if let Ok(mut gba) = self.gba.lock() { + gba.step() + } } - } - if ui.button("⏭x10").clicked() { - if let Ok(mut gba) = self.gba.lock() { - (0..10).for_each(|_| gba.step()); + if ui.button("⏭x10").clicked() { + if let Ok(mut gba) = self.gba.lock() { + (0..10).for_each(|_| gba.step()); + } } - } - if ui.button("⏭x100").clicked() { - if let Ok(mut gba) = self.gba.lock() { - (0..100).for_each(|_| gba.step()); + if ui.button("⏭x100").clicked() { + if let Ok(mut gba) = self.gba.lock() { + (0..100).for_each(|_| gba.step()); + } } - } - if ui.button("⏭x500").clicked() { - if let Ok(mut gba) = self.gba.lock() { - (0..500).for_each(|_| gba.step()); + if ui.button("⏭x500").clicked() { + if let Ok(mut gba) = self.gba.lock() { + (0..500).for_each(|_| gba.step()); + } } - } - }); - ui.add_space(20.0); + if ui.button("⏭x1000").clicked() { + if let Ok(mut gba) = self.gba.lock() { + (0..1000).for_each(|_| gba.step()); + } + } + }); - ui.heading("Breakpoints"); - ui.add_space(12.0); + ui.horizontal(|ui| { + ui.label("Step (custom) CPU cycles:"); + ui.add(egui::DragValue::new(&mut self.cycle_to_skip_custom_value).speed(100)); - ui.horizontal(|ui| { - ui.label("Address (hex) : "); - ui.text_edit_singleline(&mut self.b_address); - - egui::ComboBox::from_label("Select breakpoint type") - .selected_text(if self.breakpoint_combo == BreakpointType::Equal { - "=" - } else { - ">" - }) - .show_ui(ui, |ui| { - ui.style_mut().wrap = Some(false); - ui.set_min_width(60.0); - ui.selectable_value(&mut self.breakpoint_combo, BreakpointType::Equal, "="); - ui.selectable_value(&mut self.breakpoint_combo, BreakpointType::Greater, ">"); - }); - - if ui.button("Add").clicked() { - if self.b_address.is_empty() { - return; + if ui.button("Step").clicked() { + if let Ok(mut gba) = self.gba.lock() { + (0..self.cycle_to_skip_custom_value).for_each(|_| gba.step()); + } } + }) + }); - let a = if self.b_address.starts_with("0x") { - self.b_address[2..].to_string() - } else { - self.b_address.clone() - }; + ui.collapsing("Breakpoints", |ui| { + ui.horizontal(|ui| { + egui::ComboBox::from_id_source("breakpoint-type") + .selected_text(if self.breakpoint_combo == BreakpointType::Equal { + "Equal to" + } else { + "Greater than" + }) + .show_ui(ui, |ui| { + ui.style_mut().wrap = Some(false); + ui.set_width(40.0); + ui.set_max_width(100.0); + ui.selectable_value( + &mut self.breakpoint_combo, + BreakpointType::Equal, + "Equal to", + ); + ui.selectable_value( + &mut self.breakpoint_combo, + BreakpointType::Greater, + "Greater than", + ); + }); - let address = u32::from_str_radix(&a, 16).unwrap(); - let b = Breakpoint { - address, - kind: self.breakpoint_combo, - }; + ui.label("address (HEX):"); - self.breakpoints.lock().unwrap().insert(b); + ui.add( + TextEdit::singleline(&mut self.b_address) + .desired_width(150.0) + .char_limit(16), + ); - self.b_address.clear(); - } - }); + if ui.button("Set").clicked() { + if self.b_address.is_empty() { + return; + } - ui.add_space(10.0); - egui::containers::ScrollArea::new([false, true]).show(ui, |ui| { - let breakpoints = self.breakpoints.lock().unwrap().clone(); + let a = if self.b_address.starts_with("0x") { + self.b_address[2..].to_string() + } else { + self.b_address.clone() + }; - for b in breakpoints.iter() { - ui.horizontal(|ui| { - ui.label(format!("0x{:08X}", b.address)); - if ui.button("x").clicked() { - self.breakpoints.lock().unwrap().remove(b); - } - }); - } + let address = u32::from_str_radix(&a, 16).unwrap(); + let b = Breakpoint { + address, + kind: self.breakpoint_combo, + }; + + self.breakpoints.lock().unwrap().insert(b); + + self.b_address.clear(); + } + }); + + egui::containers::ScrollArea::new([false, true]).show(ui, |ui| { + ui.label("Active breakpoints:"); + let breakpoints = self.breakpoints.lock().unwrap().clone(); + + for b in breakpoints.iter() { + ui.horizontal(|ui| { + ui.label(format!("0x{:08X}", b.address)); + if ui.button("X").clicked() { + self.breakpoints.lock().unwrap().remove(b); + } + }); + } + }); }); } }