using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Mesen.GUI.Debugger.Controls; using Mesen.GUI.Config; using Mesen.GUI.Controls; namespace Mesen.GUI.Debugger { public partial class ctrlDebuggerCode : BaseScrollableTextboxUserControl, ICodeViewer { public delegate void AssemblerEventHandler(AssemblerEventArgs args); public event AssemblerEventHandler OnEditCode; private UInt32? _currentActiveAddress { get; set; } = null; private DebugViewInfo _config; private CodeTooltipManager _tooltipManager = null; private CodeViewerActions _codeViewerActions; public ctrlDebuggerCode() { InitializeComponent(); _tooltipManager = new CodeTooltipManager(this, this.ctrlCodeViewer); } protected override void OnLoad(EventArgs e) { base.OnLoad(e); if(!IsDesignMode) { _codeViewerActions = new CodeViewerActions(this, false); ctrlFindOccurrences.Viewer = this; splitContainer.Panel2Collapsed = true; } } public void SetConfig(DebugViewInfo config, bool disableActions = false) { _config = config; if(!disableActions) { _codeViewerActions.InitMenu(config); } if(this.ctrlCodeViewer.TextZoom != config.TextZoom) { this.ctrlCodeViewer.TextZoom = config.TextZoom; } } protected override ctrlScrollableTextbox ScrollableTextbox { get { return this.ctrlCodeViewer; } } private Ld65DbgImporter _symbolProvider; public Ld65DbgImporter SymbolProvider { get { return _symbolProvider; } set { _symbolProvider = value; } } private CodeInfo _code = new CodeInfo(""); [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public CodeInfo Code { get { return _code; } set { _code = value; _tooltipManager.Code = value; UpdateCode(); } } public bool ShowMemoryValues { get { return this.ctrlCodeViewer.ShowMemoryValues; } set { this.ctrlCodeViewer.ShowMemoryValues = value; } } public ctrlScrollableTextbox CodeViewer { get { return this.ctrlCodeViewer; } } public CodeViewerActions CodeViewerActions { get { return _codeViewerActions; } } public uint? ActiveAddress { get { return _currentActiveAddress; } } public void SelectActiveAddress(UInt32 address) { this.SetActiveAddress(address); this.ctrlCodeViewer.ScrollToLineNumber((int)address, eHistoryType.OnScroll); } public void SetActiveAddress(UInt32 address) { _currentActiveAddress = address; } public void ClearActiveAddress() { _currentActiveAddress = null; } public void UpdateLineColors() { this.ctrlCodeViewer.StyleProvider = new LineStyleProvider(this); if(this.ctrlCodeViewer.ShowScrollbars) { this.ctrlCodeViewer.ScrollbarColorProvider = new ScrollbarColorProvider(this); } } public void ScrollToAddress(AddressTypeInfo addressInfo, bool scrollToTop = false) { int relativeAddress = InteropEmu.DebugGetRelativeAddress((uint)addressInfo.Address, addressInfo.Type); if(relativeAddress >= 0) { this.ctrlCodeViewer.ScrollToLineNumber(relativeAddress, eHistoryType.Always , scrollToTop); } } public List GetCode(out int byteLength, ref int startAddress, int endAddress = -1) { _code.InitAssemblerValues(); List result = new List(); byteLength = 0; if(endAddress == -1) { //When no end address is specified, find the start of the function based on startAddress int address = InteropEmu.DebugFindSubEntryPoint((UInt16)startAddress); if(address != -1) { startAddress = address; } } for(int i = startAddress; (i <= endAddress || endAddress == -1) && endAddress < 65536; ) { string code; if(_code.CodeContent.TryGetValue(i, out code)) { code = code.Split('\x2')[0].Trim(); if(code.StartsWith("--") || code.StartsWith("__")) { //Stop adding code when we find a new section (new function, data blocks, etc.) break; } AddressTypeInfo info = new AddressTypeInfo(); InteropEmu.DebugGetAbsoluteAddressAndType((UInt32)i, info); CodeLabel codeLabel = info.Address >= 0 ? LabelManager.GetLabel((UInt32)info.Address, AddressType.PrgRom) : null; string comment = codeLabel?.Comment; string label = codeLabel?.Label; bool addPadding = true; if(code.StartsWith("STP*") || code.StartsWith("NOP*")) { //Transform unofficial opcodes that can't be reassembled properly into .byte statements if(comment != null) { comment.Insert(0, code + " - "); } else { comment = code; } code = ".byte " + string.Join(",", _code.CodeByteCode[i].Split(' ')); addPadding = false; } if(!string.IsNullOrWhiteSpace(comment) && comment.Contains("\n")) { result.AddRange(comment.Replace("\r", "").Split('\n').Select(cmt => ";" + cmt)); comment = null; } if(!string.IsNullOrWhiteSpace(label)) { result.Add(label + ":"); } result.Add((addPadding ? " " : "") + code + (!string.IsNullOrWhiteSpace(comment) ? (" ;" + comment) : "")); int length = _code.CodeByteCode[i].Count(c => c == ' ') + 1; byteLength += length; i += length; if(endAddress == -1 && (string.Compare(code, "RTI", true) == 0 || string.Compare(code, "RTS", true) == 0)) { break; } } else { break; } } result.Add(""); return result; } private void UpdateCode() { int centerLineIndex = ctrlCodeViewer.GetLineIndexAtPosition(0) + ctrlCodeViewer.GetNumberVisibleLines() / 2; int centerLineAddress; int scrollOffset = -1; do { //Save the address at the center of the debug view centerLineAddress = ctrlCodeViewer.GetLineNumber(centerLineIndex); centerLineIndex--; scrollOffset++; } while(centerLineAddress < 0 && centerLineIndex > 0); ctrlCodeViewer.LineIndentations = _code.LineIndentations; ctrlCodeViewer.Addressing = _code.Addressing; ctrlCodeViewer.Comments = _code.Comments; ctrlCodeViewer.LineNumbers = _code.LineNumbers; ctrlCodeViewer.TextLineNotes = _code.CodeNotes; ctrlCodeViewer.LineNumberNotes = _code.LineNumberNotes; ctrlCodeViewer.TextLines = _code.CodeLines; if(centerLineAddress >= 0) { //Scroll to the same address as before, to prevent the code view from changing due to setting or banking changes, etc. int lineIndex = ctrlCodeViewer.GetLineIndex(centerLineAddress) + scrollOffset; ctrlCodeViewer.ScrollToLineIndex(lineIndex, eHistoryType.None, false, true); } } private void ctrlCodeViewer_MouseLeave(object sender, EventArgs e) { _tooltipManager.Close(); } private Breakpoint GetCurrentLineBreakpoint() { AddressTypeInfo addressInfo = GetAddressInfo(ctrlCodeViewer.SelectedLine); if(addressInfo.Address >= 0) { int relativeAddress = InteropEmu.DebugGetRelativeAddress((uint)addressInfo.Address, addressInfo.Type); return BreakpointManager.GetMatchingBreakpoint(relativeAddress, addressInfo); } return null; } private void ctrlCodeViewer_MouseMove(object sender, MouseEventArgs e) { if(e.Location.X < this.ctrlCodeViewer.CodeMargin / 4) { this.ctrlCodeViewer.ContextMenuStrip = contextMenuMargin; } else { this.ctrlCodeViewer.ContextMenuStrip = _codeViewerActions.contextMenu; } _tooltipManager.ProcessMouseMove(e.Location); } protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { _codeViewerActions.UpdateContextMenuItemVisibility(); return base.ProcessCmdKey(ref msg, keyData); } private void ctrlCodeViewer_MouseUp(object sender, MouseEventArgs e) { _codeViewerActions.ProcessMouseUp(e.Location, e.Button); } private void ctrlCodeViewer_MouseDown(object sender, MouseEventArgs e) { _tooltipManager.Close(); if(e.Button == MouseButtons.Left && e.Location.X < this.ctrlCodeViewer.CodeMargin / 4) { int relativeAddress = ctrlCodeViewer.GetLineNumberAtPosition(e.Y); AddressTypeInfo info = GetAddressInfo(ctrlCodeViewer.GetLineIndexAtPosition(e.Y)); BreakpointManager.ToggleBreakpoint(relativeAddress, info, false); } } public AddressTypeInfo GetAddressInfo(int lineNumber) { AddressTypeInfo info = new AddressTypeInfo(); SetAddressInfo(info, lineNumber); return info; } private void SetAddressInfo(AddressTypeInfo info, int lineNumber) { if(lineNumber < this._code.AbsoluteLineNumbers.Length) { info.Address = this._code.AbsoluteLineNumbers[lineNumber]; switch(this._code.LineMemoryType[lineNumber]) { case 'P': info.Type = AddressType.PrgRom; break; case 'W': info.Type = AddressType.WorkRam; break; case 'S': info.Type = AddressType.SaveRam; break; case 'N': info.Type = AddressType.InternalRam; break; } } } private void ctrlCodeViewer_ScrollPositionChanged(object sender, EventArgs e) { _tooltipManager?.Close(); } private void ctrlCodeViewer_MouseDoubleClick(object sender, MouseEventArgs e) { if(e.Location.X > this.ctrlCodeViewer.CodeMargin / 2 && e.Location.X < this.ctrlCodeViewer.CodeMargin) { AddressTypeInfo info = GetAddressInfo(ctrlCodeViewer.GetLineIndexAtPosition(e.Y)); if(info.Address >= 0) { ctrlLabelList.EditLabel((UInt32)info.Address, info.Type); } } else{ _codeViewerActions.ProcessMouseDoubleClick(e.Location); } } private void contextMenuMargin_Opening(object sender, CancelEventArgs e) { Breakpoint bp = GetCurrentLineBreakpoint(); if(bp == null) { e.Cancel = true; } else { mnuDisableBreakpoint.Text = bp.Enabled ? "Disable breakpoint" : "Enable breakpoint"; } } private void mnuRemoveBreakpoint_Click(object sender, EventArgs e) { BreakpointManager.RemoveBreakpoint(GetCurrentLineBreakpoint()); } private void mnuEditBreakpoint_Click(object sender, EventArgs e) { BreakpointManager.EditBreakpoint(GetCurrentLineBreakpoint()); } private void mnuDisableBreakpoint_Click(object sender, EventArgs e) { Breakpoint bp = GetCurrentLineBreakpoint(); bp.SetEnabled(!bp.Enabled); } private void ctrlCodeViewer_TextZoomChanged(object sender, EventArgs e) { _config.TextZoom = this.ctrlCodeViewer.TextZoom; ConfigManager.ApplyChanges(); } public void EditSubroutine() { int currentLine = this.GetCurrentLine(); if(currentLine != -1 && InteropEmu.DebugIsExecutionStopped()) { int byteLength; List code = this.GetCode(out byteLength, ref currentLine); this.OnEditCode?.Invoke(new AssemblerEventArgs() { Code = string.Join(Environment.NewLine, code), StartAddress = (UInt16)currentLine, BlockLength = (UInt16)byteLength }); } } public void EditSelectedCode() { int startAddress = this.GetCurrentLine(); int endAddress = this.ctrlCodeViewer.LastSelectedLine; if(startAddress != -1 && InteropEmu.DebugIsExecutionStopped()) { int byteLength; List code = this.GetCode(out byteLength, ref startAddress, endAddress); this.OnEditCode?.Invoke(new AssemblerEventArgs() { Code = string.Join(Environment.NewLine, code), StartAddress = (UInt16)startAddress, BlockLength = (UInt16)byteLength }); } } public void FindAllOccurrences(string text, bool matchWholeWord, bool matchCase) { ctrlFindOccurrences.FindAllOccurrences(text, matchWholeWord, matchCase); this.splitContainer.Panel2Collapsed = false; } private void ctrlFindOccurrences_OnSearchResultsClosed(object sender, EventArgs e) { this.splitContainer.Panel2Collapsed = true; } public class LineStyleProvider : ctrlTextbox.ILineStyleProvider { private ctrlDebuggerCode _code; public LineStyleProvider(ctrlDebuggerCode code) { _code = code; } public string GetLineComment(int lineNumber) { if(_code.SymbolProvider != null && _code._config?.ShowSourceAsComments == true) { AddressTypeInfo addressInfo = _code.GetAddressInfo(lineNumber); if(addressInfo.Type == AddressType.PrgRom) { return _code.SymbolProvider.GetSourceCodeLine(addressInfo.Address); } } return null; } public LineProperties GetLineStyle(int cpuAddress, int lineNumber) { DebugInfo info = ConfigManager.Config.DebugInfo; LineProperties props = new LineProperties(); AddressTypeInfo addressInfo = _code.GetAddressInfo(lineNumber); GetBreakpointLineProperties(props, cpuAddress, addressInfo); bool isActiveStatement = _code._currentActiveAddress.HasValue && _code.ctrlCodeViewer.GetLineIndex((int)_code._currentActiveAddress.Value) == lineNumber; if(isActiveStatement) { props.FgColor = Color.Black; props.TextBgColor = info.CodeActiveStatementColor; props.Symbol |= LineSymbol.Arrow; } else if(_code._code.UnexecutedAddresses.Contains(lineNumber)) { props.LineBgColor = info.CodeUnexecutedCodeColor; } else if(_code._code.SpeculativeCodeAddreses.Contains(lineNumber)) { props.LineBgColor = info.CodeUnidentifiedDataColor; } else if(_code._code.VerifiedDataAddresses.Contains(lineNumber)) { props.LineBgColor = info.CodeVerifiedDataColor; } switch(_code._code.LineMemoryType[lineNumber]) { case 'P': props.AddressColor = Color.Gray; break; case 'W': props.AddressColor = Color.DarkBlue; break; case 'S': props.AddressColor = Color.DarkRed; break; case 'N': props.AddressColor = Color.DarkGreen; break; } return props; } public static void GetBreakpointLineProperties(LineProperties props, int cpuAddress, AddressTypeInfo addressInfo) { DebugInfo info = ConfigManager.Config.DebugInfo; foreach(Breakpoint breakpoint in BreakpointManager.Breakpoints) { if(breakpoint.Matches(cpuAddress, addressInfo)) { Color fgColor = Color.White; Color? bgColor = null; Color bpColor = breakpoint.BreakOnExec ? info.CodeExecBreakpointColor : (breakpoint.BreakOnWrite ? info.CodeWriteBreakpointColor : info.CodeReadBreakpointColor); Color outlineColor = bpColor; LineSymbol symbol; if(breakpoint.Enabled) { bgColor = bpColor; symbol = LineSymbol.Circle; } else { fgColor = Color.Black; symbol = LineSymbol.CircleOutline; } if(breakpoint.MarkEvent) { symbol |= LineSymbol.Mark; } if(!string.IsNullOrWhiteSpace(breakpoint.Condition)) { symbol |= LineSymbol.Plus; } props.FgColor = fgColor; props.TextBgColor = bgColor; props.OutlineColor = outlineColor; props.Symbol = symbol; return; } } } } class ScrollbarColorProvider : IScrollbarColorProvider { private Color _nesRamColor = Color.FromArgb(213, 255, 213); private Dictionary _breakpointColors = new Dictionary(); private ctrlDebuggerCode _code; public ScrollbarColorProvider(ctrlDebuggerCode code) { _code = code; DebugInfo info = ConfigManager.Config.DebugInfo; int len = _code._code.AbsoluteLineNumbers.Length; AddressTypeInfo addressInfo = new AddressTypeInfo(); Breakpoint[] breakpoints = BreakpointManager.Breakpoints.ToArray(); for(int i = 0; i < len; i++) { _code.SetAddressInfo(addressInfo, i); for(int j = 0; j < breakpoints.Length; j++) { if(breakpoints[j].Matches(_code._code.LineNumbers[i], addressInfo)) { Color bpColor = breakpoints[j].BreakOnExec ? info.CodeExecBreakpointColor : (breakpoints[j].BreakOnWrite ? info.CodeWriteBreakpointColor : info.CodeReadBreakpointColor); _breakpointColors[i] = bpColor; break; } } } } public Color GetBackgroundColor(float position) { DebugInfo info = ConfigManager.Config.DebugInfo; int lineIndex = (int)((_code._code.LineMemoryType.Length - 1) * position); if(lineIndex < _code._code.LineMemoryType.Length) { switch(_code._code.LineMemoryType[lineIndex]) { case 'N': return _nesRamColor; case 'P': if(_code._code.UnexecutedAddresses.Contains(lineIndex)) { return info.CodeUnexecutedCodeColor; } else if(_code._code.VerifiedDataAddresses.Contains(lineIndex)) { return info.CodeVerifiedDataColor; } else if(_code._code.SpeculativeCodeAddreses.Contains(lineIndex)) { return info.CodeUnidentifiedDataColor; } return Color.White; case 'W': return Color.Lavender; case 'S': return Color.MistyRose; } } return Color.Transparent; } public frmCodePreviewTooltip GetPreview(int lineIndex) { if(lineIndex < _code._code.LineNumbers.Length) { while(lineIndex > 0 && _code._code.LineNumbers[lineIndex] < 0) { lineIndex--; } return new frmCodePreviewTooltip(_code.FindForm(), lineIndex, _code._code); } else { return null; } } public int GetActiveLine() { if(_code._currentActiveAddress.HasValue) { return _code.ctrlCodeViewer.GetLineIndex((int)_code._currentActiveAddress.Value); } else { return -1; } } public int GetSelectedLine() { return _code.ctrlCodeViewer.SelectedLine; } public Color GetMarkerColor(float position, int linesPerPixel) { int lineIndex = (int)((_code._code.LineMemoryType.Length - 1) * position); Color bpColor; for(int i = 0; i < linesPerPixel; i++) { if(_breakpointColors.TryGetValue(lineIndex + i, out bpColor)) { return bpColor; } } return Color.Transparent; } } } public class AssemblerEventArgs : EventArgs { public string Code { get; set; } public UInt16 StartAddress { get; set; } public UInt16 BlockLength { get; set; } } }