using Mesen.Debugger; using Mesen.Interop; using Mesen.ViewModels; using Mesen.Windows; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Mesen.Debugger.Labels { public class LabelManager { public static Regex LabelRegex { get; } = new Regex("^[@_a-zA-Z]+[@_a-zA-Z0-9]*$", RegexOptions.Compiled); public static Regex InvalidLabelRegex { get; } = new Regex("[^@_a-zA-Z0-9]", RegexOptions.Compiled); public static Regex AssertRegex { get; } = new Regex(@"assert\((.*)\)", RegexOptions.Compiled); private static Dictionary _labelsByKey = new Dictionary(); private static HashSet _labels = new HashSet(); private static Dictionary _reverseLookup = new Dictionary(); public static event EventHandler? OnLabelUpdated; private static int _suspendEvents = 0; public static void SuspendEvents() { Interlocked.Increment(ref _suspendEvents); } public static void ResumeEvents() { Interlocked.Decrement(ref _suspendEvents); ProcessLabelUpdate(); } public static void ResetLabels() { DebugApi.ClearLabels(); _labels.Clear(); _labelsByKey.Clear(); _reverseLookup.Clear(); ProcessLabelUpdate(); } public static CodeLabel? GetLabel(UInt32 address, MemoryType type) { CodeLabel? label; _labelsByKey.TryGetValue(GetKey(address, type), out label); return label; } public static CodeLabel? GetLabel(AddressInfo addr) { CodeLabel? label = GetLabel((UInt32)addr.Address, addr.Type); if(label != null) { return label; } if(addr.Type.IsRelativeMemory()) { AddressInfo absAddress = DebugApi.GetAbsoluteAddress(addr); if(absAddress.Address >= 0) { return GetLabel((UInt32)absAddress.Address, absAddress.Type); } } return null; } public static CodeLabel? GetLabel(string label) { return _reverseLookup.ContainsKey(label) ? _reverseLookup[label] : null; } public static bool ContainsLabel(CodeLabel label) { return _labels.Contains(label); } public static void SetLabels(IEnumerable labels, bool raiseEvents = true) { Dictionary isAvailable = new(); foreach(CodeLabel label in labels) { //Check if label memory type is valid before adding it to the list if(!isAvailable.TryGetValue(label.MemoryType, out bool available)) { available = DebugApi.GetMemorySize(label.MemoryType) > 0; isAvailable[label.MemoryType] = available; } if(available) { SetLabel(label, false); } } if(raiseEvents) { ProcessLabelUpdate(); } } public static List GetLabels(CpuType cpu) { return _labels.Where((lbl) => lbl.Matches(cpu)).ToList(); } public static List GetAllLabels() { return _labels.ToList(); } private static UInt64 GetKey(UInt32 address, MemoryType memType) { return address | ((UInt64)memType << 32); } public static void SetLabel(uint address, MemoryType memType, string label, string comment) { LabelManager.SetLabel(new CodeLabel() { Address = address, MemoryType = memType, Label = label, Comment = comment }, false); } public static bool SetLabel(CodeLabel label, bool raiseEvent) { if(_reverseLookup.ContainsKey(label.Label)) { //Another identical label exists, we need to remove it DeleteLabel(_reverseLookup[label.Label], false); } string comment = label.Comment; for(UInt32 i = label.Address; i < label.Address + label.Length; i++) { UInt64 key = GetKey(i, label.MemoryType); CodeLabel? existingLabel; if(_labelsByKey.TryGetValue(key, out existingLabel)) { DeleteLabel(existingLabel, false); _reverseLookup.Remove(existingLabel.Label); } _labelsByKey[key] = label; if(label.Length == 1) { DebugApi.SetLabel(i, label.MemoryType, label.Label, comment.Replace(Environment.NewLine, "\n")); } else { DebugApi.SetLabel(i, label.MemoryType, label.Label + "+" + (i - label.Address).ToString(), comment.Replace(Environment.NewLine, "\n")); //Only set the comment on the first byte of multi-byte comments comment = ""; } } _labels.Add(label); if(label.Label.Length > 0) { _reverseLookup[label.Label] = label; } if(raiseEvent) { ProcessLabelUpdate(); } return true; } public static void DeleteLabel(CodeLabel label, bool raiseEvent) { bool needEvent = false; _labels.Remove(label); for(UInt32 i = label.Address; i < label.Address + label.Length; i++) { UInt64 key = GetKey(i, label.MemoryType); if(_labelsByKey.ContainsKey(key)) { _reverseLookup.Remove(_labelsByKey[key].Label); } if(_labelsByKey.Remove(key)) { DebugApi.SetLabel(i, label.MemoryType, string.Empty, string.Empty); if(raiseEvent) { needEvent = true; } } } if(needEvent) { ProcessLabelUpdate(); } } public static void DeleteLabels(IEnumerable labels) { foreach(CodeLabel label in labels) { DeleteLabel(label, false); } ProcessLabelUpdate(); } public static void RefreshLabels(bool raiseEvent) { DebugApi.ClearLabels(); LabelManager.SetLabels(new List(_labels), raiseEvent); } private static void ProcessLabelUpdate() { if(_suspendEvents == 0) { OnLabelUpdated?.Invoke(null, EventArgs.Empty); UpdateAssertBreakpoints(); } } private static void UpdateAssertBreakpoints() { List asserts = new List(); Action addAssert = (CodeLabel label, string condition, CpuType cpuType) => { asserts.Add(new Breakpoint() { BreakOnExec = true, MemoryType = label.MemoryType, CpuType = cpuType, StartAddress = label.Address, EndAddress = label.Address, Condition = "!(" + condition + ")", IsAssert = true }); }; foreach(CodeLabel label in _labels) { foreach(string commentLine in label.Comment.Split('\n')) { Match m = LabelManager.AssertRegex.Match(commentLine); if(m.Success) { foreach(CpuType cpuType in MainWindowViewModel.Instance.RomInfo.CpuTypes) { if(cpuType.CanAccessMemoryType(label.MemoryType)) { addAssert(label, m.Groups[1].Value, cpuType); } } } } } BreakpointManager.Asserts = asserts; BreakpointManager.SetBreakpoints(); } } [Flags] public enum CodeLabelFlags { None = 0, AutoJumpLabel = 1 } }