diff --git a/Core/Io/IO_Bus.cs b/Core/Io/IO_Bus.cs index 58d2e99..3223645 100644 --- a/Core/Io/IO_Bus.cs +++ b/Core/Io/IO_Bus.cs @@ -6,7 +6,8 @@ namespace Core.Io { public class IO_Bus { - public byte BorderColorIndex { get; set; } = 7; + public byte BorderColourIndex { get; set; } = 7; + public byte KempstonState { get; set; } = 0x00; public bool BeeperState { get; private set; } = false; public byte[] KeyboardRows = new byte[8] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; TapManager _tapManager = new TapManager(); @@ -18,14 +19,13 @@ namespace Core.Io public byte ReadPort(ushort portAddress) { - // The Spectrum ULA responds to any even port address (where the lowest bit is 0) + //ULA responds to any even port address (where the lowest bit is 0) if ((portAddress & 0x01) == 0) //Port 0xFE) { - byte highByte = (byte)(portAddress >> 8); // The B register! + byte highByte = (byte)(portAddress >> 8); // The B register byte result = 0xFF; // Start assuming no keys are pressed - // The ROM pulls a specific bit low (0) in the high byte to request a row. - // Sometimes it pulls multiple bits low to scan multiple rows at once, so we AND the results. + if ((highByte & 0x01) == 0) result &= KeyboardRows[0]; // 0xFE: CAPS, Z, X, C, V if ((highByte & 0x02) == 0) result &= KeyboardRows[1]; // 0xFD: A, S, D, F, G if ((highByte & 0x04) == 0) result &= KeyboardRows[2]; // 0xFB: Q, W, E, R, T @@ -36,20 +36,18 @@ namespace Core.Io if ((highByte & 0x80) == 0) result &= KeyboardRows[7]; // 0x7F: SPACE, SYM, M, N, B if (_tapManager.EarBit) result |= 0x40; // Set Bit 6 high else result &= 0xBF; // Set Bit 6 low - - - //return result; - // The top 3 bits (5, 6, 7) are unused by the keyboard and usually return 1 on a real Spectrum + + //The top 3 bits (5, 6, 7) are unused by the keyboard and usually return 1 on a real Spectrum return (byte)(result | 0xA0); } - // Kempston Joystick Port + //Kempston Joystick Port if ((portAddress & 0xFF) == 0x1F) { - return 0x00; // 0x00 means no joystick connected/no buttons pressed + return KempstonState; } - // Return 0xFF for unhandled ports + //Return 0xFF for unhandled ports return 0x00; } @@ -59,7 +57,7 @@ namespace Core.Io if ((portAddress & 0x01) == 0) { // The bottom 3 bits (0-2) define the border color - BorderColorIndex = (byte)(portValue & 0x07); + BorderColourIndex = (byte)(portValue & 0x07); // Bit 4 controls the speaker BeeperState = (portValue & 0x10) != 0; diff --git a/Core/Io/ULA.cs b/Core/Io/ULA.cs index aeb41e9..cee64b9 100644 --- a/Core/Io/ULA.cs +++ b/Core/Io/ULA.cs @@ -17,7 +17,7 @@ namespace Core.Io private int _ulaFrameCount = 0; // The hardware color palette belongs to the ULA, not the Windows Form! - private readonly int[] SpectrumColors = new int[] + private readonly int[] SpectrumColours = new int[] { unchecked((int)0xFF000000), unchecked((int)0xFF0000D7), unchecked((int)0xFFD70000), unchecked((int)0xFFD700D7), @@ -59,7 +59,7 @@ namespace Core.Io // 2. Calculate our visual Y coordinate (0 to 255) for the bitmap array int renderY = scanline - 32; - int currentBorderColor = SpectrumColors[_simpleIoBus.BorderColorIndex]; + int currentBorderColor = SpectrumColours[_simpleIoBus.BorderColourIndex]; // --- Are we in the Top or Bottom Border? --- if (scanline < 64 || scanline > 255) @@ -102,8 +102,8 @@ namespace Core.Io int brightOffset = (attr & 0x40) != 0 ? 8 : 0; bool isFlashSet = (attr & 0x80) != 0; - int inkColor = SpectrumColors[ink + brightOffset]; - int paperColor = SpectrumColors[paper + brightOffset]; + int inkColor = SpectrumColours[ink + brightOffset]; + int paperColor = SpectrumColours[paper + brightOffset]; if (isFlashSet && invertFlashPhase) { diff --git a/Core/SpectrumMachine.cs b/Core/SpectrumMachine.cs index 8f8264a..728b5e1 100644 --- a/Core/SpectrumMachine.cs +++ b/Core/SpectrumMachine.cs @@ -292,7 +292,7 @@ namespace Core // Cpu.InterruptMode = snaData[25]; // THE BUG FIX: Restore the ULA Border Color! - IoBus.BorderColorIndex = snaData[26]; + IoBus.BorderColourIndex = snaData[26]; // 2. Load the 48K RAM Dump for (int i = 0; i < 49152; i++) @@ -340,7 +340,7 @@ namespace Core bw.Write(Cpu.AF.Low); bw.Write(Cpu.AF.High); bw.Write((byte)(Cpu.SP & 0xFF)); bw.Write((byte)(Cpu.SP >> 8)); bw.Write((byte)Cpu.InterruptMode); - bw.Write(IoBus.BorderColorIndex); + bw.Write(IoBus.BorderColourIndex); for (int i = 0x4000; i <= 0xFFFF; i++) { diff --git a/Desktop/Desktop.csproj b/Desktop/Desktop.csproj index d4a39af..caa733e 100644 --- a/Desktop/Desktop.csproj +++ b/Desktop/Desktop.csproj @@ -56,6 +56,7 @@ + diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 44522f4..94eb401 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -1,7 +1,7 @@ -using Core; // <-- This gives us access to SpectrumMachine +using Core; using Core.Io; -using System.Diagnostics; using System.Drawing.Imaging; +using Vortice.XInput; using System.Reflection; using System.Runtime.InteropServices; @@ -17,12 +17,6 @@ namespace Desktop private Bitmap _screenBitmap = null!; private string _baseTitle = ""; - // ==================================================================== - // DEBUGGER PASS-THROUGHS - // ==================================================================== - // The DebuggerForm still looks at Form1 for data. These properties act - // as a bridge, instantly passing the request down to the actual engine! - public ushort? Breakpoint { get => _machine?.Breakpoint; @@ -84,11 +78,12 @@ namespace Desktop private void Machine_OnFrameReady(int[] pixels) { - // We must use BeginInvoke because the Machine is running on a background thread! + PollGamepad(); + this.BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { UpdateScreenBitmap(pixels); - this.Invalidate(); // Triggers OnPaint + this.Invalidate(); }); } @@ -384,6 +379,35 @@ namespace Desktop _machine.Resume(); } - + //Control pad method + + private void PollGamepad() + { + // Only check Player 1 (Index 0) + if (XInput.GetState(0, out State state)) + { + byte kempston = 0x00; + Gamepad gamepad = state.Gamepad; + + // Map D-Pad to Kempston Bits + if ((gamepad.Buttons & GamepadButtons.DPadRight) != 0) kempston |= 0x01; // Bit 0 + if ((gamepad.Buttons & GamepadButtons.DPadLeft) != 0) kempston |= 0x02; // Bit 1 + if ((gamepad.Buttons & GamepadButtons.DPadDown) != 0) kempston |= 0x04; // Bit 2 + if ((gamepad.Buttons & GamepadButtons.DPadUp) != 0) kempston |= 0x08; // Bit 3 + + // Map A Button (or whichever you prefer) to Fire + if ((gamepad.Buttons & GamepadButtons.A) != 0) kempston |= 0x10; // Bit 4 + + // Send the final byte down to the emulator core! + _machine.IoBus.KempstonState = kempston; + } + else + { + // Controller disconnected, zero it out + _machine.IoBus.KempstonState = 0x00; + } + } + + } } \ No newline at end of file