using System; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; using System.Windows.Forms; using Core.Video; namespace Desktop { public class VramViewerForm : Form { private readonly SmsVdp _vdp; private readonly System.Windows.Forms.Timer _refreshTimer; private readonly Bitmap _vramBitmap; private readonly int[] _pixelData; public VramViewerForm(SmsVdp vdp) { _vdp = vdp; this.Text = "VRAM Viewer (512 Tiles)"; // 256x128 native resolution, scaled up by 2 for visibility! this.ClientSize = new Size(512, 256); this.DoubleBuffered = true; this.FormBorderStyle = FormBorderStyle.FixedToolWindow; _vramBitmap = new Bitmap(256, 128, PixelFormat.Format32bppArgb); _pixelData = new int[256 * 128]; // Update the window 10 times a second so it doesn't drain CPU power _refreshTimer = new System.Windows.Forms.Timer(); _refreshTimer.Interval = 100; _refreshTimer.Tick += (s, e) => UpdateVramImage(); _refreshTimer.Start(); } private void UpdateVramImage() { if (_vdp == null) return; // Loop through all 512 tiles in VRAM for (int tile = 0; tile < 512; tile++) { // Calculate grid position (32 columns, 16 rows) int tileX = (tile % 32) * 8; int tileY = (tile / 32) * 8; int tileAddress = tile * 32; // Decode the 8x8 pixels for (int y = 0; y < 8; y++) { byte bp0 = _vdp.VRAM[tileAddress + (y * 4) + 0]; byte bp1 = _vdp.VRAM[tileAddress + (y * 4) + 1]; byte bp2 = _vdp.VRAM[tileAddress + (y * 4) + 2]; byte bp3 = _vdp.VRAM[tileAddress + (y * 4) + 3]; for (int x = 0; x < 8; x++) { int shift = 7 - x; int colourIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) | (((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3); // Use the Sprite Palette (Offset 16) so characters and enemies are colored correctly int colour = _vdp.GetRgbColour(16, colourIndex); //int r = (smsColor & 0x03) * 85; //int g = ((smsColor >> 2) & 0x03) * 85; //int b = ((smsColor >> 4) & 0x03) * 85; // Background color (Index 0) is technically transparent for sprites, so we render it as magenta //if (colourIndex == 0) { int r = 255; int g = 0; int b = 255; } _pixelData[((tileY + y) * 256) + (tileX + x)] = colourIndex == 0 ? (255 << 24) | (255 << 16) | (0 << 8) | 255 : colour; } } } // Blast the array into the Bitmap var data = _vramBitmap.LockBits(new Rectangle(0, 0, 256, 128), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); Marshal.Copy(_pixelData, 0, data.Scan0, _pixelData.Length); _vramBitmap.UnlockBits(data); this.Invalidate(); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); // Draw it chunky and pixel-perfect e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half; e.Graphics.DrawImage(_vramBitmap, new Rectangle(0, 0, this.ClientSize.Width, this.ClientSize.Height)); } protected override void OnFormClosing(FormClosingEventArgs e) { _refreshTimer.Stop(); base.OnFormClosing(e); } } }