1 using Mesen.GUI.Debugger.Controls;
2 using Mesen.GUI.Forms;
3 using System;
4 using System.Collections.Generic;
5 using System.ComponentModel;
6 using System.Data;
7 using System.Drawing;
8 using System.IO;
9 using System.Linq;
10 using System.Text;
11 using System.Threading.Tasks;
12 using System.Windows.Forms;
13 
14 namespace Mesen.GUI.Debugger
15 {
16 	public partial class frmGoToAll : BaseForm
17 	{
18 		private const int MaxResultCount = 30;
19 
20 		private List<ctrlSearchResult> _results = new List<ctrlSearchResult>();
21 		private int _selectedResult = 0;
22 		private int _resultCount = 0;
23 		private Ld65DbgImporter _symbolProvider;
24 		private bool _allowOutOfScope;
25 		private bool _showFilesAndConstants;
26 
27 		public GoToDestination Destination { get; private set; }
28 
frmGoToAll(bool allowOutOfScope, bool showFilesAndConstants)29 		public frmGoToAll(bool allowOutOfScope, bool showFilesAndConstants)
30 		{
31 			InitializeComponent();
32 
33 			Icon = Properties.Resources.Find;
34 			_symbolProvider = DebugWorkspaceManager.SymbolProvider;
35 			_allowOutOfScope = allowOutOfScope;
36 			_showFilesAndConstants = showFilesAndConstants;
37 
38 			tlpResults.SuspendLayout();
39 			for(int i = 0; i < MaxResultCount; i++) {
40 				ctrlSearchResult searchResult = new ctrlSearchResult();
41 				searchResult.Dock = DockStyle.Top;
42 				searchResult.BackColor = i % 2 == 0 ? SystemColors.ControlLight : SystemColors.ControlLightLight;
43 				searchResult.Visible = false;
44 				searchResult.Click += SearchResult_Click;
45 				searchResult.DoubleClick += SearchResult_DoubleClick;
46 				tlpResults.Controls.Add(searchResult, 0, i);
47 				tlpResults.RowStyles.Add(new RowStyle(SizeType.AutoSize));
48 
49 				_results.Add(searchResult);
50 			}
51 			tlpResults.ResumeLayout();
52 
53 			UpdateResults();
54 		}
55 
SearchResult_Click(object sender, EventArgs e)56 		private void SearchResult_Click(object sender, EventArgs e)
57 		{
58 			SelectedResult = _results.IndexOf(sender as ctrlSearchResult);
59 		}
60 
SearchResult_DoubleClick(object sender, EventArgs e)61 		private void SearchResult_DoubleClick(object sender, EventArgs e)
62 		{
63 			SelectedResult = _results.IndexOf(sender as ctrlSearchResult);
64 			SelectAndClose();
65 		}
66 
ProcessCmdKey(ref Message msg, Keys keyData)67 		protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
68 		{
69 			if(keyData == Keys.Up) {
70 				SelectedResult--;
71 				return true;
72 			} else if(keyData == Keys.Down) {
73 				SelectedResult++;
74 				return true;
75 			} else if(keyData == Keys.PageUp) {
76 				SelectedResult -= pnlResults.ClientSize.Height / _results[0].Height;
77 				return true;
78 			} else if(keyData == Keys.PageDown) {
79 				SelectedResult += pnlResults.ClientSize.Height / _results[0].Height;
80 				return true;
81 			} else if(keyData == Keys.Enter) {
82 				SelectAndClose();
83 			} else if(keyData == Keys.Escape) {
84 				Close();
85 			}
86 
87 			return base.ProcessCmdKey(ref msg, keyData);
88 		}
89 
90 		private int SelectedResult
91 		{
92 			get { return _selectedResult; }
93 			set
94 			{
95 				//Reset currently highlighted element's color
96 				_results[_selectedResult].BackColor = _selectedResult % 2 == 0 ? SystemColors.ControlLight : SystemColors.ControlLightLight;
97 
98 				_selectedResult = Math.Max(0, Math.Min(_resultCount - 1, value));
99 				if(_resultCount == 0) {
100 					_results[0].BackColor = SystemColors.ControlLight;
101 				} else {
102 					_results[_selectedResult].BackColor = Color.LightBlue;
103 				}
104 
105 				if(_resultCount > 0) {
106 					if(Program.IsMono) {
107 						//Use this logic to replace ScrollControlIntoView (which doesn't work properly on Mono)
108 						int startPos = (_results[0].Height + 1) * _selectedResult;
109 						int endPos = startPos + _results[0].Height + 1;
110 
111 						int minVisiblePos = pnlResults.VerticalScroll.Value;
112 						int maxVisiblePos = pnlResults.Height + pnlResults.VerticalScroll.Value;
113 
114 						if(startPos < minVisiblePos) {
115 							pnlResults.VerticalScroll.Value = startPos;
116 						} else if(endPos > maxVisiblePos) {
117 							pnlResults.VerticalScroll.Value = endPos - pnlResults.Height;
118 						}
119 					} else {
120 						pnlResults.ScrollControlIntoView(_results[_selectedResult]);
121 					}
122 				}
123 			}
124 		}
125 
Contains(string label, List<string> searchStrings)126 		private bool Contains(string label, List<string> searchStrings)
127 		{
128 			label = label.ToLower();
129 			if(searchStrings.Count == 1) {
130 				return label.Contains(searchStrings[0]);
131 			} else {
132 				for(int i = 1; i < searchStrings.Count; i++) {
133 					if(!label.Contains(searchStrings[i])) {
134 						return false;
135 					}
136 				}
137 				return true;
138 			}
139 		}
140 
UpdateResults()141 		private void UpdateResults()
142 		{
143 			string searchString = txtSearch.Text.Trim();
144 
145 			List<string> searchStrings = new List<string>();
146 			searchStrings.Add(searchString.ToLower());
147 			searchStrings.AddRange(searchString.ToLower().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
148 			for(int i = 0; i < searchString.Length; i++) {
149 				char ch = searchString[i];
150 				if(ch >= 'A' && ch <= 'Z') {
151 					searchString = searchString.Remove(i, 1).Insert(i, " " + (char)(ch + 'a' - 'A'));
152 				}
153 			}
154 			searchStrings.AddRange(searchString.ToLower().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
155 			searchStrings = searchStrings.Distinct().ToList();
156 
157 			_resultCount = 0;
158 
159 			HashSet<int> entryPoints = new HashSet<int>(InteropEmu.DebugGetFunctionEntryPoints());
160 			byte[] cdlData = InteropEmu.DebugGetPrgCdlData();
161 
162 			List<SearchResultInfo> searchResults = new List<SearchResultInfo>();
163 			bool isEmptySearch = string.IsNullOrWhiteSpace(searchString);
164 			if(!isEmptySearch) {
165 				if(_symbolProvider != null) {
166 					if(_showFilesAndConstants) {
167 						foreach(Ld65DbgImporter.FileInfo file in _symbolProvider.Files.Values) {
168 							if(Contains(file.Name, searchStrings)) {
169 								searchResults.Add(new SearchResultInfo() {
170 									Caption = Path.GetFileName(file.Name),
171 									AbsoluteAddress = -1,
172 									MemoryType = AddressType.InternalRam,
173 									SearchResultType = SearchResultType.File,
174 									Filename = file.Name,
175 									FileLineNumber = 0,
176 									RelativeAddress = -1,
177 									CodeLabel = null
178 								});
179 							}
180 						}
181 					}
182 
183 					foreach(Ld65DbgImporter.SymbolInfo symbol in _symbolProvider.GetSymbols()) {
184 						if(Contains(symbol.Name, searchStrings)) {
185 							Ld65DbgImporter.ReferenceInfo def = _symbolProvider.GetSymbolDefinition(symbol);
186 							AddressTypeInfo addressInfo = _symbolProvider.GetSymbolAddressInfo(symbol);
187 							int value = 0;
188 							int relAddress = -1;
189 							bool isConstant = addressInfo == null;
190 							if(!_showFilesAndConstants && isConstant) {
191 								continue;
192 							}
193 
194 							if(addressInfo != null) {
195 								value = InteropEmu.DebugGetMemoryValue(addressInfo.Type.ToMemoryType(), (uint)addressInfo.Address);
196 								relAddress = InteropEmu.DebugGetRelativeAddress((uint)addressInfo.Address, addressInfo.Type);
197 							} else {
198 								//For constants, the address field contains the constant's value
199 								value = symbol.Address ?? 0;
200 							}
201 
202 							SearchResultType resultType = SearchResultType.Data;
203 							if(addressInfo?.Type == AddressType.PrgRom && entryPoints.Contains(addressInfo.Address)) {
204 								resultType = SearchResultType.Function;
205 							} else if(addressInfo?.Type == AddressType.PrgRom && addressInfo.Address < cdlData.Length && (cdlData[addressInfo.Address] & (byte)CdlPrgFlags.JumpTarget) != 0) {
206 								resultType = SearchResultType.JumpTarget;
207 							} else if(isConstant) {
208 								resultType = SearchResultType.Constant;
209 							}
210 
211 							searchResults.Add(new SearchResultInfo() {
212 								Caption = symbol.Name,
213 								AbsoluteAddress = addressInfo?.Address ?? -1,
214 								Length = _symbolProvider.GetSymbolSize(symbol),
215 								MemoryType = addressInfo?.Type ?? AddressType.InternalRam,
216 								SearchResultType = resultType,
217 								Value = value,
218 								Filename = def?.FileName ?? "",
219 								FileLineNumber = def?.LineNumber ?? 0,
220 								RelativeAddress = relAddress,
221 								CodeLabel = LabelManager.GetLabel(symbol.Name)
222 							});
223 						}
224 					}
225 				} else {
226 					foreach(CodeLabel label in LabelManager.GetLabels()) {
227 						if(Contains(label.Label, searchStrings)) {
228 							SearchResultType resultType = SearchResultType.Data;
229 							if(label.AddressType == AddressType.PrgRom && entryPoints.Contains((int)label.Address)) {
230 								resultType = SearchResultType.Function;
231 							} else if(label.AddressType == AddressType.PrgRom && label.Address < cdlData.Length && (cdlData[label.Address] & (byte)CdlPrgFlags.JumpTarget) != 0) {
232 								resultType = SearchResultType.JumpTarget;
233 							}
234 
235 							int relativeAddress = label.GetRelativeAddress();
236 
237 							searchResults.Add(new SearchResultInfo() {
238 								Caption = label.Label,
239 								AbsoluteAddress = (int)label.Address,
240 								Length = (int)label.Length,
241 								Value = label.GetValue(),
242 								MemoryType = label.AddressType,
243 								SearchResultType = resultType,
244 								Filename = "",
245 								Disabled = !_allowOutOfScope && relativeAddress < 0,
246 								RelativeAddress = relativeAddress,
247 								CodeLabel = label
248 							});
249 						}
250 					}
251 				}
252 			}
253 
254 			searchResults.Sort((SearchResultInfo a, SearchResultInfo b) => {
255 				int comparison = a.Disabled.CompareTo(b.Disabled);
256 
257 				if(comparison == 0) {
258 					bool aStartsWithSearch = a.Caption.StartsWith(searchString, StringComparison.InvariantCultureIgnoreCase);
259 					bool bStartsWithSearch = b.Caption.StartsWith(searchString, StringComparison.InvariantCultureIgnoreCase);
260 
261 					comparison = bStartsWithSearch.CompareTo(aStartsWithSearch);
262 					if(comparison == 0) {
263 						comparison = a.Caption.CompareTo(b.Caption);
264 					}
265 				}
266 				return comparison;
267 			});
268 
269 			_resultCount = Math.Min(searchResults.Count, MaxResultCount);
270 			SelectedResult = 0;
271 
272 			lblResultCount.Visible = !isEmptySearch;
273 			lblResultCount.Text = searchResults.Count.ToString() + (searchResults.Count == 1 ? " result" : " results");
274 			if(searchResults.Count > MaxResultCount) {
275 				lblResultCount.Text += " (" + MaxResultCount.ToString() + " shown)";
276 			}
277 
278 			if(searchResults.Count == 0 && !isEmptySearch) {
279 				_resultCount++;
280 				searchResults.Add(new SearchResultInfo() { Caption = "No results found.", AbsoluteAddress = -1 });
281 				pnlResults.BackColor = SystemColors.ControlLight;
282 			} else {
283 				pnlResults.BackColor = SystemColors.ControlDarkDark;
284 			}
285 
286 			if(Program.IsMono) {
287 				pnlResults.Visible = false;
288 			} else {
289 				//Suspend layout causes a crash on Mono
290 				tlpResults.SuspendLayout();
291 			}
292 
293 			for(int i = 0; i < _resultCount; i++) {
294 				_results[i].Initialize(searchResults[i]);
295 				_results[i].Tag = searchResults[i];
296 				_results[i].Visible = true;
297 			}
298 
299 			for(int i = _resultCount; i < MaxResultCount; i++) {
300 				_results[i].Visible = false;
301 			}
302 
303 			pnlResults.VerticalScroll.Value = 0;
304 			tlpResults.Height = (_results[0].Height + 1) * _resultCount;
305 
306 			pnlResults.ResumeLayout();
307 			if(Program.IsMono) {
308 				pnlResults.Visible = true;
309 				tlpResults.Width = pnlResults.ClientSize.Width - 17;
310 			} else {
311 				tlpResults.ResumeLayout();
312 				tlpResults.Width = pnlResults.ClientSize.Width - 1;
313 			}
314 		}
315 
txtSearch_TextChanged(object sender, EventArgs e)316 		private void txtSearch_TextChanged(object sender, EventArgs e)
317 		{
318 			UpdateResults();
319 		}
320 
SelectAndClose()321 		private void SelectAndClose()
322 		{
323 			if(_resultCount > 0) {
324 				SearchResultInfo searchResult = _results[_selectedResult].Tag as SearchResultInfo;
325 				if(!searchResult.Disabled) {
326 					AddressTypeInfo addressInfo = new AddressTypeInfo() { Address = searchResult.AbsoluteAddress, Type = searchResult.MemoryType };
327 					Destination = new GoToDestination() {
328 						AddressInfo = addressInfo,
329 						CpuAddress = addressInfo.Address >= 0 ? InteropEmu.DebugGetRelativeAddress((UInt32)addressInfo.Address, addressInfo.Type) : -1,
330 						Label = searchResult.CodeLabel,
331 						File = searchResult.Filename,
332 						Line = searchResult.FileLineNumber
333 					};
334 					DialogResult = DialogResult.OK;
335 					Close();
336 				}
337 			}
338 		}
339 	}
340 
GoToDestinationEventHandler(GoToDestination dest)341 	public delegate void GoToDestinationEventHandler(GoToDestination dest);
342 
343 	public class GoToDestination
344 	{
345 		public CodeLabel Label;
346 		public AddressTypeInfo AddressInfo;
347 		public int CpuAddress = -1;
348 		public string File;
349 		public int Line;
350 	}
351 }
352