using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; using BizHawk.Common.NumberExtensions; using BizHawk.Emulation.Common; using BizHawk.Client.EmuHawk.WinFormExtensions; namespace BizHawk.Client.EmuHawk { public partial class BreakpointControl : UserControl { public MainForm MainForm { get; set; } public IDebuggable Core { get; set; } public IMemoryCallbackSystem Mcs { get; set; } public GenericDebugger ParentDebugger { get; set; } public IMemoryDomains MemoryDomains { get; set; } private readonly BreakpointList _breakpoints = new BreakpointList(); public BreakpointControl() { InitializeComponent(); BreakpointView.RetrieveVirtualItem += BreakPointView_QueryItemText; BreakpointView.VirtualMode = true; _breakpoints.Callback = BreakpointCallback; } private void BreakpointControl_Load(object sender, EventArgs e) { UpdateStatsLabel(); } private void BreakPointView_QueryItemText(object sender, RetrieveVirtualItemEventArgs e) { var entry = _breakpoints[e.ItemIndex]; e.Item = new ListViewItem($"{entry.Address:X}"); e.Item.SubItems.Add($"{entry.AddressMask:X}"); e.Item.SubItems.Add(entry.Type.ToString()); e.Item.SubItems.Add(entry.Name); e.Item.BackColor = entry.ReadOnly ? SystemColors.Control : entry.Active ? Color.LightCyan : Color.White; } private void BreakpointCallback(uint addr, uint value, uint flags) { MainForm.PauseEmulator(); UpdateValues(); MainForm.AddOnScreenMessage("Breakpoint hit"); } private void SeekCallback(uint addr, uint value, uint flags) { BreakpointCallback(addr, value, flags); var seekBreakpoint = _breakpoints.FirstOrDefault(x => x.Name.StartsWith(SeekName)); if (seekBreakpoint != null) { _breakpoints.Remove(seekBreakpoint); UpdateValues(); } ParentDebugger.DisableCancelSeekBtn(); } public void UpdateValues() { if (Enabled) { CheckForNewBreakpoints(); BreakpointView.VirtualListSize = _breakpoints.Count; UpdateStatsLabel(); } } // Did any breakpoints get added from other sources such as lua? private void CheckForNewBreakpoints() { if (Mcs != null) { foreach (var callback in Mcs) { if (!_breakpoints.Any(b => b.Type == callback.Type && b.Address == callback.Address && b.AddressMask == callback.AddressMask && b.Name == callback.Name && b.Callback == callback.Callback)) { _breakpoints.Add(new Breakpoint(Core, callback)); } } } } public void GenerateUI() { if (Mcs != null) { foreach (var callback in Mcs) { _breakpoints.Add(new Breakpoint(Core, callback)); } BreakpointView.VirtualListSize = _breakpoints.Count; BreakpointView.Refresh(); UpdateBreakpointRemoveButton(); UpdateStatsLabel(); } else { Enabled = false; ParentDebugger.DisableBreakpointBox(); } } public void Shutdown() { _breakpoints.Clear(); UpdateStatsLabel(); } public void AddBreakpoint(uint address, uint mask, MemoryCallbackType type) { _breakpoints.Add(Core, MemoryDomains.SystemBus.Name, address, mask, type); BreakpointView.VirtualListSize = _breakpoints.Count; UpdateBreakpointRemoveButton(); UpdateStatsLabel(); } private void AddBreakpointButton_Click(object sender, EventArgs e) { var b = CreateAddBreakpointDialog(BreakpointOperation.Add); if (b.ShowHawkDialog() == DialogResult.OK) { _breakpoints.Add(Core, MemoryDomains.SystemBus.Name, b.Address, b.AddressMask, b.BreakType); } BreakpointView.VirtualListSize = _breakpoints.Count; UpdateBreakpointRemoveButton(); UpdateStatsLabel(); } private const string SeekName = "Seek to PC 0x"; public void AddSeekBreakpoint(uint pcVal, int pcBitSize) { var name = SeekName + pcVal.ToHexString(pcBitSize / 4); _breakpoints.Add(new Breakpoint(name, true, Core, MemoryDomains.SystemBus.Name, SeekCallback, pcVal, 0xFFFFFFFF, MemoryCallbackType.Execute)); } public void RemoveCurrentSeek() { var seekBreakpoint = _breakpoints.FirstOrDefault(x => x.Name.StartsWith(SeekName)); if (seekBreakpoint != null) { _breakpoints.Remove(seekBreakpoint); UpdateValues(); } } private IEnumerable SelectedIndices => BreakpointView.SelectedIndices.Cast(); private IEnumerable SelectedItems => SelectedIndices.Select(index => _breakpoints[index]); private IEnumerable EditableItems => SelectedItems.Where(item => !item.ReadOnly); private void RemoveBreakpointButton_Click(object sender, EventArgs e) { if (EditableItems.Any()) { var items = EditableItems.ToList(); if (items.Any()) { foreach (var item in items) { _breakpoints.Remove(item); } BreakpointView.VirtualListSize = _breakpoints.Count; UpdateBreakpointRemoveButton(); UpdateStatsLabel(); } } } private void UpdateBreakpointRemoveButton() { ToggleButton.Enabled = RemoveBreakpointButton.Enabled = EditableItems.Any(); DuplicateBreakpointButton.Enabled = EditBreakpointButton.Enabled = EditableItems.Count() == 1; } private void BreakpointView_SelectedIndexChanged(object sender, EventArgs e) { UpdateBreakpointRemoveButton(); } private void BreakpointView_ItemActivate(object sender, EventArgs e) { if (EditableItems.Any()) { var items = EditableItems.ToList(); if (items.Any()) { foreach (var item in items) { item.Active ^= true; } BreakpointView.VirtualListSize = _breakpoints.Count; UpdateBreakpointRemoveButton(); UpdateStatsLabel(); } } } private void BreakpointView_KeyDown(object sender, KeyEventArgs e) { if (!e.Control && !e.Alt && !e.Shift && e.KeyCode == Keys.Delete) // Delete { RemoveBreakpointButton_Click(null, null); } } private void UpdateStatsLabel() { BreakpointStatsLabel.Text = $"{_breakpoints.Count} Total / {_breakpoints.Count(b => b.Active)} Active"; } private void ToggleButton_Click(object sender, EventArgs e) { foreach (var item in SelectedItems) { item.Active ^= true; } BreakpointView.VirtualListSize = _breakpoints.Count; UpdateStatsLabel(); } private void DuplicateBreakpointButton_Click(object sender, EventArgs e) { var breakpoint = SelectedItems.FirstOrDefault(); if (breakpoint != null && !breakpoint.ReadOnly) { var b = CreateAddBreakpointDialog(BreakpointOperation.Duplicate, breakpoint.Type, breakpoint.Address, breakpoint.AddressMask); if (b.ShowHawkDialog() == DialogResult.OK) { _breakpoints.Add(new Breakpoint(Core, MemoryDomains.SystemBus.Name, breakpoint.Callback, b.Address, b.AddressMask, b.BreakType, breakpoint.Active)); } } BreakpointView.VirtualListSize = _breakpoints.Count; UpdateBreakpointRemoveButton(); UpdateStatsLabel(); } private void EditBreakpointButton_Click(object sender, EventArgs e) { var breakpoint = SelectedItems.FirstOrDefault(); if (breakpoint != null && !breakpoint.ReadOnly) { var b = CreateAddBreakpointDialog(BreakpointOperation.Edit, breakpoint.Type, breakpoint.Address, breakpoint.AddressMask); if (b.ShowHawkDialog() == DialogResult.OK) { breakpoint.Type = b.BreakType; breakpoint.Address = b.Address; breakpoint.AddressMask = b.AddressMask; breakpoint.ResetCallback(); } } BreakpointView.VirtualListSize = _breakpoints.Count; UpdateBreakpointRemoveButton(); UpdateStatsLabel(); } private AddBreakpointDialog CreateAddBreakpointDialog(BreakpointOperation op, MemoryCallbackType? type = null, uint? address = null, uint? mask = null) { var operation = (AddBreakpointDialog.BreakpointOperation)op; var b = new AddBreakpointDialog(operation) { MaxAddressSize = MemoryDomains.SystemBus.Size - 1 }; if (type != null) { b.BreakType = (MemoryCallbackType)type; } if (address != null) { b.Address = (uint)address; } if (mask != null) { b.AddressMask = (uint)mask; } if (!Mcs.ExecuteCallbacksAvailable) { b.DisableExecuteOption(); } return b; } private enum BreakpointOperation { Add, Edit, Duplicate } } }