1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Drawing;
5 using System.Data;
6 using System.Linq;
7 using System.Text;
8 using System.Threading.Tasks;
9 using System.Windows.Forms;
10 using Mesen.GUI.Debugger.Controls;
11 using Mesen.GUI.Config;
12 using Mesen.GUI.Controls;
13 using System.Text.RegularExpressions;
14 
15 namespace Mesen.GUI.Debugger
16 {
17 	public partial class ctrlDebuggerCode : BaseScrollableTextboxUserControl, ICodeViewer
18 	{
AssemblerEventHandler(AssemblerEventArgs args)19 		public delegate void AssemblerEventHandler(AssemblerEventArgs args);
20 		public event AssemblerEventHandler OnEditCode;
21 
22 		private UInt32? _currentActiveAddress { get; set; } = null;
23 
24 		private DebugViewInfo _config;
25 		private CodeTooltipManager _tooltipManager = null;
26 		private CodeViewerActions _codeViewerActions;
27 
ctrlDebuggerCode()28 		public ctrlDebuggerCode()
29 		{
30 			InitializeComponent();
31 			_tooltipManager = new CodeTooltipManager(this, this.ctrlCodeViewer);
32 		}
33 
OnLoad(EventArgs e)34 		protected override void OnLoad(EventArgs e)
35 		{
36 			base.OnLoad(e);
37 			if(!IsDesignMode) {
38 				_codeViewerActions = new CodeViewerActions(this, false);
39 
40 				ctrlFindOccurrences.Viewer = this;
41 				splitContainer.Panel2Collapsed = true;
42 
43 				this.SymbolProvider = DebugWorkspaceManager.SymbolProvider;
44 				DebugWorkspaceManager.SymbolProviderChanged += UpdateSymbolProvider;
45 			}
46 		}
47 
UpdateSymbolProvider(Ld65DbgImporter symbolProvider)48 		private void UpdateSymbolProvider(Ld65DbgImporter symbolProvider)
49 		{
50 			this.SymbolProvider = symbolProvider;
51 		}
52 
SetConfig(DebugViewInfo config, bool disableActions = false)53 		public void SetConfig(DebugViewInfo config, bool disableActions = false)
54 		{
55 			_config = config;
56 			if(!disableActions) {
57 				_codeViewerActions.InitMenu(config);
58 			}
59 
60 			if(this.ctrlCodeViewer.TextZoom != config.TextZoom) {
61 				this.ctrlCodeViewer.TextZoom = config.TextZoom;
62 			}
63 		}
64 
SetMessage(TextboxMessageInfo message)65 		public void SetMessage(TextboxMessageInfo message)
66 		{
67 			this.ctrlCodeViewer.SetMessage(message);
68 		}
69 
70 		protected override ctrlScrollableTextbox ScrollableTextbox
71 		{
72 			get { return this.ctrlCodeViewer; }
73 		}
74 
75 		private Ld65DbgImporter _symbolProvider;
76 		public Ld65DbgImporter SymbolProvider
77 		{
78 			get { return _symbolProvider; }
79 			set { _symbolProvider = value; }
80 		}
81 
82 		private CodeInfo _code = new CodeInfo("");
83 
84 		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
85 		public CodeInfo Code
86 		{
87 			get { return _code; }
88 			set
89 			{
90 				_code = value;
91 				_tooltipManager.Code = value;
92 				UpdateCode();
93 			}
94 		}
95 
96 		public bool ShowMemoryValues
97 		{
98 			get { return this.ctrlCodeViewer.ShowMemoryValues; }
99 			set { this.ctrlCodeViewer.ShowMemoryValues = value; }
100 		}
101 
102 		public ctrlScrollableTextbox CodeViewer { get { return this.ctrlCodeViewer; } }
103 		public CodeViewerActions CodeViewerActions { get { return _codeViewerActions; } }
104 		public uint? ActiveAddress { get { return _currentActiveAddress; } }
105 
SelectActiveAddress(UInt32 address)106 		public void SelectActiveAddress(UInt32 address)
107 		{
108 			this.SetActiveAddress(address);
109 			this.ctrlCodeViewer.ScrollToLineNumber((int)address, eHistoryType.OnScroll);
110 		}
111 
SetActiveAddress(UInt32 address)112 		public void SetActiveAddress(UInt32 address)
113 		{
114 			_currentActiveAddress = address;
115 		}
116 
ClearActiveAddress()117 		public void ClearActiveAddress()
118 		{
119 			_currentActiveAddress = null;
120 		}
121 
UpdateLineColors()122 		public void UpdateLineColors()
123 		{
124 			this.ctrlCodeViewer.StyleProvider = new LineStyleProvider(this);
125 			if(this.ctrlCodeViewer.ShowScrollbars) {
126 				this.ctrlCodeViewer.ScrollbarColorProvider = new ScrollbarColorProvider(this);
127 			}
128 		}
129 
ScrollToAddress(AddressTypeInfo addressInfo, bool scrollToTop = false)130 		public void ScrollToAddress(AddressTypeInfo addressInfo, bool scrollToTop = false)
131 		{
132 			int relativeAddress = InteropEmu.DebugGetRelativeAddress((uint)addressInfo.Address, addressInfo.Type);
133 			if(relativeAddress >= 0) {
134 				this.ctrlCodeViewer.ScrollToLineNumber(relativeAddress, eHistoryType.Always , scrollToTop);
135 			}
136 		}
137 
GetCode(out int byteLength, ref int startAddress, int endAddress = -1)138 		public List<string> GetCode(out int byteLength, ref int startAddress, int endAddress = -1)
139 		{
140 			_code.InitAssemblerValues();
141 
142 			List<string> result = new List<string>();
143 			byteLength = 0;
144 
145 			if(endAddress == -1) {
146 				//When no end address is specified, find the start of the function based on startAddress
147 				int address = InteropEmu.DebugFindSubEntryPoint((UInt16)startAddress);
148 				if(address != -1) {
149 					startAddress = address;
150 				}
151 			}
152 
153 			for(int i = startAddress; (i <= endAddress || endAddress == -1) && endAddress < 65536; ) {
154 				string code;
155 				if(_code.CodeContent.TryGetValue(i, out code)) {
156 					code = code.Split('\x2')[0].Trim();
157 
158 					if(code.StartsWith("--") || code.StartsWith("__")) {
159 						//Stop adding code when we find a new section (new function, data blocks, etc.)
160 						break;
161 					}
162 
163 					AddressTypeInfo info = new AddressTypeInfo();
164 					InteropEmu.DebugGetAbsoluteAddressAndType((UInt32)i, info);
165 					CodeLabel codeLabel = info.Address >= 0 ? LabelManager.GetLabel((UInt32)info.Address, AddressType.PrgRom) : null;
166 					string comment = codeLabel?.Comment;
167 					string label = codeLabel?.Label;
168 
169 					bool addPadding = true;
170 					if(code.StartsWith("STP*") || code.StartsWith("NOP*")) {
171 						//Transform unofficial opcodes that can't be reassembled properly into .byte statements
172 						if(comment != null) {
173 							comment.Insert(0, code + " - ");
174 						} else {
175 							comment = code;
176 						}
177 						code = ".byte " + string.Join(",", _code.CodeByteCode[i].Split(' '));
178 						addPadding = false;
179 					}
180 
181 					if(!string.IsNullOrWhiteSpace(comment) && comment.Contains("\n")) {
182 						result.AddRange(comment.Replace("\r", "").Split('\n').Select(cmt => ";" + cmt));
183 						comment = null;
184 					}
185 					if(!string.IsNullOrWhiteSpace(label)) {
186 						result.Add(label + ":");
187 					}
188 					result.Add((addPadding ? "  " : "") + code + (!string.IsNullOrWhiteSpace(comment) ? (" ;" + comment) : ""));
189 
190 					int length = _code.CodeByteCode[i].Count(c => c == ' ') + 1;
191 					byteLength += length;
192 					i += length;
193 
194 					if(endAddress == -1 && (string.Compare(code, "RTI", true) == 0 || string.Compare(code, "RTS", true) == 0)) {
195 						break;
196 					}
197 				} else {
198 					break;
199 				}
200 			}
201 
202 			result.Add("");
203 			return result;
204 		}
205 
UpdateCode()206 		private void UpdateCode()
207 		{
208 			int centerLineIndex = ctrlCodeViewer.GetLineIndexAtPosition(0) + ctrlCodeViewer.GetNumberVisibleLines() / 2;
209 			int centerLineAddress;
210 			int scrollOffset = -1;
211 			do {
212 				//Save the address at the center of the debug view
213 				centerLineAddress = ctrlCodeViewer.GetLineNumber(centerLineIndex);
214 				centerLineIndex--;
215 				scrollOffset++;
216 			} while(centerLineAddress < 0 && centerLineIndex > 0);
217 
218 
219 			ctrlCodeViewer.LineIndentations = _code.LineIndentations;
220 			ctrlCodeViewer.Addressing = _code.Addressing;
221 			ctrlCodeViewer.Comments = _code.Comments;
222 
223 			ctrlCodeViewer.LineNumbers = _code.LineNumbers;
224 			ctrlCodeViewer.TextLineNotes = _code.CodeNotes;
225 			ctrlCodeViewer.LineNumberNotes = _code.LineNumberNotes;
226 			ctrlCodeViewer.TextLines = _code.CodeLines;
227 
228 			if(centerLineAddress >= 0) {
229 				//Scroll to the same address as before, to prevent the code view from changing due to setting or banking changes, etc.
230 				int lineIndex = ctrlCodeViewer.GetLineIndex(centerLineAddress) + scrollOffset;
231 				ctrlCodeViewer.ScrollToLineIndex(lineIndex, eHistoryType.None, false, true);
232 			}
233 		}
234 
GetCurrentLineBreakpoint()235 		private Breakpoint GetCurrentLineBreakpoint()
236 		{
237 			AddressTypeInfo addressInfo = GetAddressInfo(ctrlCodeViewer.SelectedLine);
238 			if(addressInfo.Address >= 0) {
239 				int relativeAddress = InteropEmu.DebugGetRelativeAddress((uint)addressInfo.Address, addressInfo.Type);
240 				return BreakpointManager.GetMatchingBreakpoint(relativeAddress, addressInfo);
241 			}
242 			return null;
243 		}
244 
ctrlCodeViewer_MouseMove(object sender, MouseEventArgs e)245 		private void ctrlCodeViewer_MouseMove(object sender, MouseEventArgs e)
246 		{
247 			if(e.Location.X < this.ctrlCodeViewer.CodeMargin / 4) {
248 				if(this.ctrlCodeViewer.ContextMenuStrip != contextMenuMargin) {
249 					this.ctrlCodeViewer.ContextMenuStrip = contextMenuMargin;
250 					ThemeHelper.FixMonoColors(contextMenuMargin);
251 				}
252 			} else {
253 				if(this.ctrlCodeViewer.ContextMenuStrip != _codeViewerActions.contextMenu) {
254 					this.ctrlCodeViewer.ContextMenuStrip = _codeViewerActions.contextMenu;
255 					ThemeHelper.FixMonoColors(this.ctrlCodeViewer.ContextMenuStrip);
256 				}
257 			}
258 		}
259 
ProcessCmdKey(ref Message msg, Keys keyData)260 		protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
261 		{
262 			_codeViewerActions.UpdateContextMenuItemVisibility(_codeViewerActions.contextMenu.Items);
263 			return base.ProcessCmdKey(ref msg, keyData);
264 		}
265 
ctrlCodeViewer_MouseUp(object sender, MouseEventArgs e)266 		private void ctrlCodeViewer_MouseUp(object sender, MouseEventArgs e)
267 		{
268 			_codeViewerActions.ProcessMouseUp(e.Location, e.Button);
269 		}
270 
ctrlCodeViewer_MouseDown(object sender, MouseEventArgs e)271 		private void ctrlCodeViewer_MouseDown(object sender, MouseEventArgs e)
272 		{
273 			if(e.Button == MouseButtons.Left && e.Location.X < this.ctrlCodeViewer.CodeMargin / 4) {
274 				_codeViewerActions.ToggleBreakpoint(false);
275 			}
276 		}
277 
GetAddressInfo(int lineNumber)278 		public AddressTypeInfo GetAddressInfo(int lineNumber)
279 		{
280 			AddressTypeInfo info = new AddressTypeInfo();
281 			SetAddressInfo(info, lineNumber);
282 			return info;
283 		}
284 
SetAddressInfo(AddressTypeInfo info, int lineNumber)285 		private void SetAddressInfo(AddressTypeInfo info, int lineNumber)
286 		{
287 			if(lineNumber < this._code.AbsoluteLineNumbers.Length) {
288 				info.Address = this._code.AbsoluteLineNumbers[lineNumber];
289 				switch(this._code.LineMemoryType[lineNumber]) {
290 					case 'P': info.Type = AddressType.PrgRom; break;
291 					case 'W': info.Type = AddressType.WorkRam; break;
292 					case 'S': info.Type = AddressType.SaveRam; break;
293 					case 'N': info.Type = AddressType.InternalRam; break;
294 				}
295 			}
296 		}
297 
ctrlCodeViewer_MouseDoubleClick(object sender, MouseEventArgs e)298 		private void ctrlCodeViewer_MouseDoubleClick(object sender, MouseEventArgs e)
299 		{
300 			if(e.Location.X > this.ctrlCodeViewer.CodeMargin / 2 && e.Location.X < this.ctrlCodeViewer.CodeMargin) {
301 				AddressTypeInfo info = GetAddressInfo(ctrlCodeViewer.GetLineIndexAtPosition(e.Y));
302 				if(info.Address >= 0) {
303 					ctrlLabelList.EditLabel((UInt32)info.Address, info.Type);
304 				}
305 			} else{
306 				_codeViewerActions.ProcessMouseDoubleClick(e.Location);
307 			}
308 		}
309 
contextMenuMargin_Opening(object sender, CancelEventArgs e)310 		private void contextMenuMargin_Opening(object sender, CancelEventArgs e)
311 		{
312 			Breakpoint bp = GetCurrentLineBreakpoint();
313 			if(bp == null) {
314 				e.Cancel = true;
315 			} else {
316 				mnuDisableBreakpoint.Text = bp.Enabled ? "Disable breakpoint" : "Enable breakpoint";
317 			}
318 		}
319 
mnuRemoveBreakpoint_Click(object sender, EventArgs e)320 		private void mnuRemoveBreakpoint_Click(object sender, EventArgs e)
321 		{
322 			BreakpointManager.RemoveBreakpoint(GetCurrentLineBreakpoint());
323 		}
324 
mnuEditBreakpoint_Click(object sender, EventArgs e)325 		private void mnuEditBreakpoint_Click(object sender, EventArgs e)
326 		{
327 			BreakpointManager.EditBreakpoint(GetCurrentLineBreakpoint());
328 		}
329 
mnuDisableBreakpoint_Click(object sender, EventArgs e)330 		private void mnuDisableBreakpoint_Click(object sender, EventArgs e)
331 		{
332 			Breakpoint bp = GetCurrentLineBreakpoint();
333 			bp.SetEnabled(!bp.Enabled);
334 		}
335 
ctrlCodeViewer_TextZoomChanged(object sender, EventArgs e)336 		private void ctrlCodeViewer_TextZoomChanged(object sender, EventArgs e)
337 		{
338 			_config.TextZoom = this.ctrlCodeViewer.TextZoom;
339 			ConfigManager.ApplyChanges();
340 		}
341 
EditSubroutine()342 		public void EditSubroutine()
343 		{
344 			int currentLine = this.GetCurrentLine();
345 			if(currentLine != -1 && InteropEmu.DebugIsExecutionStopped()) {
346 				int byteLength;
347 				List<string> code = this.GetCode(out byteLength, ref currentLine);
348 				this.OnEditCode?.Invoke(new AssemblerEventArgs() { Code = string.Join(Environment.NewLine, code), StartAddress = (UInt16)currentLine, BlockLength = (UInt16)byteLength });
349 			}
350 		}
351 
EditSelectedCode()352 		public void EditSelectedCode()
353 		{
354 			int startAddress = this.GetCurrentLine();
355 			int endAddress = this.ctrlCodeViewer.LastSelectedLine;
356 			if(startAddress != -1 && InteropEmu.DebugIsExecutionStopped()) {
357 				int byteLength;
358 				List<string> code = this.GetCode(out byteLength, ref startAddress, endAddress);
359 				this.OnEditCode?.Invoke(new AssemblerEventArgs() { Code = string.Join(Environment.NewLine, code), StartAddress = (UInt16)startAddress, BlockLength = (UInt16)byteLength });
360 			}
361 		}
362 
EditSourceFile()363 		public void EditSourceFile()
364 		{
365 			//TODO: Not supported yet
366 		}
367 
FindAllOccurrences(Ld65DbgImporter.SymbolInfo symbol)368 		public void FindAllOccurrences(Ld65DbgImporter.SymbolInfo symbol)
369 		{
370 			FindAllOccurrences(symbol.Name, true, true);
371 		}
372 
FindAllOccurrences(string text, bool matchWholeWord, bool matchCase)373 		public void FindAllOccurrences(string text, bool matchWholeWord, bool matchCase)
374 		{
375 			List<FindAllOccurrenceResult> results = new List<FindAllOccurrenceResult>();
376 
377 			string regexPattern;
378 			if(matchWholeWord) {
379 				regexPattern = $"([^0-9a-zA-Z_#@]+|^){Regex.Escape(text)}([^0-9a-zA-Z_#@]+|$)";
380 			} else {
381 				regexPattern = Regex.Escape(text);
382 			}
383 
384 			Regex regex = new Regex(regexPattern, matchCase ? RegexOptions.None : RegexOptions.IgnoreCase);
385 
386 			for(int i = 0, len = ctrlCodeViewer.LineCount; i < len; i++) {
387 				string line = ctrlCodeViewer.GetLineContent(i);
388 				if(regex.IsMatch(line)) {
389 					if(line.StartsWith("__") && line.EndsWith("__")) {
390 						line = "Function: " + line.Substring(2, line.Length - 4);
391 					}
392 					if(line.StartsWith("--") && line.EndsWith("--")) {
393 						continue;
394 					}
395 
396 					int j = i;
397 					while(j < ctrlCodeViewer.LineCount && ctrlCodeViewer.GetLineNumber(j) < 0) {
398 						j++;
399 					}
400 
401 					int cpuAddress = ctrlCodeViewer.GetLineNumber(j);
402 					results.Add(new FindAllOccurrenceResult() {
403 						MatchedLine = line,
404 						Location = "$" + cpuAddress.ToString("X4"),
405 						Destination = new GoToDestination() {
406 							CpuAddress = cpuAddress
407 						}
408 					});
409 				}
410 			}
411 
412 			ctrlFindOccurrences.FindAllOccurrences(text, results);
413 			this.splitContainer.Panel2Collapsed = false;
414 		}
415 
ctrlFindOccurrences_OnSearchResultsClosed(object sender, EventArgs e)416 		private void ctrlFindOccurrences_OnSearchResultsClosed(object sender, EventArgs e)
417 		{
418 			this.splitContainer.Panel2Collapsed = true;
419 		}
420 
421 		public class LineStyleProvider : ctrlTextbox.ILineStyleProvider
422 		{
423 			private ctrlDebuggerCode _code;
424 
LineStyleProvider(ctrlDebuggerCode code)425 			public LineStyleProvider(ctrlDebuggerCode code)
426 			{
427 				_code = code;
428 			}
429 
GetLineComment(int lineNumber)430 			public string GetLineComment(int lineNumber)
431 			{
432 				if(_code.SymbolProvider != null && _code._config?.ShowSourceAsComments == true) {
433 					AddressTypeInfo addressInfo = _code.GetAddressInfo(lineNumber);
434 					if(addressInfo.Type == AddressType.PrgRom) {
435 						return _code.SymbolProvider.GetSourceCodeLine(addressInfo.Address);
436 					}
437 				}
438 				return null;
439 			}
440 
ConfigureActiveStatement(LineProperties props)441 			public static void ConfigureActiveStatement(LineProperties props)
442 			{
443 				props.FgColor = Color.Black;
444 				props.TextBgColor = ConfigManager.Config.DebugInfo.CodeActiveStatementColor;
445 				props.Symbol |= LineSymbol.Arrow;
446 
447 				if(ConfigManager.Config.DebugInfo.ShowInstructionProgression) {
448 					InstructionProgress state = new InstructionProgress();
449 					InteropEmu.DebugGetInstructionProgress(ref state);
450 
451 					LineProgress progress = new LineProgress();
452 					progress.Current = (int)state.OpCycle;
453 					progress.Maxixum = frmOpCodeTooltip.OpCycles[state.OpCode];
454 					switch(state.OpMemoryOperationType) {
455 						case InteropMemoryOperationType.DmcRead: progress.Color = Color.FromArgb(255, 160, 221); progress.Text = "DMC"; break;
456 						case InteropMemoryOperationType.DummyRead: progress.Color = Color.FromArgb(184, 160, 255); progress.Text = "DR"; break;
457 						case InteropMemoryOperationType.DummyWrite: progress.Color = Color.FromArgb(255, 245, 137); progress.Text = "DW"; break;
458 						case InteropMemoryOperationType.Read: progress.Color = Color.FromArgb(150, 176, 255); progress.Text = "R"; break;
459 						case InteropMemoryOperationType.Write: progress.Color = Color.FromArgb(255, 171, 150); progress.Text = "W"; break;
460 						default: progress.Color = Color.FromArgb(143, 255, 173); progress.Text = "X"; break;
461 					}
462 					props.Progress = progress;
463 				}
464 			}
465 
GetLineStyle(int cpuAddress, int lineNumber)466 			public LineProperties GetLineStyle(int cpuAddress, int lineNumber)
467 			{
468 				DebugInfo info = ConfigManager.Config.DebugInfo;
469 				LineProperties props = new LineProperties();
470 				AddressTypeInfo addressInfo = _code.GetAddressInfo(lineNumber);
471 				GetBreakpointLineProperties(props, cpuAddress, addressInfo);
472 
473 				bool isActiveStatement = _code._currentActiveAddress.HasValue && _code.ctrlCodeViewer.GetLineIndex((int)_code._currentActiveAddress.Value) == lineNumber;
474 				if(isActiveStatement) {
475 					ConfigureActiveStatement(props);
476 				} else if(_code._code.UnexecutedAddresses.Contains(lineNumber)) {
477 					props.LineBgColor = info.CodeUnexecutedCodeColor;
478 				} else if(_code._code.SpeculativeCodeAddreses.Contains(lineNumber)) {
479 					props.LineBgColor = info.CodeUnidentifiedDataColor;
480 				} else if(_code._code.VerifiedDataAddresses.Contains(lineNumber)) {
481 					props.LineBgColor = info.CodeVerifiedDataColor;
482 				}
483 
484 				switch(_code._code.LineMemoryType[lineNumber]) {
485 					case 'P': props.AddressColor = Color.Gray; break;
486 					case 'W': props.AddressColor = Color.DarkBlue; break;
487 					case 'S': props.AddressColor = Color.DarkRed; break;
488 					case 'N': props.AddressColor = Color.DarkGreen; break;
489 				}
490 
491 				return props;
492 			}
493 
GetBreakpointLineProperties(LineProperties props, int cpuAddress, AddressTypeInfo addressInfo)494 			public static void GetBreakpointLineProperties(LineProperties props, int cpuAddress, AddressTypeInfo addressInfo)
495 			{
496 				DebugInfo info = ConfigManager.Config.DebugInfo;
497 				foreach(Breakpoint breakpoint in BreakpointManager.Breakpoints) {
498 					if(breakpoint.Matches(cpuAddress, addressInfo)) {
499 						Color fgColor = Color.White;
500 						Color? bgColor = null;
501 						Color bpColor = breakpoint.BreakOnExec ? info.CodeExecBreakpointColor : (breakpoint.BreakOnWrite ? info.CodeWriteBreakpointColor : info.CodeReadBreakpointColor);
502 						Color outlineColor = bpColor;
503 						LineSymbol symbol;
504 						if(breakpoint.Enabled) {
505 							bgColor = bpColor;
506 							symbol = LineSymbol.Circle;
507 						} else {
508 							fgColor = Color.Black;
509 							symbol = LineSymbol.CircleOutline;
510 						}
511 
512 						if(breakpoint.MarkEvent) {
513 							symbol |= LineSymbol.Mark;
514 						}
515 
516 						if(!string.IsNullOrWhiteSpace(breakpoint.Condition)) {
517 							symbol |= LineSymbol.Plus;
518 						}
519 
520 						props.FgColor = fgColor;
521 						props.TextBgColor = bgColor;
522 						props.OutlineColor = outlineColor;
523 						props.Symbol = symbol;
524 						return;
525 					}
526 				}
527 			}
528 		}
529 
530 		class ScrollbarColorProvider : IScrollbarColorProvider
531 		{
532 			private Color _nesRamColor = Color.FromArgb(213, 255, 213);
533 			private Dictionary<int, Color> _breakpointColors = new Dictionary<int, Color>();
534 
535 			private ctrlDebuggerCode _code;
ScrollbarColorProvider(ctrlDebuggerCode code)536 			public ScrollbarColorProvider(ctrlDebuggerCode code)
537 			{
538 				_code = code;
539 				DebugInfo info = ConfigManager.Config.DebugInfo;
540 				int len = _code._code.AbsoluteLineNumbers.Length;
541 
542 				AddressTypeInfo addressInfo = new AddressTypeInfo();
543 
544 				Breakpoint[] breakpoints = BreakpointManager.Breakpoints.ToArray();
545 				for(int i = 0; i < len; i++) {
546 					_code.SetAddressInfo(addressInfo, i);
547 					for(int j = 0; j < breakpoints.Length; j++) {
548 						if(breakpoints[j].Matches(_code._code.LineNumbers[i], addressInfo)) {
549 							Color bpColor = breakpoints[j].BreakOnExec ? info.CodeExecBreakpointColor : (breakpoints[j].BreakOnWrite ? info.CodeWriteBreakpointColor : info.CodeReadBreakpointColor);
550 							_breakpointColors[i] = bpColor;
551 							break;
552 						}
553 					}
554 				}
555 			}
556 
GetBackgroundColor(float position)557 			public Color GetBackgroundColor(float position)
558 			{
559 				DebugInfo info = ConfigManager.Config.DebugInfo;
560 				int lineIndex = (int)((_code._code.LineMemoryType.Length - 1) * position);
561 
562 				if(lineIndex < _code._code.LineMemoryType.Length) {
563 					switch(_code._code.LineMemoryType[lineIndex]) {
564 						case 'N': return _nesRamColor;
565 						case 'P':
566 							if(_code._code.UnexecutedAddresses.Contains(lineIndex)) {
567 								return info.CodeUnexecutedCodeColor;
568 							} else if(_code._code.VerifiedDataAddresses.Contains(lineIndex)) {
569 								return info.CodeVerifiedDataColor;
570 							} else if(_code._code.SpeculativeCodeAddreses.Contains(lineIndex)) {
571 								return info.CodeUnidentifiedDataColor;
572 							}
573 							return Color.White;
574 
575 						case 'W': return Color.Lavender;
576 						case 'S': return Color.MistyRose;
577 					}
578 				}
579 				return Color.Transparent;
580 			}
581 
GetPreview(int lineIndex)582 			public frmCodePreviewTooltip GetPreview(int lineIndex)
583 			{
584 				if(lineIndex < _code._code.LineNumbers.Length) {
585 					while(lineIndex > 0 && _code._code.LineNumbers[lineIndex] < 0) {
586 						lineIndex--;
587 					}
588 					return new frmCodePreviewTooltip(_code.FindForm(), lineIndex, _code._code);
589 				} else {
590 					return null;
591 				}
592 			}
593 
GetActiveLine()594 			public int GetActiveLine()
595 			{
596 				if(_code._currentActiveAddress.HasValue) {
597 					return _code.ctrlCodeViewer.GetLineIndex((int)_code._currentActiveAddress.Value);
598 				} else {
599 					return -1;
600 				}
601 			}
602 
GetSelectedLine()603 			public int GetSelectedLine()
604 			{
605 				return _code.ctrlCodeViewer.SelectedLine;
606 			}
607 
GetMarkerColor(float position, int linesPerPixel)608 			public Color GetMarkerColor(float position, int linesPerPixel)
609 			{
610 				int lineIndex = (int)((_code._code.LineMemoryType.Length - 1) * position);
611 
612 				Color bpColor;
613 				for(int i = 0; i < linesPerPixel; i++) {
614 					if(_breakpointColors.TryGetValue(lineIndex + i, out bpColor)) {
615 						return bpColor;
616 					}
617 				}
618 				return Color.Transparent;
619 			}
620 		}
621 	}
622 
623 	public class AssemblerEventArgs : EventArgs
624 	{
625 		public string Code { get; set; }
626 		public UInt16 StartAddress { get; set; }
627 		public UInt16 BlockLength { get; set; }
628 	}
629 }
630