diff --git a/Core/Memory/MemoryBus.cs b/Core/Memory/MemoryBus.cs index c7d65d4..bf1595e 100644 --- a/Core/Memory/MemoryBus.cs +++ b/Core/Memory/MemoryBus.cs @@ -31,6 +31,16 @@ namespace Core.Memory _memory[address] = value; } + //Put some initial random data into RAM for authenticity + public void CrapRAMData() + { + Random random = new Random(); + for (int i = 0x4000; i < 0x5AFF; i++) + { + _memory[i] = (byte)random.Next(0x00, 0xFF); + } + } + // Load the ROM file public void LoadRom(byte[] romData) { diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 46f512f..b15862e 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -10,20 +10,23 @@ namespace Desktop { private readonly Z80 _cpu; private readonly MemoryBus _memoryBus; + private readonly Form1 _mainForm; private bool _isRunning = false; private ushort? _breakpoint = null; - public DebuggerForm(Z80 cpu, MemoryBus memoryBus) + public DebuggerForm(Z80 cpu, MemoryBus memoryBus, Form1 mainForm) { InitializeComponent(); _cpu = cpu; _memoryBus = memoryBus; + _mainForm = mainForm; // Set default memory view address txtMemoryStart.Text = "0000"; UpdateDisplay(); UpdateStackView(); UpdateDisassemblyView(); + _mainForm = mainForm; } private void btnStep_Click(object sender, EventArgs e) @@ -119,8 +122,14 @@ namespace Desktop nextFrameTargetTStates += TStatesPerFrame; frameCount++; - // 3. Throttle to real-time (50 frames per second = 20ms per frame) - long targetTimeMs = frameCount * 20; + //3 Render the screen + _mainForm.Invoke((MethodInvoker)delegate + { + _mainForm.RenderScreen(); + }); + + // 4. Throttle to real-time (50 frames per second = 20ms per frame) + long targetTimeMs = frameCount * 20; long elapsedMs = stopwatch.ElapsedMilliseconds; if (elapsedMs < targetTimeMs) @@ -131,7 +140,7 @@ namespace Desktop // Optional: Update the UI every 10 frames so you can watch it run safely // without overwhelming the WinForms rendering engine. - if (frameCount % 10 == 0) + if (frameCount % 1 == 0) { this.Invoke((MethodInvoker)delegate { diff --git a/Desktop/Form1.Designer.cs b/Desktop/Form1.Designer.cs index 4abfaa3..ced990d 100644 --- a/Desktop/Form1.Designer.cs +++ b/Desktop/Form1.Designer.cs @@ -28,18 +28,34 @@ /// private void InitializeComponent() { + picScreen = new PictureBox(); + ((System.ComponentModel.ISupportInitialize)picScreen).BeginInit(); SuspendLayout(); // + // picScreen + // + picScreen.BackColor = Color.Black; + picScreen.Location = new Point(12, 12); + picScreen.Name = "picScreen"; + picScreen.Size = new Size(768, 576); + picScreen.SizeMode = PictureBoxSizeMode.Zoom; + picScreen.TabIndex = 0; + picScreen.TabStop = false; + // // Form1 // AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(800, 450); + ClientSize = new Size(791, 596); + Controls.Add(picScreen); Name = "Form1"; Text = "Parsons Sinclair ZX Spectrum 48K - 2026"; + ((System.ComponentModel.ISupportInitialize)picScreen).EndInit(); ResumeLayout(false); } #endregion + + private PictureBox picScreen; } } diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index f86aefe..dea3f91 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -1,4 +1,7 @@ using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; using System.Windows.Forms; using Core.Cpu; using Core.Io; @@ -12,6 +15,30 @@ namespace Desktop private MemoryBus _memoryBus = null!; private SimpleIoBus _simpleIoBus = null!; + // The 16 physical colors of the ZX Spectrum (ARGB format) + private readonly int[] SpectrumColors = new int[] +{ + // Normal Colors (Bright = 0) + unchecked((int)0xFF000000), // 0: Black + unchecked((int)0xFF0000D7), // 1: Blue + unchecked((int)0xFFD70000), // 2: Red + unchecked((int)0xFFD700D7), // 3: Magenta + unchecked((int)0xFF00D700), // 4: Green + unchecked((int)0xFF00D7D7), // 5: Cyan + unchecked((int)0xFFD7D700), // 6: Yellow + unchecked((int)0xFFD7D7D7), // 7: White + + // Bright Colors (Bright = 1) + unchecked((int)0xFF000000), // 8: Bright Black + unchecked((int)0xFF0000FF), // 9: Bright Blue + unchecked((int)0xFFFF0000), // 10: Bright Red + unchecked((int)0xFFFF00FF), // 11: Bright Magenta + unchecked((int)0xFF00FF00), // 12: Bright Green + unchecked((int)0xFF00FFFF), // 13: Bright Cyan + unchecked((int)0xFFFFFF00), // 14: Bright Yellow + unchecked((int)0xFFFFFFFF) // 15: Bright White +}; + public Form1() { InitializeComponent(); @@ -22,27 +49,76 @@ namespace Desktop { try { - // 1. Initialize the memory bus _memoryBus = new MemoryBus(); _simpleIoBus = new SimpleIoBus(); - // 2. Load the ROM + _memoryBus.CrapRAMData(); byte[] romData = RomLoader.Load("48.rom"); - - // 3. Inject the ROM into the memory bus _memoryBus.LoadRom(romData); - // 4. Initialize the CPU with the populated memory _cpu = new Z80(_memoryBus, _simpleIoBus); - DebuggerForm debugger = new DebuggerForm(_cpu, _memoryBus); + // Pass 'this' so the DebuggerForm can talk back to this main window + DebuggerForm debugger = new DebuggerForm(_cpu, _memoryBus, this); debugger.Show(); - //MessageBox.Show("ROM loaded and CPU initialized successfully!", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show($"Failed to initialize emulator:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } + + // Public so the Debugger's background thread can call it 50 times a second + public void RenderScreen() + { + int[] pixelData = new int[256 * 192]; + + // Loop through the 6144 bytes of Pixel RAM + for (int offset = 0; offset < 6144; offset++) + { + ushort address = (ushort)(0x4000 + offset); + byte pixels = _memoryBus.Read(address); + + // Unwind the Sinclair interlace + int y = ((offset & 0x0700) >> 8) | + ((offset & 0x00E0) >> 2) | + ((offset & 0x1800) >> 5); + + int x = (offset & 0x001F) * 8; + + // Fetch Color Attributes + int attrRow = y / 8; + int attrCol = x / 8; + ushort attrAddress = (ushort)(0x5800 + (attrRow * 32) + attrCol); + byte attr = _memoryBus.Read(attrAddress); + + int ink = attr & 0x07; + int paper = (attr >> 3) & 0x07; + int brightOffset = (attr & 0x40) != 0 ? 8 : 0; + + int inkColor = SpectrumColors[ink + brightOffset]; + int paperColor = SpectrumColors[paper + brightOffset]; + + // Draw the 8 pixels + for (int bit = 0; bit < 8; bit++) + { + bool isPixelSet = (pixels & (1 << (7 - bit))) != 0; + pixelData[(y * 256) + (x + bit)] = isPixelSet ? inkColor : paperColor; + } + } + + // Blast it to the PictureBox + Bitmap bmp = new Bitmap(256, 192, PixelFormat.Format32bppArgb); + BitmapData bmpData = bmp.LockBits( + new Rectangle(0, 0, 256, 192), + ImageLockMode.WriteOnly, + bmp.PixelFormat); + + Marshal.Copy(pixelData, 0, bmpData.Scan0, pixelData.Length); + bmp.UnlockBits(bmpData); + + if (picScreen.Image != null) picScreen.Image.Dispose(); + picScreen.Image = bmp; + } } } \ No newline at end of file