271 lines
10 KiB
C#
271 lines
10 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows.Forms;
|
|
using Core.Cpu;
|
|
using Core.Io;
|
|
using Core.Memory;
|
|
|
|
namespace Desktop
|
|
{
|
|
public partial class Form1 : Form
|
|
{
|
|
private Z80 _cpu = null!;
|
|
private MemoryBus _memoryBus = null!;
|
|
private IO_Bus _simpleIoBus = null!;
|
|
private TapManager _tapManager = null!;
|
|
private int _ulaFrameCount = 0;
|
|
|
|
// 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();
|
|
InitializeEmulator();
|
|
}
|
|
|
|
private void InitializeEmulator()
|
|
{
|
|
try
|
|
{
|
|
_memoryBus = new MemoryBus();
|
|
_simpleIoBus = new IO_Bus();
|
|
_tapManager = new TapManager();
|
|
_memoryBus.CrapRAMData();
|
|
byte[] romData = RomLoader.Load("48.rom");
|
|
_memoryBus.LoadRom(romData);
|
|
|
|
_cpu = new Z80(_memoryBus, _simpleIoBus, _tapManager);
|
|
|
|
// Pass 'this' so the DebuggerForm can talk back to this main window
|
|
DebuggerForm debugger = new DebuggerForm(_cpu, _memoryBus, this);
|
|
debugger.Show();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show($"Failed to initialize emulator:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
}
|
|
|
|
// Inside Desktop/Form1.cs
|
|
private void loadTAPToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
using (OpenFileDialog ofd = new OpenFileDialog())
|
|
{
|
|
ofd.Filter = "Spectrum TAP Files|*.tap";
|
|
if (ofd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
// The Desktop UI reads the file from the hard drive
|
|
byte[] tapBytes = System.IO.File.ReadAllBytes(ofd.FileName);
|
|
|
|
// The pure Core logic processes the bytes
|
|
_cpu._tapManager.LoadTapData(tapBytes);
|
|
|
|
//MessageBox.Show("Tape inserted! Type LOAD \"\" and press Enter.", "Tape Deck");
|
|
}
|
|
}
|
|
}
|
|
private void openSNAToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
using (OpenFileDialog ofd = new OpenFileDialog())
|
|
{
|
|
ofd.Filter = "Spectrum Snapshot Files (*.sna)|*.sna";
|
|
if (ofd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
byte[] snaBytes = System.IO.File.ReadAllBytes(ofd.FileName);
|
|
_cpu.LoadSNA(snaBytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Public so the Debugger's background thread can call it 50 times a second
|
|
public void RenderScreen()
|
|
{
|
|
_ulaFrameCount++;
|
|
bool invertFlashPhase = (_ulaFrameCount % 32) >= 16;
|
|
|
|
// --- NEW: Expanded screen size (32px border on all sides) ---
|
|
const int screenWidth = 320;
|
|
const int screenHeight = 256;
|
|
const int borderSize = 32;
|
|
|
|
int[] pixelData = new int[screenWidth * screenHeight];
|
|
|
|
// --- NEW: Fill the background with the Border Color ---
|
|
// (Note: The hardware border always uses standard brightness, never bright)
|
|
int currentBorderColor = SpectrumColors[_simpleIoBus.BorderColorIndex];
|
|
Array.Fill(pixelData, currentBorderColor);
|
|
|
|
// 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);
|
|
|
|
int y = ((offset & 0x0700) >> 8) |
|
|
((offset & 0x00E0) >> 2) |
|
|
((offset & 0x1800) >> 5);
|
|
|
|
int x = (offset & 0x001F) * 8;
|
|
|
|
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;
|
|
bool isFlashSet = (attr & 0x80) != 0;
|
|
|
|
int inkColor = SpectrumColors[ink + brightOffset];
|
|
int paperColor = SpectrumColors[paper + brightOffset];
|
|
|
|
if (isFlashSet && invertFlashPhase)
|
|
{
|
|
int temp = inkColor;
|
|
inkColor = paperColor;
|
|
paperColor = temp;
|
|
}
|
|
|
|
// Draw the 8 pixels
|
|
for (int bit = 0; bit < 8; bit++)
|
|
{
|
|
bool isPixelSet = (pixels & (1 << (7 - bit))) != 0;
|
|
|
|
// --- NEW: Add the 32px border offset to our X and Y calculations! ---
|
|
int renderY = y + borderSize;
|
|
int renderX = x + borderSize + bit;
|
|
|
|
// Map it to our new, wider pixel array
|
|
pixelData[(renderY * screenWidth) + renderX] = isPixelSet ? inkColor : paperColor;
|
|
}
|
|
}
|
|
|
|
// --- NEW: Update Bitmap dimensions to match the new 320x256 array ---
|
|
Bitmap bmp = new Bitmap(screenWidth, screenHeight, PixelFormat.Format32bppArgb);
|
|
BitmapData bmpData = bmp.LockBits(
|
|
new Rectangle(0, 0, screenWidth, screenHeight),
|
|
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;
|
|
}
|
|
private void UpdateMatrix(int row, int col, bool isPressed)
|
|
{
|
|
if (isPressed)
|
|
{
|
|
// Clear the bit to 0 (Active-Low = Pressed)
|
|
_simpleIoBus.KeyboardRows[row] &= (byte)~(1 << col);
|
|
}
|
|
else
|
|
{
|
|
// Set the bit back to 1 (Unpressed)
|
|
_simpleIoBus.KeyboardRows[row] |= (byte)(1 << col);
|
|
}
|
|
}
|
|
|
|
// Hook this to Form1's KeyDown event
|
|
protected override void OnKeyDown(KeyEventArgs e)
|
|
{
|
|
HandleKey(e.KeyCode, true);
|
|
base.OnKeyDown(e);
|
|
}
|
|
|
|
// Hook this to Form1's KeyUp event
|
|
protected override void OnKeyUp(KeyEventArgs e)
|
|
{
|
|
HandleKey(e.KeyCode, false);
|
|
base.OnKeyUp(e);
|
|
}
|
|
|
|
private void HandleKey(Keys key, bool isPressed)
|
|
{
|
|
switch (key)
|
|
{
|
|
//Row 0:
|
|
case Keys.ShiftKey: UpdateMatrix(0, 0, isPressed); break;
|
|
case Keys.Z: UpdateMatrix(0, 1, isPressed); break;
|
|
case Keys.X: UpdateMatrix(0, 2, isPressed); break;
|
|
case Keys.C: UpdateMatrix(0, 3, isPressed); break;
|
|
case Keys.V: UpdateMatrix(0, 4, isPressed); break;
|
|
|
|
// Row 1
|
|
case Keys.A: UpdateMatrix(1, 0, isPressed); break;
|
|
case Keys.S: UpdateMatrix(1, 1, isPressed); break;
|
|
case Keys.D: UpdateMatrix(1, 2, isPressed); break;
|
|
case Keys.F: UpdateMatrix(1, 3, isPressed); break;
|
|
case Keys.G: UpdateMatrix(1, 4, isPressed); break;
|
|
|
|
//Row 2:
|
|
case Keys.Q: UpdateMatrix(2, 0, isPressed); break;
|
|
case Keys.W: UpdateMatrix(2, 1, isPressed); break;
|
|
case Keys.E: UpdateMatrix(2, 2, isPressed); break;
|
|
case Keys.R: UpdateMatrix(2, 3, isPressed); break;
|
|
case Keys.T: UpdateMatrix(2, 4, isPressed); break;
|
|
|
|
// Row 3:
|
|
case Keys.D1: UpdateMatrix(3, 0, isPressed); break;
|
|
case Keys.D2: UpdateMatrix(3, 1, isPressed); break;
|
|
case Keys.D3: UpdateMatrix(3, 2, isPressed); break;
|
|
case Keys.D4: UpdateMatrix(3, 3, isPressed); break;
|
|
case Keys.D5: UpdateMatrix(3, 4, isPressed); break;
|
|
|
|
// Row 4:
|
|
case Keys.D0: UpdateMatrix(4, 0, isPressed); break;
|
|
case Keys.D9: UpdateMatrix(4, 1, isPressed); break;
|
|
case Keys.D8: UpdateMatrix(4, 2, isPressed); break;
|
|
case Keys.D7: UpdateMatrix(4, 3, isPressed); break;
|
|
case Keys.D6: UpdateMatrix(4, 4, isPressed); break;
|
|
|
|
// Row 5:
|
|
case Keys.P: UpdateMatrix(5, 0, isPressed); break;
|
|
case Keys.O: UpdateMatrix(5, 1, isPressed); break;
|
|
case Keys.I: UpdateMatrix(5, 2, isPressed); break;
|
|
case Keys.U: UpdateMatrix(5, 3, isPressed); break;
|
|
case Keys.Y: UpdateMatrix(5, 4, isPressed); break;
|
|
|
|
// Row 6
|
|
case Keys.Enter: UpdateMatrix(6, 0, isPressed); break;
|
|
case Keys.L: UpdateMatrix(6, 1, isPressed); break;
|
|
case Keys.K: UpdateMatrix(6, 2, isPressed); break;
|
|
case Keys.J: UpdateMatrix(6, 3, isPressed); break;
|
|
case Keys.H: UpdateMatrix(6, 4, isPressed); break;
|
|
|
|
// Row 7
|
|
case Keys.Space: UpdateMatrix(7, 0, isPressed); break;
|
|
case Keys.ControlKey: UpdateMatrix(7, 1, isPressed); break; // Symbol Shift
|
|
case Keys.M: UpdateMatrix(7, 2, isPressed); break;
|
|
case Keys.N: UpdateMatrix(7, 3, isPressed); break;
|
|
case Keys.B: UpdateMatrix(7, 4, isPressed); break;
|
|
}
|
|
}
|
|
}
|
|
} |