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 System.Runtime.InteropServices;
11 using Mesen.GUI.Config;
12 using Mesen.GUI.Controls;
13 using Mesen.GUI.Forms;
14 using System.Drawing.Drawing2D;
15 using System.Drawing.Imaging;
16 
17 namespace Mesen.GUI.Debugger.Controls
18 {
19 	public partial class ctrlNametableViewer : BaseControl, ICompactControl
20 	{
OnSelectChrTileHandler(int tileIndex, int paletteIndex)21 		public delegate void OnSelectChrTileHandler(int tileIndex, int paletteIndex);
22 		public event OnSelectChrTileHandler OnSelectChrTile;
23 
24 		private byte[][] _nametablePixelData = new byte[4][];
25 
26 		private byte[][] _prevTileData = new byte[4][];
27 		private byte[][] _prevAttributeData = new byte[4][];
28 
29 		private byte[][] _tileData = new byte[4][];
30 		private byte[][] _attributeData = new byte[4][];
31 		private Bitmap _gridOverlay;
32 		private Bitmap _nametableImage = new Bitmap(512, 480, PixelFormat.Format32bppPArgb);
33 		private Bitmap _finalImage = new Bitmap(512, 480, PixelFormat.Format32bppPArgb);
34 		private Bitmap _hudImage = new Bitmap(512, 480, PixelFormat.Format32bppPArgb);
35 		private TileInfo _tileInfo;
36 		private int _currentPpuAddress = -1;
37 		private int _tileX = 0;
38 		private int _tileY = 0;
39 		private int _xScroll = 0;
40 		private int _yScroll = 0;
41 		private int _nametableIndex = 0;
42 		private ctrlChrViewer _chrViewer;
43 		private DebugState _state = new DebugState();
44 		private HdPackCopyHelper _hdCopyHelper = new HdPackCopyHelper();
45 		private bool _firstDraw = true;
46 		private bool[] _ntChanged = null;
47 		private bool _showAttributeColorsOnly = false;
48 
ctrlNametableViewer()49 		public ctrlNametableViewer()
50 		{
51 			InitializeComponent();
52 
53 			bool designMode = (LicenseManager.UsageMode == LicenseUsageMode.Designtime);
54 			if(!designMode) {
55 				chkShowPpuScrollOverlay.Checked = ConfigManager.Config.DebugInfo.ShowPpuScrollOverlay;
56 				chkShowTileGrid.Checked = ConfigManager.Config.DebugInfo.ShowTileGrid;
57 				chkShowAttributeGrid.Checked = ConfigManager.Config.DebugInfo.ShowAttributeGrid;
58 				chkHighlightChrTile.Checked = ConfigManager.Config.DebugInfo.HighlightChrTile;
59 				chkUseGrayscalePalette.Checked = ConfigManager.Config.DebugInfo.NtViewerUseGrayscalePalette;
60 				chkHighlightTileUpdates.Checked = ConfigManager.Config.DebugInfo.NtViewerHighlightTileUpdates;
61 				chkHighlightAttributeUpdates.Checked = ConfigManager.Config.DebugInfo.NtViewerHighlightAttributeUpdates;
62 				chkIgnoreRedundantWrites.Checked = ConfigManager.Config.DebugInfo.NtViewerIgnoreRedundantWrites;
63 
64 				chkShowAttributeColorsOnly.Checked = ConfigManager.Config.DebugInfo.ShowAttributeColorsOnly;
65 				_showAttributeColorsOnly = ConfigManager.Config.DebugInfo.ShowAttributeColorsOnly;
66 				chkUseGrayscalePalette.Enabled = !_showAttributeColorsOnly;
67 
68 				UpdateIgnoreWriteCheckbox();
69 			}
70 		}
71 
GetCompactSize(bool includeMargins)72 		public Size GetCompactSize(bool includeMargins)
73 		{
74 			return new Size(picNametable.Width, picNametable.Height);
75 		}
76 
ScaleImage(double scale)77 		public void ScaleImage(double scale)
78 		{
79 			picNametable.Size = new Size((int)(picNametable.Width * scale), (int)(picNametable.Height * scale));
80 			picNametable.InterpolationMode = scale > 1 ? InterpolationMode.NearestNeighbor : InterpolationMode.Default;
81 		}
82 
ProcessCmdKey(ref Message msg, Keys keyData)83 		protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
84 		{
85 			if(ctxMenu.ProcessCommandKey(ref msg, keyData)) {
86 				return true;
87 			}
88 			return base.ProcessCmdKey(ref msg, keyData);
89 		}
90 
OnLoad(EventArgs e)91 		protected override void OnLoad(EventArgs e)
92 		{
93 			base.OnLoad(e);
94 			if(!IsDesignMode) {
95 				mnuCopyToClipboard.InitShortcut(this, nameof(DebuggerShortcutsConfig.Copy));
96 				mnuEditInMemoryViewer.InitShortcut(this, nameof(DebuggerShortcutsConfig.CodeWindow_EditInMemoryViewer));
97 				mnuAddBreakpoint.InitShortcut(this, nameof(DebuggerShortcutsConfig.PpuViewer_AddBreakpointTile));
98 				mnuAddBreakpointAttribute.InitShortcut(this, nameof(DebuggerShortcutsConfig.PpuViewer_AddBreakpointAttribute));
99 			}
100 		}
101 
Connect(ctrlChrViewer chrViewer)102 		public void Connect(ctrlChrViewer chrViewer)
103 		{
104 			_chrViewer = chrViewer;
105 		}
106 
GetData()107 		public void GetData()
108 		{
109 			InteropEmu.DebugGetPpuScroll(out _xScroll, out _yScroll);
110 			InteropEmu.DebugGetState(ref _state);
111 
112 			_ntChanged = InteropEmu.DebugGetNametableChangedData();
113 
114 			//Keep a copy of the previous frame to highlight modifications
115 			for(int i = 0; i < 4; i++) {
116 				_prevTileData[i] = _tileData[i] != null ? (byte[])_tileData[i].Clone() : null;
117 				_prevAttributeData[i] = _attributeData[i] != null ? (byte[])_attributeData[i].Clone() : null;
118 			}
119 
120 			NametableDisplayMode mode;
121 			if(_showAttributeColorsOnly) {
122 				mode = NametableDisplayMode.AttributeView;
123 			} else if(ConfigManager.Config.DebugInfo.NtViewerUseGrayscalePalette) {
124 				mode = NametableDisplayMode.Grayscale;
125 			} else {
126 				mode = NametableDisplayMode.Normal;
127 			}
128 
129 			for(int i = 0; i < 4; i++) {
130 				InteropEmu.DebugGetNametable(i, mode, out _nametablePixelData[i], out _tileData[i], out _attributeData[i]);
131 			}
132 
133 			_hdCopyHelper.RefreshData();
134 		}
135 
RefreshViewer()136 		public void RefreshViewer()
137 		{
138 			int tileIndexOffset = _state.PPU.ControlFlags.BackgroundPatternAddr == 0x1000 ? 256 : 0;
139 			lblMirroringType.Text = ResourceHelper.GetEnumText(_state.Cartridge.Mirroring);
140 
141 			using(Graphics gNametable = Graphics.FromImage(_nametableImage)) {
142 				for(int i = 0; i < 4; i++) {
143 					GCHandle handle = GCHandle.Alloc(_nametablePixelData[i], GCHandleType.Pinned);
144 					Bitmap source = new Bitmap(256, 240, 4*256, PixelFormat.Format32bppPArgb, handle.AddrOfPinnedObject());
145 					try {
146 						gNametable.DrawImage(source, new Rectangle(i % 2 == 0 ? 0 : 256, i <= 1 ? 0 : 240, 256, 240), new Rectangle(0, 0, 256, 240), GraphicsUnit.Pixel);
147 					} finally {
148 						handle.Free();
149 					}
150 				}
151 			}
152 
153 			if(this._gridOverlay == null && (chkShowTileGrid.Checked || chkShowAttributeGrid.Checked)) {
154 				this._gridOverlay = new Bitmap(512, 480, PixelFormat.Format32bppPArgb);
155 
156 				using(Graphics overlay = Graphics.FromImage(this._gridOverlay)) {
157 					if(chkShowTileGrid.Checked) {
158 						using(Pen pen = new Pen(Color.FromArgb(chkShowAttributeGrid.Checked ? 120 : 180, 240, 100, 120))) {
159 							if(chkShowAttributeGrid.Checked) {
160 								pen.DashStyle = DashStyle.Dot;
161 							}
162 							DrawGrid(overlay, pen, 1);
163 						}
164 					}
165 
166 					if(chkShowAttributeGrid.Checked) {
167 						using(Pen pen = new Pen(Color.FromArgb(180, 80, 130, 250))) {
168 							DrawGrid(overlay, pen, 2);
169 						}
170 					}
171 				}
172 			}
173 
174 			using(Graphics g = Graphics.FromImage(_finalImage)) {
175 				g.DrawImage(_nametableImage, 0, 0);
176 
177 				for(int i = 0; i < 4; i++) {
178 					if(_chrViewer.SelectedTileIndex >= 0 && this.chkHighlightChrTile.Checked) {
179 						HighlightChrViewerTile(tileIndexOffset, g, i);
180 					}
181 				}
182 
183 				if(this._gridOverlay != null) {
184 					g.DrawImage(this._gridOverlay, 0, 0);
185 				}
186 
187 				if(chkShowPpuScrollOverlay.Checked) {
188 					DrawScrollOverlay(_xScroll, _yScroll, g);
189 				}
190 
191 				if(chkHighlightAttributeUpdates.Checked || chkHighlightTileUpdates.Checked) {
192 					DrawEditHighlights(g);
193 				}
194 			}
195 
196 			if(_firstDraw) {
197 				_currentPpuAddress = 0x2000;
198 				UpdateTileInformation(0, 0, 0x2000, 0);
199 				_firstDraw = false;
200 			}
201 
202 			this.DrawHud();
203 		}
204 
DrawHud()205 		private void DrawHud()
206 		{
207 			using(Graphics g = Graphics.FromImage(_hudImage)) {
208 				g.DrawImage(_finalImage, 0, 0);
209 
210 				if(_currentPpuAddress >= 0) {
211 					//Draw overlay over current tile
212 					int x = _tileX + ((_nametableIndex & 0x01) == 1 ? 32 : 0);
213 					int y = _tileY + (_nametableIndex >= 2 ? 30 : 0);
214 					using(SolidBrush brush = new SolidBrush(Color.FromArgb(100, 255, 255, 255))) {
215 						g.FillRectangle(brush, x * 8, y * 8, 8, 8);
216 					}
217 					g.DrawRectangle(Pens.White, x * 8, y * 8, 7, 7);
218 
219 					if(ConfigManager.Config.DebugInfo.PpuShowInformationOverlay) {
220 						//Draw tooltip box with information
221 						string tooltipText = (
222 							"Tile:      $" + _tileInfo.PpuAddress.ToString("X4") + " = $" + _tileInfo.TileIndex.ToString("X2") + Environment.NewLine +
223 							"Position:  " + _tileInfo.TileX.ToString() + ", " + _tileInfo.TileY.ToString() + Environment.NewLine +
224 							"Attribute: $" + _tileInfo.AttributeAddress.ToString("X4") + " = $" + _tileInfo.AttributeData.ToString("X2") + Environment.NewLine +
225 							"Palette:   " + (_tileInfo.PaletteAddress >> 2).ToString() + " ($" + (0x3F00 + _tileInfo.PaletteAddress).ToString("X4") + ")" + Environment.NewLine
226 						);
227 
228 						PpuViewerHelper.DrawOverlayTooltip(_hudImage, tooltipText, picTile.Image, _tileInfo.PaletteAddress >> 2, _nametableIndex >= 2, g);
229 					}
230 				}
231 			}
232 
233 			picNametable.Image = _hudImage;
234 			picNametable.Refresh();
235 		}
236 
DrawEditHighlights(Graphics g)237 		private void DrawEditHighlights(Graphics g)
238 		{
239 			bool ignoreRedundantWrites = chkIgnoreRedundantWrites.Checked;
240 			using(Brush redBrush = new SolidBrush(Color.FromArgb(128, Color.Red))) {
241 				using(Brush yellowBrush = new SolidBrush(Color.FromArgb(128, Color.Yellow))) {
242 					for(int nt = 0; nt < 4; nt++) {
243 						if(_prevTileData[nt] == null || _prevAttributeData[nt] == null || _ntChanged == null) {
244 							continue;
245 						}
246 
247 						int ntBaseAddress = nt * 0x400;
248 						for(int y = 0; y < 30; y++) {
249 							for(int x = 0; x < 32; x++) {
250 								int tileX = ((nt % 2 == 1) ? x + 32 : x) * 8;
251 								int tileY = ((nt >= 2) ? y + 30 : y) * 8;
252 
253 								bool tileChanged = false;
254 								bool attrChanged = false;
255 
256 								if(ignoreRedundantWrites) {
257 									tileChanged = _prevTileData[nt][y * 32 + x] != _tileData[nt][y * 32 + x];
258 									int shift = (x & 0x02) | ((y & 0x02) << 1);
259 									int attribute = (_attributeData[nt][y * 32 + x] >> shift) & 0x03;
260 									int prevAttribute = (_prevAttributeData[nt][y * 32 + x] >> shift) & 0x03;
261 									attrChanged = attribute != prevAttribute;
262 								} else {
263 									int tileAddress = ntBaseAddress + y * 32 + x;
264 									int attrAddress = ntBaseAddress + 32 * 30 + ((y & 0xFC) << 1) + (x >> 2);
265 
266 									tileChanged = _ntChanged[tileAddress];
267 									attrChanged = _ntChanged[attrAddress];
268 								}
269 
270 								if(chkHighlightTileUpdates.Checked && tileChanged) {
271 									g.FillRectangle(redBrush, tileX, tileY, 8, 8);
272 									g.DrawRectangle(Pens.Red, tileX, tileY, 8, 8);
273 								}
274 
275 								if(chkHighlightAttributeUpdates.Checked && attrChanged) {
276 									g.FillRectangle(yellowBrush, tileX, tileY, 8, 8);
277 									g.DrawRectangle(Pens.Yellow, tileX, tileY, 8, 8);
278 								}
279 							}
280 						}
281 					}
282 				}
283 			}
284 		}
285 
HighlightChrViewerTile(int tileIndexOffset, Graphics dest, int nametableIndex)286 		private void HighlightChrViewerTile(int tileIndexOffset, Graphics dest, int nametableIndex)
287 		{
288 			int xOffset = nametableIndex % 2 == 0 ? 0 : 256;
289 			int yOffset = nametableIndex <= 1 ? 0 : 240;
290 
291 			using(Pen pen = new Pen(Color.Red, 2)) {
292 				for(int j = 0; j < 960; j++) {
293 					if(_tileData[nametableIndex][j] + tileIndexOffset == _chrViewer.SelectedTileIndex) {
294 						dest.DrawRectangle(pen, new Rectangle(xOffset + (j%32)*8-1, yOffset + (j/32)*8-1, 10, 10));
295 					}
296 				}
297 			}
298 		}
299 
DrawGrid(Graphics g, Pen pen, int factor)300 		private static void DrawGrid(Graphics g, Pen pen, int factor)
301 		{
302 			for(int i = 0; i < 64 / factor; i++) {
303 				g.DrawLine(pen, i * 8 * factor - 1, 0, i * 8 * factor - 1, 479);
304 			}
305 
306 			for(int i = 0; i < 60 / factor; i++) {
307 				g.DrawLine(pen, 0, i * 8 * factor - 1, 511, i * 8 * factor - 1);
308 			}
309 		}
310 
DrawScrollOverlay(int xScroll, int yScroll, Graphics g)311 		private static void DrawScrollOverlay(int xScroll, int yScroll, Graphics g)
312 		{
313 			using(Brush brush = new SolidBrush(Color.FromArgb(75, 100, 180, 215))) {
314 				g.FillRectangle(brush, xScroll, yScroll, 256, 240);
315 				if(xScroll + 256 >= 512) {
316 					g.FillRectangle(brush, 0, yScroll, xScroll - 256, 240);
317 				}
318 				if(yScroll + 240 >= 480) {
319 					g.FillRectangle(brush, xScroll, 0, 256, yScroll - 240);
320 				}
321 				if(xScroll + 256 >= 512 && yScroll + 240 >= 480) {
322 					g.FillRectangle(brush, 0, 0, xScroll - 256, yScroll - 240);
323 				}
324 			}
325 			using(Pen pen = new Pen(Color.FromArgb(230, 150, 150, 150), 2)) {
326 				g.DrawRectangle(pen, xScroll, yScroll, 256, 240);
327 				if(xScroll + 256 >= 512) {
328 					g.DrawRectangle(pen, 0, yScroll, xScroll - 256, 240);
329 				}
330 				if(yScroll + 240 >= 480) {
331 					g.DrawRectangle(pen, xScroll, 0, 256, yScroll - 240);
332 				}
333 				if(xScroll + 256 >= 512 && yScroll + 240 >= 480) {
334 					g.DrawRectangle(pen, 0, 0, xScroll - 256, yScroll - 240);
335 				}
336 			}
337 		}
338 
picNametable_MouseLeave(object sender, EventArgs e)339 		private void picNametable_MouseLeave(object sender, EventArgs e)
340 		{
341 			_currentPpuAddress = -1;
342 			DrawHud();
343 		}
344 
picNametable_MouseMove(object sender, MouseEventArgs e)345 		private void picNametable_MouseMove(object sender, MouseEventArgs e)
346 		{
347 			int xPos = Math.Max(0, e.X * 512 / (picNametable.Width - 2));
348 			int yPos = Math.Max(0, e.Y * 480 / (picNametable.Height - 2));
349 
350 			_nametableIndex = 0;
351 			if(xPos >= 256) {
352 				_nametableIndex++;
353 			}
354 			if(yPos >= 240) {
355 				_nametableIndex += 2;
356 			}
357 
358 			int baseAddress = 0x2000 + _nametableIndex * 0x400;
359 
360 			_tileX = Math.Min(xPos / 8, 63);
361 			_tileY = Math.Min(yPos / 8, 59);
362 
363 			if(_nametableIndex % 2 == 1) {
364 				_tileX -= 32;
365 			}
366 			if(_nametableIndex >= 2) {
367 				_tileY -= 30;
368 			}
369 
370 			int shift = (_tileX & 0x02) | ((_tileY & 0x02) << 1);
371 			int ppuAddress = (baseAddress + _tileX + _tileY * 32);
372 			if(_currentPpuAddress == ppuAddress) {
373 				return;
374 			}
375 			_currentPpuAddress = ppuAddress;
376 
377 			UpdateTileInformation(xPos, yPos, baseAddress, shift);
378 			DrawHud();
379 		}
380 
UpdateTileInformation(int xPos, int yPos, int baseAddress, int shift)381 		private void UpdateTileInformation(int xPos, int yPos, int baseAddress, int shift)
382 		{
383 			DebugState state = new DebugState();
384 			InteropEmu.DebugGetState(ref state);
385 			int bgAddr = state.PPU.ControlFlags.BackgroundPatternAddr;
386 
387 			byte tileIndex = _tileData[_nametableIndex][_tileY * 32 + _tileX];
388 			byte attributeData = _attributeData[_nametableIndex][_tileY * 32 + _tileX];
389 
390 			_tileInfo = new TileInfo() {
391 				PpuAddress = _currentPpuAddress,
392 				TileIndex = tileIndex,
393 				AttributeData = attributeData,
394 				AttributeAddress = baseAddress + 960 + ((_tileY & 0xFC) << 1) + (_tileX >> 2),
395 				PaletteAddress = ((attributeData >> shift) & 0x03) << 2,
396 				TileX = _tileX,
397 				TileY = _tileY,
398 				Nametable = _nametableIndex,
399 				TileAddress = bgAddr + tileIndex * 16
400 			};
401 
402 			this.ctrlTilePalette.SelectedPalette = (_tileInfo.PaletteAddress >> 2);
403 
404 			this.txtPpuAddress.Text = _tileInfo.PpuAddress.ToString("X4");
405 			this.txtNametable.Text = _tileInfo.Nametable.ToString();
406 			this.txtLocation.Text = _tileInfo.TileX.ToString() + ", " + _tileInfo.TileY.ToString();
407 			this.txtTileIndex.Text = _tileInfo.TileIndex.ToString("X2");
408 			this.txtTileAddress.Text = _tileInfo.TileAddress.ToString("X4");
409 			this.txtAttributeData.Text = _tileInfo.AttributeData.ToString("X2");
410 			this.txtAttributeAddress.Text = _tileInfo.AttributeAddress.ToString("X4");
411 			this.txtPaletteAddress.Text = (0x3F00 + _tileInfo.PaletteAddress).ToString("X4");
412 
413 			picTile.Image = PpuViewerHelper.GetPreview(new Point(xPos / 8 * 8, yPos / 8 * 8), new Size(8, 8), 8, _nametableImage);
414 		}
415 
chkShowScrollWindow_Click(object sender, EventArgs e)416 		private void chkShowScrollWindow_Click(object sender, EventArgs e)
417 		{
418 			ConfigManager.Config.DebugInfo.ShowPpuScrollOverlay = chkShowPpuScrollOverlay.Checked;
419 			ConfigManager.ApplyChanges();
420 			this.RefreshViewer();
421 		}
422 
chkShowAttributeColorsOnly_Click(object sender, EventArgs e)423 		private void chkShowAttributeColorsOnly_Click(object sender, EventArgs e)
424 		{
425 			ConfigManager.Config.DebugInfo.ShowAttributeColorsOnly = chkShowAttributeColorsOnly.Checked;
426 			ConfigManager.ApplyChanges();
427 
428 			_showAttributeColorsOnly = chkShowAttributeColorsOnly.Checked;
429 			chkUseGrayscalePalette.Enabled = !chkShowAttributeColorsOnly.Checked;
430 			this.GetData();
431 			this.RefreshViewer();
432 		}
433 
chkShowTileGrid_Click(object sender, EventArgs e)434 		private void chkShowTileGrid_Click(object sender, EventArgs e)
435 		{
436 			ConfigManager.Config.DebugInfo.ShowTileGrid = chkShowTileGrid.Checked;
437 			ConfigManager.ApplyChanges();
438 			this._gridOverlay = null;
439 			this.RefreshViewer();
440 		}
441 
chkShowAttributeGrid_Click(object sender, EventArgs e)442 		private void chkShowAttributeGrid_Click(object sender, EventArgs e)
443 		{
444 			ConfigManager.Config.DebugInfo.ShowAttributeGrid = chkShowAttributeGrid.Checked;
445 			ConfigManager.ApplyChanges();
446 			this._gridOverlay = null;
447 			this.RefreshViewer();
448 		}
449 
chkHighlightChrTile_Click(object sender, EventArgs e)450 		private void chkHighlightChrTile_Click(object sender, EventArgs e)
451 		{
452 			ConfigManager.Config.DebugInfo.HighlightChrTile = chkHighlightChrTile.Checked;
453 			ConfigManager.ApplyChanges();
454 			this.RefreshViewer();
455 		}
456 
chkUseGrayscalePalette_Click(object sender, EventArgs e)457 		private void chkUseGrayscalePalette_Click(object sender, EventArgs e)
458 		{
459 			ConfigManager.Config.DebugInfo.NtViewerUseGrayscalePalette = chkUseGrayscalePalette.Checked;
460 			ConfigManager.ApplyChanges();
461 			this.GetData();
462 			this.RefreshViewer();
463 		}
464 
chkHighlightTileUpdates_Click(object sender, EventArgs e)465 		private void chkHighlightTileUpdates_Click(object sender, EventArgs e)
466 		{
467 			ConfigManager.Config.DebugInfo.NtViewerHighlightTileUpdates = chkHighlightTileUpdates.Checked;
468 			ConfigManager.ApplyChanges();
469 			this.RefreshViewer();
470 			UpdateIgnoreWriteCheckbox();
471 		}
472 
chkHighlightAttributeUpdates_Click(object sender, EventArgs e)473 		private void chkHighlightAttributeUpdates_Click(object sender, EventArgs e)
474 		{
475 			ConfigManager.Config.DebugInfo.NtViewerHighlightAttributeUpdates = chkHighlightAttributeUpdates.Checked;
476 			ConfigManager.ApplyChanges();
477 			this.RefreshViewer();
478 			UpdateIgnoreWriteCheckbox();
479 		}
480 
chkIgnoreRedundantWrites_Click(object sender, EventArgs e)481 		private void chkIgnoreRedundantWrites_Click(object sender, EventArgs e)
482 		{
483 			ConfigManager.Config.DebugInfo.NtViewerIgnoreRedundantWrites = chkIgnoreRedundantWrites.Checked;
484 			ConfigManager.ApplyChanges();
485 			this.RefreshViewer();
486 		}
487 
UpdateIgnoreWriteCheckbox()488 		private void UpdateIgnoreWriteCheckbox()
489 		{
490 			chkIgnoreRedundantWrites.Enabled = chkHighlightAttributeUpdates.Checked || chkHighlightTileUpdates.Checked;
491 		}
492 
493 		string _copyData;
mnuCopyHdPack_Click(object sender, EventArgs e)494 		private void mnuCopyHdPack_Click(object sender, EventArgs e)
495 		{
496 			Clipboard.SetText(_copyData);
497 		}
498 
ToHdPackFormat(int nametableIndex, int nametableTileIndex)499 		private string ToHdPackFormat(int nametableIndex, int nametableTileIndex)
500 		{
501 			int x = nametableTileIndex % 32;
502 			int y = nametableTileIndex / 32;
503 
504 			int tileIndex = _tileData[_nametableIndex][nametableTileIndex];
505 			int attributeData = _attributeData[_nametableIndex][nametableTileIndex];
506 			int shift = (x & 0x02) | ((y & 0x02) << 1);
507 			int palette = (attributeData >> shift) & 0x03;
508 			DebugState state = new DebugState();
509 			InteropEmu.DebugGetState(ref state);
510 			int bgAddr = state.PPU.ControlFlags.BackgroundPatternAddr;
511 			int tileAddr = bgAddr + tileIndex * 16;
512 
513 			return _hdCopyHelper.ToHdPackFormat(tileAddr, palette, false, false);
514 		}
515 
ctxMenu_Opening(object sender, CancelEventArgs e)516 		private void ctxMenu_Opening(object sender, CancelEventArgs e)
517 		{
518 			mnuAddBreakpoint.Text = "Add breakpoint (Tile - $" + _tileInfo.PpuAddress.ToString("X4") + ")";
519 			mnuAddBreakpointAttribute.Text = "Add breakpoint (Attribute - $" + _tileInfo.AttributeAddress.ToString("X4") + ")";
520 			mnuEditInMemoryViewer.Text = "Edit in Memory Viewer ($" + _currentPpuAddress.ToString("X4") + ")";
521 			mnuAddBreakpoint.Enabled = DebugWindowManager.GetDebugger() != null;
522 			mnuAddBreakpointAttribute.Enabled = DebugWindowManager.GetDebugger() != null;
523 			mnuCopyNametableHdPack.Visible = Control.ModifierKeys == Keys.Shift;
524 			_copyData = ToHdPackFormat(_nametableIndex, _tileY * 32 + _tileX);
525 		}
526 
ShowInChrViewer()527 		private void ShowInChrViewer()
528 		{
529 			int tileIndex = _tileData[_nametableIndex][_tileY*32+_tileX];
530 			int attributeData = _attributeData[_nametableIndex][_tileY*32+_tileX];
531 			int shift = (_tileX & 0x02) | ((_tileY & 0x02) << 1);
532 			int paletteIndex = ((attributeData >> shift) & 0x03);
533 
534 			DebugState state = new DebugState();
535 			InteropEmu.DebugGetState(ref state);
536 			int tileIndexOffset = state.PPU.ControlFlags.BackgroundPatternAddr == 0x1000 ? 256 : 0;
537 
538 			OnSelectChrTile?.Invoke(tileIndex + tileIndexOffset, paletteIndex);
539 		}
540 
picNametable_DoubleClick(object sender, EventArgs e)541 		private void picNametable_DoubleClick(object sender, EventArgs e)
542 		{
543 			ShowInChrViewer();
544 		}
545 
mnuShowInChrViewer_Click(object sender, EventArgs e)546 		private void mnuShowInChrViewer_Click(object sender, EventArgs e)
547 		{
548 			ShowInChrViewer();
549 		}
550 
mnuCopyToClipboard_Click(object sender, EventArgs e)551 		private void mnuCopyToClipboard_Click(object sender, EventArgs e)
552 		{
553 			CopyToClipboard();
554 		}
555 
CopyToClipboard()556 		public void CopyToClipboard()
557 		{
558 			Clipboard.SetImage(_nametableImage);
559 		}
560 
mnuCopyNametableHdPack_Click(object sender, EventArgs e)561 		private void mnuCopyNametableHdPack_Click(object sender, EventArgs e)
562 		{
563 			StringBuilder sb = new StringBuilder();
564 			for(int y = 0; y < 30; y++) {
565 				for(int x = 0; x < 32; x++) {
566 					sb.AppendLine(ToHdPackFormat(_nametableIndex, y*32+x) + "," + (x * 8).ToString() + "," + (y*8).ToString());
567 				}
568 			}
569 			Clipboard.SetText(sb.ToString());
570 		}
571 
mnuExportToPng_Click(object sender, EventArgs e)572 		private void mnuExportToPng_Click(object sender, EventArgs e)
573 		{
574 			using(SaveFileDialog sfd = new SaveFileDialog()) {
575 				sfd.SetFilter("PNG files|*.png");
576 				if(sfd.ShowDialog() == DialogResult.OK) {
577 					_nametableImage.Save(sfd.FileName, System.Drawing.Imaging.ImageFormat.Png);
578 				}
579 			}
580 		}
581 
mnuEditInMemoryViewer_Click(object sender, EventArgs e)582 		private void mnuEditInMemoryViewer_Click(object sender, EventArgs e)
583 		{
584 			DebugWindowManager.OpenMemoryViewer(_tileInfo.PpuAddress, DebugMemoryType.PpuMemory);
585 		}
586 
AddBreakpoint(int address)587 		private void AddBreakpoint(int address)
588 		{
589 			PpuAddressTypeInfo addressInfo = InteropEmu.DebugGetPpuAbsoluteAddressAndType((uint)address);
590 
591 			BreakpointManager.EditBreakpoint(new Breakpoint() {
592 				MemoryType = addressInfo.Type.ToMemoryType(),
593 				BreakOnExec = false,
594 				BreakOnRead = true,
595 				BreakOnWrite = true,
596 				Address = (UInt32)addressInfo.Address,
597 				StartAddress = (UInt32)addressInfo.Address,
598 				EndAddress = (UInt32)addressInfo.Address,
599 				AddressType = BreakpointAddressType.SingleAddress
600 			});
601 		}
602 
mnuAddBreakpointAttribute_Click(object sender, EventArgs e)603 		private void mnuAddBreakpointAttribute_Click(object sender, EventArgs e)
604 		{
605 			if(DebugWindowManager.GetDebugger() == null) {
606 				return;
607 			}
608 			AddBreakpoint(_tileInfo.AttributeAddress);
609 		}
610 
mnuAddBreakpoint_Click(object sender, EventArgs e)611 		private void mnuAddBreakpoint_Click(object sender, EventArgs e)
612 		{
613 			if(DebugWindowManager.GetDebugger() == null) {
614 				return;
615 			}
616 			AddBreakpoint(_tileInfo.PpuAddress);
617 		}
618 
picNametable_MouseEnter(object sender, EventArgs e)619 		private void picNametable_MouseEnter(object sender, EventArgs e)
620 		{
621 			if(this.ParentForm.ContainsFocus) {
622 				this.Focus();
623 			}
624 		}
625 
626 		private class TileInfo
627 		{
628 			public int PpuAddress;
629 			public byte TileIndex;
630 			public int TileAddress;
631 			public byte AttributeData;
632 			public int AttributeAddress;
633 			public int PaletteAddress;
634 			public int TileX;
635 			public int TileY;
636 			public int Nametable;
637 		}
638 	}
639 }
640