using System.Reflection; using System.Reflection.PortableExecutable; using Core; using System.Threading.Tasks; using System.Drawing.Imaging; using System.Runtime.InteropServices; using System.Windows.Forms; namespace Desktop { public partial class Form1 : Form { private SmsMachine _machine = null!; private DebuggerForm _debugger; private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb); private Task _emulatorTask; public bool IsRunning { get; private set; } = false; public ushort? Breakpoint { get => _machine?.Breakpoint; set { if (_machine != null) _machine.Breakpoint = value; } } public Form1() { InitializeComponent(); _machine = new SmsMachine(); PopulateIncludedRomsMenu(); this.KeyPreview = true; this.KeyDown += Form1_KeyDown; this.KeyUp += Form1_KeyUp; } private void DrawScreen() { // Rapidly copy our VDP FrameBuffer into the Windows Bitmap var data = _screenBitmap.LockBits(new Rectangle(0, 0, 256, 192), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); Marshal.Copy(_machine.VideoProcessor.FrameBuffer, 0, data.Scan0, _machine.VideoProcessor.FrameBuffer.Length); _screenBitmap.UnlockBits(data); // Update the PictureBox pbScreen.Image = _screenBitmap; } public void StartEmulator() { if (IsRunning) return; IsRunning = true; _emulatorTask = Task.Run(() => { while (IsRunning) { _machine.RunFrame(); Invoke((System.Windows.Forms.MethodInvoker)delegate { DrawScreen(); }); // Safety catch: If we hit a breakpoint while running, stop the loop! if (_machine.Breakpoint.HasValue && _machine.Cpu.PC == _machine.Breakpoint.Value) { IsRunning = false; // Optional: Force the debugger UI to update immediately so you see it! Invoke((System.Windows.Forms.MethodInvoker)delegate { _debugger?.uiUpdateTimer_Tick(null, EventArgs.Empty); }); } else { // Only throttle the speed if we are actively running Thread.Sleep(8); } } }); } public void StopEmulator() { IsRunning = false; } public void StepEmulator() { _machine.StepMachine(); } private async void LoadRomAndStart(string filePath) { StopEmulator(); if (_emulatorTask != null) { await _emulatorTask; } // 2. Load the file byte[] rom = File.ReadAllBytes(filePath); // 3. Jam it into the Sega Mapper _machine.LoadCartridge(rom); // 4. Update the Window title so it looks professional this.Text = $"Parsons Master System 2026 - {Path.GetFileNameWithoutExtension(filePath)}"; // 5. Turn the power on! StartEmulator(); } private void PopulateIncludedRomsMenu() { // The folder you used for Golden Axe Warrior string romsDirectory = @"C:\Parsons\Local Code Projects\ParsonsMasterSystem2026\Desktop\ROMS\"; if (Directory.Exists(romsDirectory)) { string[] romFiles = Directory.GetFiles(romsDirectory, "*.sms"); foreach (string file in romFiles) { // Create a new dropdown item for each .sms file it finds string romName = Path.GetFileNameWithoutExtension(file); ToolStripMenuItem romMenuItem = new ToolStripMenuItem(romName); // When clicked, pass the file path to our helper method romMenuItem.Click += (sender, e) => LoadRomAndStart(file); // Add it to the "Included" submenu (make sure the name matches your designer element!) includedToolStripMenuItem.DropDownItems.Add(romMenuItem); } } } private void selectROMToolStripMenuItem_Click(object sender, EventArgs e) { using (OpenFileDialog ofd = new OpenFileDialog()) { ofd.Title = "Select Master System ROM"; ofd.Filter = "SMS ROMs (*.sms)|*.sms|All Files (*.*)|*.*"; if (ofd.ShowDialog() == DialogResult.OK) { LoadRomAndStart(ofd.FileName); _machine.Reset(); } } } private void resetToolStripMenuItem_Click(object sender, EventArgs e) { _machine.Reset(); } private void debuggerToolStripMenuItem_Click(object sender, EventArgs e) { if (_debugger == null || _debugger.IsDisposed) { _debugger = new DebuggerForm(_machine.Cpu, _machine.MemoryBus, this); } _debugger.Show(); } private void includedToolStripMenuItem_Click(object sender, EventArgs e) { } private void exitToolStripMenuItem_Click(object sender, EventArgs e) { Environment.Exit(0); } private void Form1_KeyDown(object sender, KeyEventArgs e) { UpdateJoypad(e.KeyCode, true); } private void Form1_KeyUp(object sender, KeyEventArgs e) { UpdateJoypad(e.KeyCode, false); } private void UpdateJoypad(Keys key, bool isPressed) { if (_machine == null) return; byte bitMask = 0; // Map your keys to the Sega hardware bits switch (key) { case Keys.W: bitMask = 0x01; break; // Bit 0: Up case Keys.S: bitMask = 0x02; break; // Bit 1: Down case Keys.A: bitMask = 0x04; break; // Bit 2: Left case Keys.D: bitMask = 0x08; break; // Bit 3: Right case Keys.O: bitMask = 0x10; break; // Bit 4: Button 1 (Start/Action) case Keys.P: bitMask = 0x20; break; // Bit 5: Button 2 default: return; // Ignore any other keys } if (isPressed) { // Active-Low: Clear the specific bit to 0 using a bitwise AND with a NOT mask _machine.IoBus.Joypad1State &= (byte)~bitMask; } else { // Active-Low: Reset the specific bit to 1 using a bitwise OR mask _machine.IoBus.Joypad1State |= bitMask; } } } }