using Mesen.Debugger.Utilities; using Mesen.Interop; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace Mesen.Debugger { public static class BreakpointManager { public static event EventHandler? BreakpointsChanged; private static List _breakpoints = new List(); private static List _temporaryBreakpoints = new List(); private static HashSet _activeCpuTypes = new HashSet(); public static ReadOnlyCollection Breakpoints { get { return _breakpoints.ToList().AsReadOnly(); } } public static List Asserts { internal get; set; } = new List(); public static List GetBreakpoints(CpuType cpuType) { List breakpoints = new List(); foreach(Breakpoint bp in _breakpoints) { if(bp.CpuType == cpuType) { breakpoints.Add(bp); } } return breakpoints; } public static void AddCpuType(CpuType cpuType) { _activeCpuTypes.Add(cpuType); SetBreakpoints(); } public static void RemoveCpuType(CpuType cpuType) { _activeCpuTypes.Remove(cpuType); SetBreakpoints(); } public static void RefreshBreakpoints(Breakpoint? bp = null) { BreakpointsChanged?.Invoke(bp, EventArgs.Empty); SetBreakpoints(); } public static void ClearBreakpoints() { _breakpoints = new(); RefreshBreakpoints(); } public static void AddBreakpoints(List breakpoints) { _breakpoints.AddRange(breakpoints); RefreshBreakpoints(); } public static void RemoveBreakpoint(Breakpoint bp) { if(_breakpoints.Remove(bp)) { DebugWorkspaceManager.AutoSave(); } RefreshBreakpoints(bp); } public static void RemoveBreakpoints(IEnumerable breakpoints) { foreach(Breakpoint bp in breakpoints) { _breakpoints.Remove(bp); } RefreshBreakpoints(null); } public static void AddBreakpoint(Breakpoint bp) { if(!_breakpoints.Contains(bp)) { _breakpoints.Add(bp); DebugWorkspaceManager.AutoSave(); } RefreshBreakpoints(bp); } public static void AddBreakpoint(AddressInfo addr, CpuType cpuType) { if(BreakpointManager.GetMatchingBreakpoint(addr, cpuType) == null) { Breakpoint bp = new Breakpoint() { StartAddress = (uint)addr.Address, EndAddress = (uint)addr.Address, MemoryType = addr.Type, CpuType = cpuType, BreakOnExec = true, BreakOnWrite = true, BreakOnRead = true }; BreakpointManager.AddBreakpoint(bp); } } public static void AddTemporaryBreakpoint(Breakpoint bp) { _temporaryBreakpoints.Add(bp); SetBreakpoints(); } public static void ClearTemporaryBreakpoints() { if(_temporaryBreakpoints.Count > 0) { _temporaryBreakpoints.Clear(); SetBreakpoints(); } } private static Breakpoint? GetMatchingBreakpoint(AddressInfo info, CpuType cpuType, Func predicate) { Breakpoint? bp = Breakpoints.Where((bp) => predicate(bp) && bp.Matches((UInt32)info.Address, info.Type, cpuType)).FirstOrDefault(); if(bp == null) { AddressInfo altAddr; if(info.Type.IsRelativeMemory()) { altAddr = DebugApi.GetAbsoluteAddress(info); } else { altAddr = DebugApi.GetRelativeAddress(info, cpuType); } if(altAddr.Address >= 0) { bp = Breakpoints.Where((bp) => predicate(bp) && bp.Matches((UInt32)altAddr.Address, altAddr.Type, cpuType)).FirstOrDefault(); } } return bp; } public static Breakpoint? GetMatchingBreakpoint(AddressInfo info, CpuType cpuType, bool ignoreRangedRwBp = false) { return GetMatchingBreakpoint(info, cpuType, (bp) => !ignoreRangedRwBp || bp.IsSingleAddress || bp.BreakOnExec); } public static Breakpoint? GetMatchingForbidBreakpoint(AddressInfo info, CpuType cpuType) { return GetMatchingBreakpoint(info, cpuType, (bp) => bp.Forbid); } public static Breakpoint? GetMatchingBreakpoint(UInt32 startAddress, UInt32 endAddress, MemoryType memoryType) { return Breakpoints.Where((bp) => bp.MemoryType == memoryType && bp.StartAddress == startAddress && bp.EndAddress == endAddress ).FirstOrDefault(); } public static bool EnableDisableBreakpoint(AddressInfo info, CpuType cpuType) { Breakpoint? breakpoint = BreakpointManager.GetMatchingBreakpoint(info, cpuType); if(breakpoint != null) { breakpoint.Enabled = !breakpoint.Enabled; DebugWorkspaceManager.AutoSave(); RefreshBreakpoints(); return true; } return false; } public static void ToggleBreakpoint(AddressInfo info, CpuType cpuType) { if(info.Address < 0) { return; } Breakpoint? breakpoint = BreakpointManager.GetMatchingForbidBreakpoint(info, cpuType) ?? BreakpointManager.GetMatchingBreakpoint(info, cpuType, true); if(breakpoint != null) { BreakpointManager.RemoveBreakpoint(breakpoint); } else { bool execBreakpoint = true; bool readWriteBreakpoint = !info.Type.IsRomMemory() || info.Type.IsRelativeMemory(); if(info.Type.SupportsCdl()) { CdlFlags cdlData = DebugApi.GetCdlData((uint)info.Address, 1, info.Type)[0]; bool isCode = cdlData.HasFlag(CdlFlags.Code); bool isData = cdlData.HasFlag(CdlFlags.Data); if(isCode || isData) { readWriteBreakpoint = !isCode; execBreakpoint = isCode; } } breakpoint = new Breakpoint() { CpuType = cpuType, Enabled = true, BreakOnExec = execBreakpoint, BreakOnRead = readWriteBreakpoint, BreakOnWrite = readWriteBreakpoint, StartAddress = (UInt32)info.Address, EndAddress = (UInt32)info.Address }; breakpoint.MemoryType = info.Type; BreakpointManager.AddBreakpoint(breakpoint); } } public static void ToggleForbidBreakpoint(AddressInfo addr, CpuType cpuType) { if(addr.Address < 0) { return; } Breakpoint? breakpoint = GetMatchingForbidBreakpoint(addr, cpuType); if(breakpoint != null) { BreakpointManager.RemoveBreakpoint(breakpoint); } else { breakpoint = new Breakpoint() { CpuType = cpuType, Enabled = true, Forbid = true, StartAddress = (UInt32)addr.Address, EndAddress = (UInt32)addr.Address }; breakpoint.MemoryType = addr.Type; BreakpointManager.AddBreakpoint(breakpoint); } } public static void SetBreakpoints() { List breakpoints = new List(); int id = 0; void toInteropBreakpoints(IEnumerable bpList) { foreach(Breakpoint bp in bpList) { if(_activeCpuTypes.Contains(bp.CpuType)) { breakpoints.Add(bp.ToInteropBreakpoint(id)); } id++; } } toInteropBreakpoints(BreakpointManager.Breakpoints); toInteropBreakpoints(BreakpointManager.Asserts); toInteropBreakpoints(BreakpointManager._temporaryBreakpoints); DebugApi.SetBreakpoints(breakpoints.ToArray(), (UInt32)breakpoints.Count); } public static Breakpoint? GetBreakpointById(int breakpointId) { if(breakpointId < 0) { return null; } else if(breakpointId < _breakpoints.Count) { return _breakpoints[breakpointId]; } else if(breakpointId < _breakpoints.Count + Asserts.Count) { return Asserts[breakpointId - _breakpoints.Count]; } else if(breakpointId < _breakpoints.Count + Asserts.Count + _temporaryBreakpoints.Count) { return _temporaryBreakpoints[breakpointId - _breakpoints.Count - Asserts.Count]; } return null; } } }