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.Controls; 12 using System.Drawing.Imaging; 13 using Mesen.GUI.Config; 14 using Mesen.GUI.Forms; 15 using System.Drawing.Drawing2D; 16 17 namespace Mesen.GUI.Debugger.Controls 18 { 19 public partial class ctrlSpriteViewer : BaseControl, ICompactControl 20 { SelectTilePaletteHandler(int tileIndex, int paletteIndex)21 public delegate void SelectTilePaletteHandler(int tileIndex, int paletteIndex); 22 public event SelectTilePaletteHandler OnSelectTilePalette; 23 private byte[] _spriteRam; 24 private byte[] _spritePixelData; 25 26 private int _selectedSprite = -1; 27 private SpriteInfo _spriteInfo = new SpriteInfo(); 28 29 private bool _largeSprites = false; 30 private bool _prevLargeSprites = true; 31 private int _spritePatternAddr; 32 private bool _forceRefresh; 33 private Point? _previewMousePosition = null; 34 private int _contextMenuSpriteIndex = -1; 35 private bool _copyPreview = false; 36 private Bitmap _imgSprites; 37 private Bitmap _scaledSprites = new Bitmap(256, 512, PixelFormat.Format32bppPArgb); 38 private Bitmap _screenPreview = new Bitmap(256, 240, PixelFormat.Format32bppPArgb); 39 private HdPackCopyHelper _hdCopyHelper = new HdPackCopyHelper(); 40 private bool _firstDraw = true; 41 private int _originalSpriteHeight = 0; 42 private int _originalTileHeight = 0; 43 private Size _originalPreviewSize; 44 private double _scale = 1; 45 ctrlSpriteViewer()46 public ctrlSpriteViewer() 47 { 48 InitializeComponent(); 49 50 if(!IsDesignMode) { 51 picPreview.Image = new Bitmap(256, 240, PixelFormat.Format32bppPArgb); 52 picSprites.Image = new Bitmap(256, 512, PixelFormat.Format32bppPArgb); 53 54 chkDisplaySpriteOutlines.Checked = ConfigManager.Config.DebugInfo.SpriteViewerDisplaySpriteOutlines; 55 56 _originalSpriteHeight = picSprites.Height; 57 _originalTileHeight = picTile.Height; 58 _originalPreviewSize = picPreview.Size; 59 } 60 } 61 GetCompactSize(bool includeMargins)62 public Size GetCompactSize(bool includeMargins) 63 { 64 return new Size(picSprites.Width, _prevLargeSprites ? picSprites.Height : (picSprites.Height * 2)); 65 } 66 ScaleImage(double scale)67 public void ScaleImage(double scale) 68 { 69 _scale *= scale; 70 71 picSprites.Size = new Size((int)(picSprites.Width * scale), (int)(picSprites.Height * scale)); 72 if(_largeSprites) { 73 picPreview.Size = _originalPreviewSize; 74 } else { 75 picPreview.Size = new Size((int)(_originalPreviewSize.Width * _scale), (int)(_originalPreviewSize.Height * _scale)); 76 } 77 _originalSpriteHeight = (int)(_originalSpriteHeight * scale); 78 79 picSprites.InterpolationMode = scale > 1 ? InterpolationMode.NearestNeighbor : InterpolationMode.Default; 80 picPreview.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 } 98 } 99 GetData()100 public void GetData() 101 { 102 DebugState state = new DebugState(); 103 InteropEmu.DebugGetState(ref state); 104 _largeSprites = state.PPU.ControlFlags.LargeSprites != 0; 105 _spritePatternAddr = state.PPU.ControlFlags.SpritePatternAddr; 106 107 _spriteRam = InteropEmu.DebugGetMemoryState(DebugMemoryType.SpriteMemory); 108 _spritePixelData = InteropEmu.DebugGetSprites(); 109 110 _hdCopyHelper.RefreshData(); 111 } 112 RefreshViewer()113 public void RefreshViewer() 114 { 115 _forceRefresh = true; 116 117 GCHandle handle = GCHandle.Alloc(_spritePixelData, GCHandleType.Pinned); 118 try { 119 Bitmap source = new Bitmap(64, 128, 4*64, PixelFormat.Format32bppPArgb, handle.AddrOfPinnedObject()); 120 121 Bitmap sprites = new Bitmap(64, 128, PixelFormat.Format32bppPArgb); 122 using(Graphics g = Graphics.FromImage(sprites)) { 123 if(_largeSprites) { 124 g.DrawImage(source, 0, 0); 125 } else { 126 for(int i = 0; i < 8; i++) { 127 g.DrawImage(source, 0, 8 * i, new RectangleF(0, 8 * i * 2, 64, 8), GraphicsUnit.Pixel); 128 } 129 } 130 } 131 _imgSprites = sprites; 132 133 using(Graphics g = Graphics.FromImage(_scaledSprites)) { 134 g.Clear(Color.FromArgb(64, 64, 64)); 135 g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; 136 g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; 137 g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half; 138 139 g.ScaleTransform(4, 4); 140 g.DrawImageUnscaled(sprites, 0, 0); 141 } 142 } finally { 143 handle.Free(); 144 } 145 146 if(_prevLargeSprites != _largeSprites) { 147 ToggleSpriteMode(); 148 _prevLargeSprites = _largeSprites; 149 } 150 151 if(_previewMousePosition.HasValue) { 152 SelectSpriteUnderCursor(); 153 } 154 CreateScreenPreview(); 155 picPreview.Refresh(); 156 157 if(_firstDraw) { 158 //Update the UI with the first sprite when showing for the first time 159 UpdateTileInfo(0); 160 _selectedSprite = -1; 161 _firstDraw = false; 162 } 163 164 DrawHud(); 165 } 166 ToggleSpriteMode()167 private void ToggleSpriteMode() 168 { 169 if(_largeSprites) { 170 picSprites.Image = new Bitmap(256, 512, PixelFormat.Format32bppPArgb); 171 picSprites.Height = _originalSpriteHeight; 172 picTile.Height = _originalTileHeight; 173 picPreview.Size = _originalPreviewSize; 174 175 tlpMain.SetRowSpan(picSprites, 2); 176 tlpMain.SetColumnSpan(picPreview, 4); 177 tlpInfo.Controls.Add(picPreview, 1, 5); 178 lblScreenPreview.Visible = true; 179 } else { 180 picSprites.Image = new Bitmap(256, 256, PixelFormat.Format32bppPArgb); 181 picSprites.Height = (_originalSpriteHeight - 2) / 2 + 2; 182 picTile.Height = (_originalTileHeight - 2) / 2 + 2; 183 picPreview.Size = new Size((int)(_originalPreviewSize.Width * _scale), (int)(_originalPreviewSize.Height * _scale)); 184 185 tlpMain.SetRowSpan(picSprites, 1); 186 tlpMain.SetColumnSpan(picPreview, 1); 187 tlpMain.Controls.Add(picPreview, 0, 1); 188 lblScreenPreview.Visible = false; 189 } 190 } 191 DrawHud()192 private void DrawHud() 193 { 194 using(Graphics g = Graphics.FromImage(picSprites.Image)) { 195 g.DrawImage(_scaledSprites, 0, 0); 196 197 if(_selectedSprite >= 0) { 198 int x = _selectedSprite % 8; 199 int y = _selectedSprite / 8; 200 int spriteHeight = _largeSprites ? 64 : 32; 201 using(SolidBrush brush = new SolidBrush(Color.FromArgb(100, 255, 255, 255))) { 202 g.FillRectangle(brush, x * 32, y * spriteHeight, 32, spriteHeight); 203 } 204 g.DrawRectangle(Pens.White, x * 32, y * spriteHeight, 31, spriteHeight - 1); 205 206 if(ConfigManager.Config.DebugInfo.PpuShowInformationOverlay) { 207 string tooltipText = ( 208 "Sprite: $" + _spriteInfo.SpriteIndex.ToString("X2") + Environment.NewLine + 209 "Tile: $" + _spriteInfo.TileIndex.ToString("X2") + Environment.NewLine + 210 "Position: " + _spriteInfo.SpriteX.ToString() + ", " + _spriteInfo.SpriteY.ToString() + Environment.NewLine + 211 "Flags: " + (_spriteInfo.HorizontalMirror ? "H" : "-") + (_spriteInfo.VerticalMirror ? "V" : "-") + (_spriteInfo.BackgroundPriority ? "B" : "-") + Environment.NewLine + 212 "Palette: " + _spriteInfo.PaletteIndex.ToString() + " ($" + (0x3F10 + (_spriteInfo.PaletteIndex << 2)).ToString("X4") + ")" + Environment.NewLine 213 ); 214 215 PpuViewerHelper.DrawOverlayTooltip(picSprites.Image, tooltipText, picTile.Image, _spriteInfo.PaletteIndex + 4, _selectedSprite >= 32, g); 216 } 217 } 218 } 219 220 picSprites.Refresh(); 221 } 222 CreateScreenPreview()223 private void CreateScreenPreview() 224 { 225 GCHandle handle = GCHandle.Alloc(_spritePixelData, GCHandleType.Pinned); 226 try { 227 Bitmap source = new Bitmap(64, 128, 4*64, PixelFormat.Format32bppPArgb, handle.AddrOfPinnedObject()); 228 229 using(Graphics g = Graphics.FromImage(_screenPreview)) { 230 g.InterpolationMode = InterpolationMode.NearestNeighbor; 231 g.SmoothingMode = SmoothingMode.None; 232 g.PixelOffsetMode = PixelOffsetMode.Half; 233 g.Clear(Color.Transparent); 234 235 for(int i = 63; i >= 0; i--) { 236 if(i != _selectedSprite) { 237 DrawSprite(source, g, i); 238 } 239 } 240 241 if(ConfigManager.Config.DebugInfo.SpriteViewerDisplaySpriteOutlines) { 242 using(Pen pen = new Pen(Color.White, 1)) { 243 for(int i = 63; i >= 0; i--) { 244 int spriteY = _spriteRam[i * 4]; 245 int spriteX = _spriteRam[i * 4 + 3]; 246 g.DrawRectangle(pen, new Rectangle(spriteX, spriteY, 9, _largeSprites ? 17 : 9)); 247 } 248 } 249 } 250 251 if(_selectedSprite >= 0) { 252 DrawSprite(source, g, _selectedSprite); 253 } 254 } 255 256 using(Graphics g = Graphics.FromImage(picPreview.Image)) { 257 g.Clear(Color.FromArgb(64, 64, 64)); 258 g.DrawImage(_screenPreview, 0, 0); 259 } 260 picPreview.Invalidate(); 261 } finally { 262 handle.Free(); 263 } 264 } 265 DrawSprite(Bitmap source, Graphics g, int i)266 private void DrawSprite(Bitmap source, Graphics g, int i) 267 { 268 int spriteY = _spriteRam[i*4]; 269 int spriteX = _spriteRam[i*4+3]; 270 271 if(spriteY < 240) { 272 g.DrawImage(source, new Rectangle(spriteX, spriteY, 8, _largeSprites ? 16 : 8), new Rectangle((i % 8) * 8, (i / 8) * 16, 8, _largeSprites ? 16 : 8), GraphicsUnit.Pixel); 273 } 274 275 if(_selectedSprite == i) { 276 using(Pen pen = new Pen(Color.Red, 2)) { 277 g.DrawRectangle(pen, new Rectangle(spriteX - 1, spriteY - 1, 10, _largeSprites ? 18 : 10)); 278 } 279 } 280 } 281 picSprites_MouseMove(object sender, MouseEventArgs e)282 private void picSprites_MouseMove(object sender, MouseEventArgs e) 283 { 284 _previewMousePosition = null; 285 286 int tileX = Math.Max(0, Math.Min(e.X * 256 / (picSprites.Width - 2) / 32, 31)); 287 int tileY = 0; 288 int ramAddr = 0; 289 if(_largeSprites) { 290 tileY = Math.Max(0, Math.Min(e.Y * 512 / (picSprites.Height - 2) / 64, 63)); 291 ramAddr = ((tileY << 3) + tileX) << 2; 292 } else { 293 tileY = Math.Max(0, Math.Min(e.Y * 256 / (picSprites.Height - 2) / 32, 31)); 294 ramAddr = ((tileY << 3) + tileX) << 2; 295 } 296 297 ramAddr = Math.Min(ramAddr, 63 * 4); 298 299 if(ramAddr / 4 == _selectedSprite && !_forceRefresh) { 300 return; 301 } 302 303 UpdateTileInfo(ramAddr); 304 DrawHud(); 305 } 306 UpdateTileInfo(int ramAddr)307 private void UpdateTileInfo(int ramAddr) 308 { 309 _forceRefresh = false; 310 _selectedSprite = ramAddr / 4; 311 312 int spriteY = _spriteRam[ramAddr]; 313 int tileIndex = _spriteRam[ramAddr + 1]; 314 int attributes = _spriteRam[ramAddr + 2]; 315 int spriteX = _spriteRam[ramAddr + 3]; 316 317 int tileAddr; 318 if(_largeSprites) { 319 tileAddr = ((tileIndex & 0x01) == 0x01 ? 0x1000 : 0x0000) + ((tileIndex & 0xFE) << 4); 320 } else { 321 tileAddr = _spritePatternAddr + (tileIndex << 4); 322 } 323 324 this.ctrlTilePalette.SelectedPalette = (attributes & 0x03) + 4; 325 326 int paletteAddr = 0x3F10 + ((attributes & 0x03) << 2); 327 bool verticalMirror = (attributes & 0x80) == 0x80; 328 bool horizontalMirror = (attributes & 0x40) == 0x40; 329 bool backgroundPriority = (attributes & 0x20) == 0x20; 330 331 _spriteInfo = new SpriteInfo() { 332 SpriteIndex = _selectedSprite, 333 SpriteX = spriteX, 334 SpriteY = spriteY, 335 TileIndex = tileIndex, 336 HorizontalMirror = horizontalMirror, 337 VerticalMirror = verticalMirror, 338 BackgroundPriority = backgroundPriority, 339 PaletteIndex = (attributes & 0x03) 340 }; 341 342 this.txtSpriteIndex.Text = _selectedSprite.ToString("X2"); 343 this.txtTileIndex.Text = tileIndex.ToString("X2"); 344 this.txtTileAddress.Text = tileAddr.ToString("X4"); 345 this.txtPosition.Text = spriteX.ToString() + ", " + spriteY.ToString(); 346 this.txtPaletteAddress.Text = paletteAddr.ToString("X4"); 347 this.chkVerticalMirroring.Checked = verticalMirror; 348 this.chkHorizontalMirroring.Checked = horizontalMirror; 349 this.chkBackgroundPriority.Checked = backgroundPriority; 350 351 int tileX = _selectedSprite % 8; 352 int tileY = _selectedSprite / 8; 353 354 int spriteHeight = _largeSprites ? 64 : 32; 355 picTile.Image = PpuViewerHelper.GetPreview(new Point(tileX * 32, tileY * spriteHeight), new Size(32, spriteHeight), 2, _scaledSprites); 356 357 this.CreateScreenPreview(); 358 } 359 picSprites_MouseLeave(object sender, EventArgs e)360 private void picSprites_MouseLeave(object sender, EventArgs e) 361 { 362 this._selectedSprite = -1; 363 this.CreateScreenPreview(); 364 this.DrawHud(); 365 } 366 367 string _copyData; mnuCopyHdPack_Click(object sender, EventArgs e)368 private void mnuCopyHdPack_Click(object sender, EventArgs e) 369 { 370 Clipboard.SetText(_copyData); 371 } 372 ctxMenu_Opening(object sender, CancelEventArgs e)373 private void ctxMenu_Opening(object sender, CancelEventArgs e) 374 { 375 mnuCopyAllSpritesHdPack.Visible = Control.ModifierKeys == Keys.Shift; 376 377 if(_selectedSprite < 0) { 378 _contextMenuSpriteIndex = -1; 379 return; 380 } 381 382 _contextMenuSpriteIndex = _selectedSprite; 383 _copyData = ToHdPackFormat(_selectedSprite); 384 } 385 ToHdPackFormat(int spriteIndex)386 private string ToHdPackFormat(int spriteIndex) 387 { 388 int ramAddr = spriteIndex * 4; 389 int tileIndex = _spriteRam[ramAddr + 1]; 390 int palette = (_spriteRam[ramAddr + 2] & 0x03) + 4; 391 392 int tileAddr; 393 if(_largeSprites) { 394 tileAddr = ((tileIndex & 0x01) == 0x01 ? 0x1000 : 0x0000) + ((tileIndex & 0xFE) << 4); 395 } else { 396 tileAddr = _spritePatternAddr + (tileIndex << 4); 397 } 398 399 return _hdCopyHelper.ToHdPackFormat(tileAddr, palette, true, false); 400 } 401 picPreview_MouseMove(object sender, MouseEventArgs e)402 private void picPreview_MouseMove(object sender, MouseEventArgs e) 403 { 404 _previewMousePosition = e.Location; 405 SelectSpriteUnderCursor(); 406 DrawHud(); 407 } 408 SelectSpriteUnderCursor()409 private void SelectSpriteUnderCursor() 410 { 411 Point p = _previewMousePosition.Value; 412 int xPos = p.X * 256 / (picPreview.Width - 2); 413 int yPos = p.Y * 240 / (picPreview.Height - 2); 414 int prevSprite = _selectedSprite; 415 _selectedSprite = -1; 416 for(int i = 0x100 - 4; i >= 0; i-=4) { 417 int spriteY = _spriteRam[i]; 418 int spriteX = _spriteRam[i + 3]; 419 420 if(xPos >= spriteX && xPos < spriteX + 8 && yPos >= spriteY && yPos < spriteY + (_largeSprites ? 16 : 8)) { 421 _selectedSprite = i / 4; 422 break; 423 } 424 } 425 426 if(prevSprite != _selectedSprite) { 427 if(_selectedSprite >= 0) { 428 UpdateTileInfo(_selectedSprite * 4); 429 } 430 CreateScreenPreview(); 431 } 432 } 433 picPreview_MouseLeave(object sender, EventArgs e)434 private void picPreview_MouseLeave(object sender, EventArgs e) 435 { 436 CreateScreenPreview(); 437 } 438 ShowInChrViewer()439 private void ShowInChrViewer() 440 { 441 if(_selectedSprite < 0 && _contextMenuSpriteIndex < 0) { 442 return; 443 } 444 445 int ramAddr = (_selectedSprite >= 0 ? _selectedSprite : _contextMenuSpriteIndex) * 4; 446 int tileIndex = _spriteRam[ramAddr + 1]; 447 int palette = (_spriteRam[ramAddr + 2] & 0x03) + 4; 448 449 DebugState state = new DebugState(); 450 InteropEmu.DebugGetState(ref state); 451 452 if(_largeSprites) { 453 if(tileIndex % 2 == 1) { 454 tileIndex += 256; 455 tileIndex--; 456 } 457 OnSelectTilePalette?.Invoke(tileIndex, palette); 458 } else { 459 int tileIndexOffset = state.PPU.ControlFlags.SpritePatternAddr == 0x1000 ? 256 : 0; 460 OnSelectTilePalette?.Invoke(tileIndex+tileIndexOffset, palette); 461 } 462 } 463 picSprites_DoubleClick(object sender, EventArgs e)464 private void picSprites_DoubleClick(object sender, EventArgs e) 465 { 466 ShowInChrViewer(); 467 } 468 mnuShowInChrViewer_Click(object sender, EventArgs e)469 private void mnuShowInChrViewer_Click(object sender, EventArgs e) 470 { 471 ShowInChrViewer(); 472 } 473 picSprites_MouseEnter(object sender, EventArgs e)474 private void picSprites_MouseEnter(object sender, EventArgs e) 475 { 476 _copyPreview = false; 477 if(this.ParentForm.ContainsFocus) { 478 this.Focus(); 479 } 480 } 481 picPreview_MouseEnter(object sender, EventArgs e)482 private void picPreview_MouseEnter(object sender, EventArgs e) 483 { 484 _copyPreview = true; 485 if(this.ParentForm.ContainsFocus) { 486 this.Focus(); 487 } 488 } 489 mnuCopyToClipboard_Click(object sender, EventArgs e)490 private void mnuCopyToClipboard_Click(object sender, EventArgs e) 491 { 492 CopyToClipboard(); 493 } 494 GetCopyBitmap()495 private Bitmap GetCopyBitmap() 496 { 497 Bitmap src = _copyPreview ? _screenPreview : _imgSprites; 498 Bitmap target = new Bitmap(src.Width, src.Height); 499 using(Graphics g = Graphics.FromImage(target)) { 500 g.Clear(Color.FromArgb(64, 64, 64)); 501 g.DrawImage(src, 0, 0); 502 } 503 return target; 504 } 505 CopyToClipboard()506 public void CopyToClipboard() 507 { 508 using(Bitmap target = GetCopyBitmap()) { 509 Clipboard.SetImage(target); 510 } 511 } 512 mnuExportToPng_Click(object sender, EventArgs e)513 private void mnuExportToPng_Click(object sender, EventArgs e) 514 { 515 using(SaveFileDialog sfd = new SaveFileDialog()) { 516 sfd.SetFilter("PNG files|*.png"); 517 if(sfd.ShowDialog() == DialogResult.OK) { 518 using(Bitmap target = GetCopyBitmap()) { 519 target.Save(sfd.FileName, System.Drawing.Imaging.ImageFormat.Png); 520 } 521 } 522 } 523 } 524 mnuCopyAllSpritesHdPack_Click(object sender, EventArgs e)525 private void mnuCopyAllSpritesHdPack_Click(object sender, EventArgs e) 526 { 527 StringBuilder sb = new StringBuilder(); 528 for(int i = 0; i < 64; i++) { 529 int ramAddr = i * 4; 530 int spriteY = _spriteRam[ramAddr]; 531 int spriteX = _spriteRam[ramAddr+3]; 532 int attributes = _spriteRam[ramAddr+2]; 533 bool horizontalMirror = (attributes & 0x40) == 0x40; 534 bool verticalMirror = (attributes & 0x80) == 0x80; 535 536 if(spriteY >= 0 && spriteY < 240) { 537 sb.AppendLine( 538 ToHdPackFormat(i) + "," + 539 spriteX.ToString() + "," + 540 spriteY.ToString() + "," + 541 (horizontalMirror ? "Y" : "N") + "," + 542 (verticalMirror ? "Y" : "N") 543 ); 544 } 545 } 546 if(sb.Length > 0) { 547 Clipboard.SetText(sb.ToString()); 548 } else { 549 Clipboard.Clear(); 550 } 551 } 552 mnuEditInMemoryViewer_Click(object sender, EventArgs e)553 private void mnuEditInMemoryViewer_Click(object sender, EventArgs e) 554 { 555 if(_selectedSprite < 0 && _contextMenuSpriteIndex < 0) { 556 return; 557 } 558 559 int ramAddr = (_selectedSprite >= 0 ? _selectedSprite : _contextMenuSpriteIndex) * 4; 560 int tileIndex = _spriteRam[ramAddr + 1]; 561 562 DebugState state = new DebugState(); 563 InteropEmu.DebugGetState(ref state); 564 565 int tileIndexOffset = (!_largeSprites && state.PPU.ControlFlags.SpritePatternAddr == 0x1000) ? 256 : 0; 566 DebugWindowManager.OpenMemoryViewer((tileIndex + tileIndexOffset) * 16, DebugMemoryType.PpuMemory); 567 } 568 chkDisplaySpriteOutlines_Click(object sender, EventArgs e)569 private void chkDisplaySpriteOutlines_Click(object sender, EventArgs e) 570 { 571 ConfigManager.Config.DebugInfo.SpriteViewerDisplaySpriteOutlines = chkDisplaySpriteOutlines.Checked; 572 ConfigManager.ApplyChanges(); 573 RefreshViewer(); 574 } 575 576 private class SpriteInfo 577 { 578 public int SpriteIndex; 579 public int TileIndex; 580 public bool HorizontalMirror; 581 public bool VerticalMirror; 582 public bool BackgroundPriority; 583 public int SpriteX; 584 public int SpriteY; 585 public int PaletteIndex; 586 } 587 } 588 } 589