From b5695b5c2fdcb64681bfd54d6e7cec61e082de95 Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Fri, 22 May 2026 16:40:33 +0100 Subject: [PATCH] Some fixes and code refactoring --- Core/Cpu/Z80.cs | 19 ++- Core/Memory/SmsMemoryBus.cs | 7 +- Core/Video/SmsVdp.cs | 18 ++- Desktop/DebuggerForm.Designer.cs | 211 +++++++++++++++---------------- Desktop/DebuggerForm.cs | 6 +- Desktop/Form1.Designer.cs | 47 +++---- Desktop/Form1.cs | 115 ++++++++++------- 7 files changed, 233 insertions(+), 190 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index 04a3e00..16cb436 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -25,10 +25,12 @@ namespace Core.Cpu public int InterruptMode { get; private set; } = 0; + // Interrupt Flip-Flops public bool IFF1 { get; private set; } = false; public bool IFF2 { get; private set; } = false; public bool InterruptRequested { get; private set; } = false; + private bool _eiPending = false; // Main Register Set public RegisterPair AF; @@ -213,7 +215,8 @@ namespace Core.Cpu } public int Step() - { + { + bool triggerEi = _eiPending; // Fetch the next opcode and increment the Program Counter byte opcode = ReadMemory(PC++); @@ -221,6 +224,13 @@ namespace Core.Cpu int tStates = ExecuteOpcode(opcode); TotalTStates += tStates; + if (triggerEi) + { + IFF1 = true; + IFF2 = true; + _eiPending = false; + } + // Decode and execute return tStates; } @@ -240,9 +250,6 @@ namespace Core.Cpu $"C:{f & 1}"; } - // ========================================================================= - // MATH AND LOGIC HELPERS - // ========================================================================= private void SubA(byte value, bool isCompare) { @@ -1272,6 +1279,7 @@ namespace Core.Cpu case 0xF3: // DI IFF1 = false; IFF2 = false; + _eiPending = false; return 4; case 0xf5: //push af Push(AF.Word); @@ -1283,8 +1291,7 @@ namespace Core.Cpu SP = HL.Word; return 6; case 0xFB: // EI - IFF1 = true; - IFF2 = true; + _eiPending = true; return 4; case 0xFD: return ExecuteFDPrefix(); diff --git a/Core/Memory/SmsMemoryBus.cs b/Core/Memory/SmsMemoryBus.cs index 178dabb..6cb5bfa 100644 --- a/Core/Memory/SmsMemoryBus.cs +++ b/Core/Memory/SmsMemoryBus.cs @@ -42,6 +42,11 @@ namespace Core.Memory public byte Read(ushort address) { + if (address < 0xC000 && !_isCartridgeLoaded) + { + return 0xFF; + } + if (address < 0x4000) { if (address < 0x0400) @@ -65,7 +70,7 @@ namespace Core.Memory return _cartridgeRom[(_romBank2 * 0x4000) + (address - 0x8000)]; } - // THE FIX 1: Bitwise AND perfectly forces 0xE000-0xFFFF to mirror down to 0xC000! + // Bitwise AND perfectly forces 0xE000-0xFFFF to mirror down to 0xC000! return _workRam[address & 0x1FFF]; } diff --git a/Core/Video/SmsVdp.cs b/Core/Video/SmsVdp.cs index 529b8ac..5c6cc6f 100644 --- a/Core/Video/SmsVdp.cs +++ b/Core/Video/SmsVdp.cs @@ -40,7 +40,7 @@ namespace Core.Video _isSecondControlByte = false; // Reading data resets the control latch byte value = _readBuffer; _readBuffer = VRAM[_controlWord & 0x3FFF]; - _controlWord++; + IncrementVdpAddress(); return value; } @@ -82,7 +82,7 @@ namespace Core.Video } // THE FIX: The pointer MUST auto-increment so the CPU can blast data fast! - _controlWord++; + IncrementVdpAddress(); } public void WriteControlPort(byte value) // Port 0xBF @@ -104,7 +104,7 @@ namespace Core.Video if (command == 0) // Code 0: Prep for VRAM Read { _readBuffer = VRAM[_controlWord & 0x3FFF]; - _controlWord++; + IncrementVdpAddress(); } else if (command == 2) // Code 2: Write to Internal VDP Register { @@ -116,6 +116,18 @@ namespace Core.Video } } + private void IncrementVdpAddress() + { + // The VDP address register is only 14 bits + // When it increments past 0x3FFF, it rolls over to 0x0000. + // It can't overflow and corrupt the 2-bit command register (bits 14 and 15). + ushort address = (ushort)(_controlWord & 0x3FFF); + ushort command = (ushort)(_controlWord & 0xC000); + + address = (ushort)((address + 1) & 0x3FFF); + _controlWord = (ushort)(command | address); + } + public byte ReadVCounter() { // NTSC Math: 262 lines. Counts 0 to 218, jumps to 213 (0xD5), counts to 255. diff --git a/Desktop/DebuggerForm.Designer.cs b/Desktop/DebuggerForm.Designer.cs index 47266da..22d6b24 100644 --- a/Desktop/DebuggerForm.Designer.cs +++ b/Desktop/DebuggerForm.Designer.cs @@ -56,102 +56,102 @@ lblFPS = new Label(); lblFrameTime = new Label(); richTextBox1 = new RichTextBox(); - button1 = new Button(); + btnCpuStep = new Button(); CpuRun = new Button(); groupBox1 = new GroupBox(); - lblTone0 = new Label(); - lblNoise = new Label(); - lblTone2 = new Label(); lblTone1 = new Label(); + lblTone2 = new Label(); + lblNoise = new Label(); + lblTone0 = new Label(); groupBox1.SuspendLayout(); SuspendLayout(); // // lblAF // lblAF.AutoSize = true; - lblAF.Location = new Point(12, 9); + lblAF.Location = new Point(10, 7); lblAF.Margin = new Padding(2, 0, 2, 0); lblAF.Name = "lblAF"; - lblAF.Size = new Size(33, 25); + lblAF.Size = new Size(26, 20); lblAF.TabIndex = 0; lblAF.Text = "AF"; // // lblBC // lblBC.AutoSize = true; - lblBC.Location = new Point(11, 60); + lblBC.Location = new Point(9, 48); lblBC.Margin = new Padding(2, 0, 2, 0); lblBC.Name = "lblBC"; - lblBC.Size = new Size(33, 25); + lblBC.Size = new Size(27, 20); lblBC.TabIndex = 1; lblBC.Text = "BC"; // // lblDE // lblDE.AutoSize = true; - lblDE.Location = new Point(12, 125); + lblDE.Location = new Point(10, 100); lblDE.Margin = new Padding(2, 0, 2, 0); lblDE.Name = "lblDE"; - lblDE.Size = new Size(34, 25); + lblDE.Size = new Size(28, 20); lblDE.TabIndex = 2; lblDE.Text = "DE"; // // lblHL // lblHL.AutoSize = true; - lblHL.Location = new Point(12, 188); + lblHL.Location = new Point(10, 150); lblHL.Margin = new Padding(2, 0, 2, 0); lblHL.Name = "lblHL"; - lblHL.Size = new Size(33, 25); + lblHL.Size = new Size(27, 20); lblHL.TabIndex = 3; lblHL.Text = "HL"; // // lblPC // lblPC.AutoSize = true; - lblPC.Location = new Point(11, 250); + lblPC.Location = new Point(9, 200); lblPC.Margin = new Padding(2, 0, 2, 0); lblPC.Name = "lblPC"; - lblPC.Size = new Size(33, 25); + lblPC.Size = new Size(26, 20); lblPC.TabIndex = 4; lblPC.Text = "PC"; // // lblSP // lblSP.AutoSize = true; - lblSP.Location = new Point(11, 315); + lblSP.Location = new Point(9, 252); lblSP.Margin = new Padding(2, 0, 2, 0); lblSP.Name = "lblSP"; - lblSP.Size = new Size(32, 25); + lblSP.Size = new Size(25, 20); lblSP.TabIndex = 6; lblSP.Text = "SP"; // // lblFlags // lblFlags.AutoSize = true; - lblFlags.Location = new Point(110, 65); + lblFlags.Location = new Point(88, 52); lblFlags.Margin = new Padding(2, 0, 2, 0); lblFlags.Name = "lblFlags"; - lblFlags.Size = new Size(53, 25); + lblFlags.Size = new Size(43, 20); lblFlags.TabIndex = 7; lblFlags.Text = "Flags"; // // lblTStates // lblTStates.AutoSize = true; - lblTStates.Location = new Point(110, 9); + lblTStates.Location = new Point(88, 7); lblTStates.Margin = new Padding(2, 0, 2, 0); lblTStates.Name = "lblTStates"; - lblTStates.Size = new Size(75, 25); + lblTStates.Size = new Size(63, 20); lblTStates.TabIndex = 8; lblTStates.Text = "T-States"; // // txtMemoryStart // - txtMemoryStart.Location = new Point(375, 26); + txtMemoryStart.Location = new Point(300, 21); txtMemoryStart.Margin = new Padding(2); txtMemoryStart.Name = "txtMemoryStart"; - txtMemoryStart.Size = new Size(150, 31); + txtMemoryStart.Size = new Size(121, 27); txtMemoryStart.TabIndex = 9; txtMemoryStart.Text = "Memory Start"; txtMemoryStart.TextAlign = HorizontalAlignment.Center; @@ -159,10 +159,10 @@ // // btnRefreshMemory // - btnRefreshMemory.Location = new Point(531, 26); + btnRefreshMemory.Location = new Point(425, 21); btnRefreshMemory.Margin = new Padding(2); btnRefreshMemory.Name = "btnRefreshMemory"; - btnRefreshMemory.Size = new Size(112, 34); + btnRefreshMemory.Size = new Size(90, 27); btnRefreshMemory.TabIndex = 14; btnRefreshMemory.Text = "Refresh Memory"; btnRefreshMemory.UseVisualStyleBackColor = true; @@ -170,117 +170,109 @@ // // txtMemoryView // - txtMemoryView.Location = new Point(110, 101); + txtMemoryView.Location = new Point(88, 81); txtMemoryView.Margin = new Padding(2); txtMemoryView.Name = "txtMemoryView"; - txtMemoryView.Size = new Size(553, 543); + txtMemoryView.Size = new Size(443, 435); txtMemoryView.TabIndex = 15; txtMemoryView.Text = "Memory View Window"; // // lstDisassembly // lstDisassembly.FormattingEnabled = true; - lstDisassembly.ItemHeight = 25; - lstDisassembly.Location = new Point(709, 10); + lstDisassembly.Location = new Point(567, 8); lstDisassembly.Margin = new Padding(2); lstDisassembly.Name = "lstDisassembly"; - lstDisassembly.Size = new Size(314, 329); + lstDisassembly.Size = new Size(252, 264); lstDisassembly.TabIndex = 16; // // lstStack // lstStack.FormattingEnabled = true; - lstStack.ItemHeight = 25; - lstStack.Location = new Point(1029, 10); + lstStack.Location = new Point(823, 8); lstStack.Margin = new Padding(2); lstStack.Name = "lstStack"; - lstStack.Size = new Size(162, 329); + lstStack.Size = new Size(130, 264); lstStack.TabIndex = 17; // // label1 // label1.AutoSize = true; - label1.Location = new Point(710, 372); - label1.Margin = new Padding(4, 0, 4, 0); + label1.Location = new Point(568, 298); label1.Name = "label1"; - label1.Size = new Size(97, 25); + label1.Size = new Size(81, 20); label1.TabIndex = 19; label1.Text = "Breakpoint"; // // txtBreakpoint // - txtBreakpoint.Location = new Point(819, 369); - txtBreakpoint.Margin = new Padding(4); + txtBreakpoint.Location = new Point(655, 295); txtBreakpoint.Name = "txtBreakpoint"; - txtBreakpoint.Size = new Size(155, 31); + txtBreakpoint.Size = new Size(125, 27); txtBreakpoint.TabIndex = 20; // // label2 // label2.AutoSize = true; - label2.Location = new Point(291, 26); - label2.Margin = new Padding(4, 0, 4, 0); + label2.Location = new Point(233, 21); label2.Name = "label2"; - label2.Size = new Size(77, 25); + label2.Size = new Size(62, 20); label2.TabIndex = 21; label2.Text = "Address"; // // lblIX // lblIX.AutoSize = true; - lblIX.Location = new Point(11, 372); + lblIX.Location = new Point(9, 298); lblIX.Margin = new Padding(2, 0, 2, 0); lblIX.Name = "lblIX"; - lblIX.Size = new Size(28, 25); + lblIX.Size = new Size(22, 20); lblIX.TabIndex = 22; lblIX.Text = "IX"; // // lblIY // lblIY.AutoSize = true; - lblIY.Location = new Point(12, 430); + lblIY.Location = new Point(10, 344); lblIY.Margin = new Padding(2, 0, 2, 0); lblIY.Name = "lblIY"; - lblIY.Size = new Size(27, 25); + lblIY.Size = new Size(21, 20); lblIY.TabIndex = 23; lblIY.Text = "IY"; // // lblIff1 // lblIff1.AutoSize = true; - lblIff1.Location = new Point(11, 533); - lblIff1.Margin = new Padding(4, 0, 4, 0); + lblIff1.Location = new Point(9, 426); lblIff1.Name = "lblIff1"; - lblIff1.Size = new Size(45, 25); + lblIff1.Size = new Size(35, 20); lblIff1.TabIndex = 24; lblIff1.Text = "IFF1"; // // lblIff2 // lblIff2.AutoSize = true; - lblIff2.Location = new Point(11, 586); - lblIff2.Margin = new Padding(4, 0, 4, 0); + lblIff2.Location = new Point(9, 469); lblIff2.Name = "lblIff2"; - lblIff2.Size = new Size(45, 25); + lblIff2.Size = new Size(35, 20); lblIff2.TabIndex = 25; lblIff2.Text = "IFF2"; // // lblIE // lblIE.AutoSize = true; - lblIE.Location = new Point(11, 486); - lblIE.Margin = new Padding(4, 0, 4, 0); + lblIE.Location = new Point(9, 389); lblIE.Name = "lblIE"; - lblIE.Size = new Size(33, 25); + lblIE.Size = new Size(26, 20); lblIE.TabIndex = 26; lblIE.Text = "IM"; // // btnReset // - btnReset.Location = new Point(814, 409); + btnReset.Location = new Point(651, 327); btnReset.Margin = new Padding(2); btnReset.Name = "btnReset"; - btnReset.Size = new Size(165, 34); + btnReset.Size = new Size(132, 27); btnReset.TabIndex = 27; btnReset.Text = "Set Breakpoint"; btnReset.UseVisualStyleBackColor = true; @@ -295,61 +287,58 @@ // lblFrames // lblFrames.AutoSize = true; - lblFrames.Location = new Point(14, 660); + lblFrames.Location = new Point(11, 528); lblFrames.Margin = new Padding(2, 0, 2, 0); lblFrames.Name = "lblFrames"; - lblFrames.Size = new Size(149, 25); + lblFrames.Size = new Size(124, 20); lblFrames.TabIndex = 28; lblFrames.Text = "Frames Rendered"; // // lblFPS // lblFPS.AutoSize = true; - lblFPS.Location = new Point(129, 757); + lblFPS.Location = new Point(103, 606); lblFPS.Margin = new Padding(2, 0, 2, 0); lblFPS.Name = "lblFPS"; - lblFPS.Size = new Size(41, 25); + lblFPS.Size = new Size(32, 20); lblFPS.TabIndex = 29; lblFPS.Text = "FPS"; // // lblFrameTime // lblFrameTime.AutoSize = true; - lblFrameTime.Location = new Point(60, 706); + lblFrameTime.Location = new Point(48, 565); lblFrameTime.Margin = new Padding(2, 0, 2, 0); lblFrameTime.Name = "lblFrameTime"; - lblFrameTime.Size = new Size(104, 25); + lblFrameTime.Size = new Size(87, 20); lblFrameTime.TabIndex = 30; lblFrameTime.Text = "Frame Time"; // // richTextBox1 // richTextBox1.Enabled = false; - richTextBox1.Location = new Point(682, 474); - richTextBox1.Margin = new Padding(4); + richTextBox1.Location = new Point(546, 379); richTextBox1.Name = "richTextBox1"; richTextBox1.ReadOnly = true; - richTextBox1.Size = new Size(379, 159); + richTextBox1.Size = new Size(304, 128); richTextBox1.TabIndex = 32; richTextBox1.Text = "Sega Master System Memory Map:\n0x0000 - 0x3FFF: ROM Slot 0 (16KB)\n0x4000 - 0x7FFF: ROM Slot 1 (16KB)\n0x8000 - 0xBFFF: ROM Slot 2 (16KB)\n0xC000 - 0xDFFF: System RAM (8KB)\n0xE000 - 0xFFFF: RAM Mirror"; // - // button1 + // btnCpuStep // - button1.Location = new Point(1075, 756); - button1.Margin = new Padding(4); - button1.Name = "button1"; - button1.Size = new Size(118, 36); - button1.TabIndex = 33; - button1.Text = "CPU Step"; - button1.UseVisualStyleBackColor = true; - button1.Click += btnStep_Click; + btnCpuStep.Location = new Point(860, 605); + btnCpuStep.Name = "btnCpuStep"; + btnCpuStep.Size = new Size(94, 29); + btnCpuStep.TabIndex = 33; + btnCpuStep.Text = "CPU Step"; + btnCpuStep.UseVisualStyleBackColor = true; + btnCpuStep.Click += btnStep_Click; // // CpuRun // - CpuRun.Location = new Point(943, 757); - CpuRun.Margin = new Padding(4); + CpuRun.Location = new Point(754, 606); CpuRun.Name = "CpuRun"; - CpuRun.Size = new Size(118, 36); + CpuRun.Size = new Size(94, 29); CpuRun.TabIndex = 34; CpuRun.Text = "CPU Run"; CpuRun.UseVisualStyleBackColor = true; @@ -361,57 +350,63 @@ groupBox1.Controls.Add(lblTone2); groupBox1.Controls.Add(lblNoise); groupBox1.Controls.Add(lblTone0); - groupBox1.Location = new Point(266, 658); + groupBox1.Location = new Point(213, 526); + groupBox1.Margin = new Padding(2, 2, 2, 2); groupBox1.Name = "groupBox1"; - groupBox1.Size = new Size(397, 222); + groupBox1.Padding = new Padding(2, 2, 2, 2); + groupBox1.Size = new Size(318, 178); groupBox1.TabIndex = 35; groupBox1.TabStop = false; groupBox1.Text = "Audio (SN76489)"; // - // lblTone0 + // lblTone1 // - lblTone0.AutoSize = true; - lblTone0.Location = new Point(25, 48); - lblTone0.Name = "lblTone0"; - lblTone0.Size = new Size(59, 25); - lblTone0.TabIndex = 0; - lblTone0.Text = "Tone0"; - // - // lblNoise - // - lblNoise.AutoSize = true; - lblNoise.Location = new Point(25, 160); - lblNoise.Name = "lblNoise"; - lblNoise.Size = new Size(57, 25); - lblNoise.TabIndex = 1; - lblNoise.Text = "Noise"; + lblTone1.AutoSize = true; + lblTone1.Location = new Point(20, 66); + lblTone1.Margin = new Padding(2, 0, 2, 0); + lblTone1.Name = "lblTone1"; + lblTone1.Size = new Size(49, 20); + lblTone1.TabIndex = 3; + lblTone1.Text = "Tone1"; // // lblTone2 // lblTone2.AutoSize = true; - lblTone2.Location = new Point(25, 122); + lblTone2.Location = new Point(20, 98); + lblTone2.Margin = new Padding(2, 0, 2, 0); lblTone2.Name = "lblTone2"; - lblTone2.Size = new Size(59, 25); + lblTone2.Size = new Size(49, 20); lblTone2.TabIndex = 2; lblTone2.Text = "Tone2"; // - // lblTone1 + // lblNoise // - lblTone1.AutoSize = true; - lblTone1.Location = new Point(25, 83); - lblTone1.Name = "lblTone1"; - lblTone1.Size = new Size(59, 25); - lblTone1.TabIndex = 3; - lblTone1.Text = "Tone1"; + lblNoise.AutoSize = true; + lblNoise.Location = new Point(20, 128); + lblNoise.Margin = new Padding(2, 0, 2, 0); + lblNoise.Name = "lblNoise"; + lblNoise.Size = new Size(47, 20); + lblNoise.TabIndex = 1; + lblNoise.Text = "Noise"; + // + // lblTone0 + // + lblTone0.AutoSize = true; + lblTone0.Location = new Point(20, 38); + lblTone0.Margin = new Padding(2, 0, 2, 0); + lblTone0.Name = "lblTone0"; + lblTone0.Size = new Size(49, 20); + lblTone0.TabIndex = 0; + lblTone0.Text = "Tone0"; // // DebuggerForm // - AutoScaleDimensions = new SizeF(10F, 25F); + AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(1206, 892); + ClientSize = new Size(965, 714); Controls.Add(groupBox1); Controls.Add(CpuRun); - Controls.Add(button1); + Controls.Add(btnCpuStep); Controls.Add(richTextBox1); Controls.Add(lblFrameTime); Controls.Add(lblFPS); @@ -475,7 +470,7 @@ private Label lblFPS; private Label lblFrameTime; private RichTextBox richTextBox1; - private Button button1; + private Button btnCpuStep; private Button CpuRun; public System.Windows.Forms.Timer uiUpdateTimer; private GroupBox groupBox1; diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 2e934d7..cea06dc 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -27,6 +27,7 @@ namespace Desktop UpdateStackView(); UpdateDisassemblyView(); _mainForm = mainForm; + CpuRun.Text = _mainForm.IsRunning ? "CPU Stop" : "CPU Run"; } private void CpuRun_Click(object sender, EventArgs e) @@ -35,7 +36,6 @@ namespace Desktop { // Stop the machine _mainForm.StopEmulator(); - CpuRun.Text = "CPU Run"; // Stop the live UI updates and do one final manual refresh uiUpdateTimer.Stop(); @@ -45,12 +45,12 @@ namespace Desktop { // Start the machine _mainForm.StartEmulator(); - CpuRun.Text = "CPU Stop"; // Start the timer so the debugger screen updates automatically // (Make sure your uiUpdateTimer Interval in the designer is set to something like 100ms) uiUpdateTimer.Start(); } + CpuRun.Text = _mainForm.IsRunning ? "CPU Stop" : "CPU Run"; } private void btnStep_Click(object sender, EventArgs e) @@ -92,6 +92,8 @@ namespace Desktop // Current Emulator State private void UpdateDisplay() { + CpuRun.Text = _mainForm.IsRunning ? "CPU Stop" : "CPU Run"; + btnCpuStep.Enabled = _mainForm.IsRunning ? false : true; lblAF.Text = $"AF: {_cpu.AF.Word:X4}"; lblBC.Text = $"BC: {_cpu.BC.Word:X4}"; lblDE.Text = $"DE: {_cpu.DE.Word:X4}"; diff --git a/Desktop/Form1.Designer.cs b/Desktop/Form1.Designer.cs index d1896ba..795774f 100644 --- a/Desktop/Form1.Designer.cs +++ b/Desktop/Form1.Designer.cs @@ -32,6 +32,8 @@ fileToolStripMenuItem = new ToolStripMenuItem(); openToolStripMenuItem = new ToolStripMenuItem(); includedToolStripMenuItem = new ToolStripMenuItem(); + masterSystemToolStripMenuItem = new ToolStripMenuItem(); + gameGearToolStripMenuItem = new ToolStripMenuItem(); selectROMToolStripMenuItem1 = new ToolStripMenuItem(); exitToolStripMenuItem = new ToolStripMenuItem(); viewToolStripMenuItem = new ToolStripMenuItem(); @@ -44,8 +46,6 @@ turboModeToolStripMenuItem = new ToolStripMenuItem(); helpToolStripMenuItem = new ToolStripMenuItem(); aboutToolStripMenuItem = new ToolStripMenuItem(); - masterSystemToolStripMenuItem = new ToolStripMenuItem(); - gameGearToolStripMenuItem = new ToolStripMenuItem(); menuStrip1.SuspendLayout(); SuspendLayout(); // @@ -71,27 +71,39 @@ // openToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { includedToolStripMenuItem, selectROMToolStripMenuItem1 }); openToolStripMenuItem.Name = "openToolStripMenuItem"; - openToolStripMenuItem.Size = new Size(224, 26); + openToolStripMenuItem.Size = new Size(128, 26); openToolStripMenuItem.Text = "Open"; // // includedToolStripMenuItem // includedToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { masterSystemToolStripMenuItem, gameGearToolStripMenuItem }); includedToolStripMenuItem.Name = "includedToolStripMenuItem"; - includedToolStripMenuItem.Size = new Size(224, 26); + includedToolStripMenuItem.Size = new Size(178, 26); includedToolStripMenuItem.Text = "Included"; // + // masterSystemToolStripMenuItem + // + masterSystemToolStripMenuItem.Name = "masterSystemToolStripMenuItem"; + masterSystemToolStripMenuItem.Size = new Size(188, 26); + masterSystemToolStripMenuItem.Text = "Master System"; + // + // gameGearToolStripMenuItem + // + gameGearToolStripMenuItem.Name = "gameGearToolStripMenuItem"; + gameGearToolStripMenuItem.Size = new Size(188, 26); + gameGearToolStripMenuItem.Text = "Game Gear"; + // // selectROMToolStripMenuItem1 // selectROMToolStripMenuItem1.Name = "selectROMToolStripMenuItem1"; - selectROMToolStripMenuItem1.Size = new Size(224, 26); + selectROMToolStripMenuItem1.Size = new Size(178, 26); selectROMToolStripMenuItem1.Text = "Select ROM..."; selectROMToolStripMenuItem1.Click += selectROMToolStripMenuItem_Click; // // exitToolStripMenuItem // exitToolStripMenuItem.Name = "exitToolStripMenuItem"; - exitToolStripMenuItem.Size = new Size(224, 26); + exitToolStripMenuItem.Size = new Size(128, 26); exitToolStripMenuItem.Text = "Exit"; exitToolStripMenuItem.Click += exitToolStripMenuItem_Click; // @@ -126,28 +138,28 @@ // resetToolStripMenuItem // resetToolStripMenuItem.Name = "resetToolStripMenuItem"; - resetToolStripMenuItem.Size = new Size(224, 26); + resetToolStripMenuItem.Size = new Size(174, 26); resetToolStripMenuItem.Text = "Reset"; resetToolStripMenuItem.Click += resetToolStripMenuItem_Click; // // saveStateToolStripMenuItem // saveStateToolStripMenuItem.Name = "saveStateToolStripMenuItem"; - saveStateToolStripMenuItem.Size = new Size(224, 26); + saveStateToolStripMenuItem.Size = new Size(174, 26); saveStateToolStripMenuItem.Text = "Save State"; saveStateToolStripMenuItem.Click += saveStateToolStripMenuItem_Click; // // loadStateToolStripMenuItem // loadStateToolStripMenuItem.Name = "loadStateToolStripMenuItem"; - loadStateToolStripMenuItem.Size = new Size(224, 26); + loadStateToolStripMenuItem.Size = new Size(174, 26); loadStateToolStripMenuItem.Text = "Load State"; loadStateToolStripMenuItem.Click += loadStateToolStripMenuItem_Click; // // turboModeToolStripMenuItem // turboModeToolStripMenuItem.Name = "turboModeToolStripMenuItem"; - turboModeToolStripMenuItem.Size = new Size(224, 26); + turboModeToolStripMenuItem.Size = new Size(174, 26); turboModeToolStripMenuItem.Text = "Turbo Mode"; turboModeToolStripMenuItem.Click += turboModeToolStripMenuItem_Click; // @@ -161,20 +173,9 @@ // aboutToolStripMenuItem // aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; - aboutToolStripMenuItem.Size = new Size(133, 26); + aboutToolStripMenuItem.Size = new Size(224, 26); aboutToolStripMenuItem.Text = "About"; - // - // masterSystemToolStripMenuItem - // - masterSystemToolStripMenuItem.Name = "masterSystemToolStripMenuItem"; - masterSystemToolStripMenuItem.Size = new Size(224, 26); - masterSystemToolStripMenuItem.Text = "Master System"; - // - // gameGearToolStripMenuItem - // - gameGearToolStripMenuItem.Name = "gameGearToolStripMenuItem"; - gameGearToolStripMenuItem.Size = new Size(224, 26); - gameGearToolStripMenuItem.Text = "Game Gear"; + aboutToolStripMenuItem.Click += aboutToolStripMenuItem_Click; // // ParsonsForm1 // diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 799a77a..272d0d2 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -59,6 +59,7 @@ namespace Desktop _machine = new SmsMachine(); _audioPlayer = new NAudioPlayer(); _machine.AudioProcessor.AudioDevice = _audioPlayer; + IsRunning = false; PopulateIncludedRomsMenu(); } @@ -127,7 +128,7 @@ namespace Desktop e.Graphics.DrawImage(_screenBitmap, destRect, sourceRect, GraphicsUnit.Pixel); } } - + public void StartEmulator() { @@ -253,50 +254,51 @@ namespace Desktop { IsRunning = false; } - private async void LoadRomAndStart(string filePath) + + private async void LoadRomDataAndStart(byte[] romData, string fileName, string uniqueId) { StopEmulator(); if (_emulatorTask != null) { await _emulatorTask; } - _romType = Path.GetExtension(filePath); - if (_romType == ".sms") - { - _machine.VideoProcessor.IsGameGear = false; - } - else if (_romType == ".gg") - { - _machine.VideoProcessor.IsGameGear = true; - } - else - throw new Exception("Incorrect ROM Type!"); - _romType = _machine.VideoProcessor.IsGameGear ? "GG" : "SMS"; - // 1. SAVE THE PREVIOUS GAME! SaveCurrentSram(); - // 2. Load the file - byte[] rom = File.ReadAllBytes(filePath); + // Blast the bytes straight into the virtual cartridge slot! + _machine.LoadCartridge(romData); - // 3. Jam it into the Sega Mapper - _machine.LoadCartridge(rom); + // Check the hardware mode based on the file name + bool isGG = fileName.EndsWith(".gg", StringComparison.OrdinalIgnoreCase); + _machine.VideoProcessor.IsGameGear = isGG; - // 4. Update the path tracking - _currentRomPath = filePath; - _currentRomName = Path.GetFileNameWithoutExtension(filePath); - //this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName}"; + // We use uniqueId to track the SRAM file path (works for both real paths and resource names) + _currentRomPath = uniqueId; + _currentRomName = Path.GetFileNameWithoutExtension(fileName); + + // Update the window title dynamically + _romType = isGG ? "GG" : "MS"; + this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName}"; - // 5. LOAD THE NEW SAVE DATA FROM THE EXE FOLDER! string savPath = GetSaveFilePath(); if (savPath != null) { _machine.MemoryBus.LoadSaveData(savPath); } - // 6. Turn the power on! StartEmulator(); } + + private void LoadRomAndStart(string filePath) + { + if (!File.Exists(filePath)) return; + + // Read from the hard drive, then pass to the universal loader + byte[] romData = File.ReadAllBytes(filePath); + LoadRomDataAndStart(romData, Path.GetFileName(filePath), filePath); + } + + private string GetSaveFilePath() { // Don't try to save if a game hasn't been loaded yet! @@ -339,7 +341,7 @@ namespace Desktop ToolStripMenuItem romMenuItem = new ToolStripMenuItem(menuName); // 3. Point the click event to our new extraction helper! - romMenuItem.Click += (sender, e) => ExtractAndLoadEmbeddedRom(resourceName); + romMenuItem.Click += (sender, e) => LoadEmbeddedRom(resourceName); // 4. Sort into the correct menu based on extension! if (resourceName.EndsWith(".gg", StringComparison.OrdinalIgnoreCase)) @@ -353,32 +355,28 @@ namespace Desktop } } - private void ExtractAndLoadEmbeddedRom(string resourceName) + private void LoadEmbeddedRom(string resourceName) { - // 1. Strip off the ugly "Desktop.ROMS." prefix to get the real file name + // 1. Strip off the ugly prefix to get the real file name for the UI and the .gg check string cleanFileName = resourceName.Replace("Desktop.ROMS.", ""); - // 2. Create a physical path right next to your emulator .exe - string physicalPath = Path.Combine(Application.StartupPath, cleanFileName); + Assembly assembly = Assembly.GetExecutingAssembly(); - // 3. If we haven't extracted this game yet, extract it now! - if (!File.Exists(physicalPath)) + // 2. Open a direct pipe to the internal resource + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) { - Assembly assembly = Assembly.GetExecutingAssembly(); -#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - using (Stream stream = assembly.GetManifestResourceStream(resourceName)) - { - if (stream == null) return; - using (FileStream fileStream = new FileStream(physicalPath, FileMode.Create)) - { - stream.CopyTo(fileStream); - } - } -#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. - } + if (stream == null) return; - // 4. Now that it is a REAL file on the hard drive, pass it to your normal loader! - LoadRomAndStart(physicalPath); + // 3. Copy the stream entirely in RAM—never touching the hard drive! + using (MemoryStream ms = new MemoryStream()) + { + stream.CopyTo(ms); + byte[] romData = ms.ToArray(); + + // 4. Pass the raw bytes to our universal loader + LoadRomDataAndStart(romData, cleanFileName, resourceName); + } + } } public static string[] GetFilesInResourceDirectory(string directoryPrefix) @@ -443,7 +441,7 @@ namespace Desktop _vramViewer.Show(); } - + private void exitToolStripMenuItem_Click(object sender, EventArgs e) { this.Close(); @@ -532,5 +530,28 @@ namespace Desktop turboModeToolStripMenuItem.Checked = !turboModeToolStripMenuItem.Checked; isTurboMode = turboModeToolStripMenuItem.Checked ? true : false; } + + private void aboutToolStripMenuItem_Click(object sender, EventArgs e) + { + MessageBox.Show( + $"First full release of Parsons Master System Emulator 2026!\r\n\r\nFeatures (v1.0):\r\n\r\n" + + $" *Full compatibility with all Master System and Game Gear ROMs\r\n" + + $" *Full window scaling with fixed aspect ratio (SMS & GG)\r\n" + + $" *Full 4 channel SN76489 implememtation using NAudio\r\n" + + $" *SRAM auto save and load (Battery Backed RAM)\r\n" + + $" *Keyboard and XInput control pad support\r\n" + + $" *Comprehensive Debugger\r\n" + + $" *VRAM Viewer\r\n" + + $" *Single point Save State\r\n" + + $" *Turbo mode\r\n" + + $" *Includes a selection of commercial ROMs\r\n\r\n" + + $"Planned updates (v1.1):\r\n\r\n" + + $" *Architectural double buffering (prevent screen tear)\r\n" + + $" *Graphics shaders (scanlines etc...)\r\n" + + $" *Add multiple Save States per game\r\n\r\n\r\n" + + $"(c) Marc Parsons 2026","Parsons Master System 2026 v1.0", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } } }