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