From 66f18d0510416a40e1e6ea52c1a387d8e7827a58 Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Mon, 18 May 2026 14:57:19 +0100 Subject: [PATCH] Updated VDP to handle GameGear. Added Turbo mode. Versio 1.0 me thinks! --- Core/SmsMachine.cs | 2 +- Core/Video/SmsVdp.cs | 123 +++++++++++++++++++------------- Desktop/Form1.Designer.cs | 41 +++++++---- Desktop/Form1.cs | 143 +++++++++++++++++++++++++++----------- Desktop/VramViewerForm.cs | 17 +++-- 5 files changed, 213 insertions(+), 113 deletions(-) diff --git a/Core/SmsMachine.cs b/Core/SmsMachine.cs index 0d280c7..31f66b0 100644 --- a/Core/SmsMachine.cs +++ b/Core/SmsMachine.cs @@ -23,7 +23,7 @@ namespace Core //public const int TStatesPerFrame = 49780; //PAL public SmsMachine() - { + { MemoryBus = new SmsMemoryBus(); VideoProcessor = new SmsVdp(); AudioProcessor = new SmsApu(); diff --git a/Core/Video/SmsVdp.cs b/Core/Video/SmsVdp.cs index 1b24b58..529b8ac 100644 --- a/Core/Video/SmsVdp.cs +++ b/Core/Video/SmsVdp.cs @@ -1,14 +1,15 @@ using System; +using System.Diagnostics; using System.IO; namespace Core.Video { public class SmsVdp { - public bool IsPalRegion { get; set; } = false; // The VDP's private memory! The CPU cannot touch these arrays directly. public byte[] VRAM { get; private set; } = new byte[0x4000]; // 16KB Video RAM - public byte[] CRAM { get; private set; } = new byte[0x20]; // 32 Bytes Color Palette + public byte[] smsCRAM { get; private set; } = new byte[0x20]; // Master System - 32 Bytes colour Palette + public byte[] ggCRAM { get; private set; } = new byte[0x40]; // GameGear - 64 Bytes colour palette public byte[] Registers { get; private set; } = new byte[16]; // 11 Hardware Control Registers public int[] FrameBuffer { get; private set; } = new int[256 * 192]; private bool[] _priorityBuffer = new bool[256 * 192]; // Tracks priority pixels! @@ -26,11 +27,14 @@ namespace Core.Video private int _currentScanline = 0; private int _lineCounter = 0; private byte _statusRegister = 0x00; + public bool IsGameGear { get; set; } = false; public bool InterruptPending => ((_statusRegister & 0x80) != 0 && (Registers[1] & 0x20) != 0) || // VBlank ((_statusRegister & 0x40) != 0 && (Registers[0] & 0x10) != 0); // Line Interrupt + + public byte ReadDataPort() // Port 0xBE { _isSecondControlByte = false; // Reading data resets the control latch @@ -45,8 +49,8 @@ namespace Core.Video _isSecondControlByte = false; byte currentStatus = _statusRegister; - // CRITICAL HARDWARE QUIRK: Reading the status port physically - // clears the flags inside the chip! If we don't clear this, + // Reading the status port physically + // clears the flags inside the chip If we don't clear this, // the interrupt line gets stuck on forever. _statusRegister = 0x00; @@ -56,20 +60,29 @@ namespace Core.Video public void WriteDataPort(byte value) // Port 0xBE { _isSecondControlByte = false; - _readBuffer = value; + _readBuffer = value; int address = _controlWord & 0x3FFF; int command = (_controlWord >> 14) & 0x03; - if (command == 3) // Code 3: Write to Color Palette (CRAM) + if (command == 3) // Code 3: Write to Colour Palette (CRAM) { - CRAM[address & 0x1F] = value; + if (IsGameGear) + { + ggCRAM[address & 0x3F] = value; // GG has 64 bytes of CRAM + } + else + { + smsCRAM[address & 0x1F] = value; // SMS has 32 bytes of CRAM + } } - else // Code 0, 1, 2: Write to VRAM + else // THE FIX: Code 0, 1, 2: Write graphics to VRAM! { VRAM[address] = value; } - _controlWord++; // Auto-increment so the Z80 can blast data fast + + // THE FIX: The pointer MUST auto-increment so the CPU can blast data fast! + _controlWord++; } public void WriteControlPort(byte value) // Port 0xBF @@ -105,23 +118,14 @@ namespace Core.Video public byte ReadVCounter() { - if (IsPalRegion) - { - // PAL Math: 313 lines. Counts 0 to 242, jumps to 186 (0xBA), counts to 255. - if (_currentScanline <= 242) return (byte)_currentScanline; - else return (byte)(_currentScanline - 57); - } - else - { - // NTSC Math: 262 lines. Counts 0 to 218, jumps to 213 (0xD5), counts to 255. + // NTSC Math: 262 lines. Counts 0 to 218, jumps to 213 (0xD5), counts to 255. if (_currentScanline <= 218) return (byte)_currentScanline; else return (byte)(_currentScanline - 6); - } + } public byte ReadHCounter() { - // The Master System H-Counter is a notoriously weird 8-bit timer. - // It counts from 0x00 to 0x93, then jumps forward to 0xE9, ending at 0xFF. + // The Master System H-Counter counts from 0x00 to 0x93, then jumps forward to 0xE9, ending at 0xFF. // 1 T-State = 1.5 pixels. The H-Counter increments every 2 pixels. // So H = T * 0.75 @@ -173,7 +177,7 @@ namespace Core.Video // 3. MOVE TO THE NEXT LINE _currentScanline++; - int maxLines = IsPalRegion ? 313 : 262; + int maxLines = 262; if (_currentScanline > maxLines -1) { @@ -197,7 +201,6 @@ namespace Core.Video for (int x = 0; x < 256; x++) FrameBuffer[(screenY * 256) + x] = unchecked((int)0xFF000000); return; } - // --- 1. RENDER BACKGROUND LINE --- ushort nameTableBase = (ushort)((Registers[2] & 0x0E) << 10); int scrollX = Registers[8]; @@ -213,16 +216,10 @@ namespace Core.Video // --- LEFT COLUMN MASKING (OVERSCAN CURTAIN) --- if (maskLeftCol && screenX < 8) { - // Draw the physical backdrop color (from Sprite Palette + Reg 7 index) - byte bgSmsColor = CRAM[16 + (Registers[7] & 0x0F)]; - int bgR = (bgSmsColor & 0x03) * 85; - int bgG = ((bgSmsColor >> 2) & 0x03) * 85; - int bgB = ((bgSmsColor >> 4) & 0x03) * 85; - + // REPLACE THE R/G/B MATH WITH THIS SINGLE LINE: int bgAddress = (screenY * 256) + screenX; - FrameBuffer[bgAddress] = (255 << 24) | (bgR << 16) | (bgG << 8) | bgB; + FrameBuffer[bgAddress] = GetRgbColour(16, Registers[7] & 0x0F); - // Flag it as priority so sprites also hide behind the curtain! _priorityBuffer[bgAddress] = true; continue; } @@ -261,21 +258,17 @@ namespace Core.Video byte bp3 = VRAM[tileAddress + (readY * 4) + 3]; int readX = flipH ? tileX : (7 - tileX); - int colorIndex = ((bp0 >> readX) & 1) | (((bp1 >> readX) & 1) << 1) | + int colourIndex = ((bp0 >> readX) & 1) | (((bp1 >> readX) & 1) << 1) | (((bp2 >> readX) & 1) << 2) | (((bp3 >> readX) & 1) << 3); int paletteOffset = useSpritePalette ? 16 : 0; - byte smsColor = CRAM[paletteOffset + colorIndex]; - - int r = (smsColor & 0x03) * 85; - int g = ((smsColor >> 2) & 0x03) * 85; - int b = ((smsColor >> 4) & 0x03) * 85; + int finalColour = GetRgbColour(paletteOffset, colourIndex); int screenAddress = (screenY * 256) + screenX; // Draw background and reset priority mask for this exact pixel - FrameBuffer[screenAddress] = (255 << 24) | (r << 16) | (g << 8) | b; - _priorityBuffer[screenAddress] = (priority && colorIndex != 0); + FrameBuffer[screenAddress] = finalColour; + _priorityBuffer[screenAddress] = (priority && colourIndex != 0); } // --- 2. RENDER SPRITE LINE --- @@ -329,24 +322,53 @@ namespace Core.Video if (_priorityBuffer[(screenY * 256) + screenX]) continue; int shift = 7 - px; - int colorIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) | + int colourIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) | (((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3); - if (colorIndex == 0) continue; + if (colourIndex == 0) continue; - byte smsColor = CRAM[16 + colorIndex]; - int r = (smsColor & 0x03) * 85; - int g = ((smsColor >> 2) & 0x03) * 85; - int b = ((smsColor >> 4) & 0x03) * 85; - - FrameBuffer[(screenY * 256) + screenX] = (255 << 24) | (r << 16) | (g << 8) | b; + // REPLACE THE R/G/B MATH WITH THIS SINGLE LINE: + FrameBuffer[(screenY * 256) + screenX] = GetRgbColour(16, colourIndex); } } } + + public int GetRgbColour(int paletteOffset, int colourIndex) + { + //Debug.WriteLine(_isGameGear); + int r, g, b; + + if (IsGameGear) + { + // Game Gear: 2 bytes per colour. Format: ----BBBBGGGGRRRR + int cramIndex = (paletteOffset + colourIndex) * 2; + ushort ggcolour = (ushort)(ggCRAM[cramIndex] | (ggCRAM[cramIndex + 1] << 8)); + + // Extract 4-bit values (0-15) and map them to standard 8-bit values (0-255). + // We multiply by 17 because 255 / 15 = 17! + r = (ggcolour & 0x0F) * 17; + g = ((ggcolour >> 4) & 0x0F) * 17; + b = ((ggcolour >> 8) & 0x0F) * 17; + } + else + { + // Master System: 1 byte per colour. Format: --BBGGRR + byte smscolour = smsCRAM[paletteOffset + colourIndex]; + + // Extract 2-bit values (0-3) and map them to standard 8-bit values (0-255). + // We multiply by 85 because 255 / 3 = 85! + r = (smscolour & 0x03) * 85; + g = ((smscolour >> 2) & 0x03) * 85; + b = ((smscolour >> 4) & 0x03) * 85; + } + + return (255 << 24) | (r << 16) | (g << 8) | b; + } public void SaveState(BinaryWriter bw) { bw.Write(VRAM); - bw.Write(CRAM); + bw.Write(smsCRAM); + bw.Write(ggCRAM); bw.Write(Registers); bw.Write(_isSecondControlByte); bw.Write(_controlWord); @@ -359,12 +381,14 @@ namespace Core.Video // ADD THESE: bw.Write(_latchedHScroll); bw.Write(_latchedVScroll); + bw.Write(IsGameGear); } public void LoadState(BinaryReader br) { Array.Copy(br.ReadBytes(VRAM.Length), VRAM, VRAM.Length); - Array.Copy(br.ReadBytes(CRAM.Length), CRAM, CRAM.Length); + Array.Copy(br.ReadBytes(smsCRAM.Length), smsCRAM, smsCRAM.Length); + Array.Copy(br.ReadBytes(ggCRAM.Length), ggCRAM, ggCRAM.Length); Array.Copy(br.ReadBytes(Registers.Length), Registers, Registers.Length); _isSecondControlByte = br.ReadBoolean(); _controlWord = br.ReadUInt16(); @@ -377,6 +401,7 @@ namespace Core.Video // ADD THESE: _latchedHScroll = br.ReadInt32(); _latchedVScroll = br.ReadInt32(); + IsGameGear = br.ReadBoolean(); } } } \ No newline at end of file diff --git a/Desktop/Form1.Designer.cs b/Desktop/Form1.Designer.cs index ef827ae..d1896ba 100644 --- a/Desktop/Form1.Designer.cs +++ b/Desktop/Form1.Designer.cs @@ -41,9 +41,11 @@ resetToolStripMenuItem = new ToolStripMenuItem(); saveStateToolStripMenuItem = new ToolStripMenuItem(); loadStateToolStripMenuItem = new ToolStripMenuItem(); + turboModeToolStripMenuItem = new ToolStripMenuItem(); helpToolStripMenuItem = new ToolStripMenuItem(); aboutToolStripMenuItem = new ToolStripMenuItem(); - pALRegionToolStripMenuItem = new ToolStripMenuItem(); + masterSystemToolStripMenuItem = new ToolStripMenuItem(); + gameGearToolStripMenuItem = new ToolStripMenuItem(); menuStrip1.SuspendLayout(); SuspendLayout(); // @@ -74,15 +76,15 @@ // // includedToolStripMenuItem // + includedToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { masterSystemToolStripMenuItem, gameGearToolStripMenuItem }); includedToolStripMenuItem.Name = "includedToolStripMenuItem"; - includedToolStripMenuItem.Size = new Size(178, 26); + includedToolStripMenuItem.Size = new Size(224, 26); includedToolStripMenuItem.Text = "Included"; - includedToolStripMenuItem.Click += includedToolStripMenuItem_Click; // // selectROMToolStripMenuItem1 // selectROMToolStripMenuItem1.Name = "selectROMToolStripMenuItem1"; - selectROMToolStripMenuItem1.Size = new Size(178, 26); + selectROMToolStripMenuItem1.Size = new Size(224, 26); selectROMToolStripMenuItem1.Text = "Select ROM..."; selectROMToolStripMenuItem1.Click += selectROMToolStripMenuItem_Click; // @@ -116,7 +118,7 @@ // // machineToolStripMenuItem // - machineToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { resetToolStripMenuItem, saveStateToolStripMenuItem, loadStateToolStripMenuItem, pALRegionToolStripMenuItem }); + machineToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { resetToolStripMenuItem, saveStateToolStripMenuItem, loadStateToolStripMenuItem, turboModeToolStripMenuItem }); machineToolStripMenuItem.Name = "machineToolStripMenuItem"; machineToolStripMenuItem.Size = new Size(79, 24); machineToolStripMenuItem.Text = "Machine"; @@ -142,6 +144,13 @@ loadStateToolStripMenuItem.Text = "Load State"; loadStateToolStripMenuItem.Click += loadStateToolStripMenuItem_Click; // + // turboModeToolStripMenuItem + // + turboModeToolStripMenuItem.Name = "turboModeToolStripMenuItem"; + turboModeToolStripMenuItem.Size = new Size(224, 26); + turboModeToolStripMenuItem.Text = "Turbo Mode"; + turboModeToolStripMenuItem.Click += turboModeToolStripMenuItem_Click; + // // helpToolStripMenuItem // helpToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { aboutToolStripMenuItem }); @@ -155,15 +164,17 @@ aboutToolStripMenuItem.Size = new Size(133, 26); aboutToolStripMenuItem.Text = "About"; // - // pALRegionToolStripMenuItem + // masterSystemToolStripMenuItem // - pALRegionToolStripMenuItem.Checked = true; - pALRegionToolStripMenuItem.CheckOnClick = true; - pALRegionToolStripMenuItem.CheckState = CheckState.Checked; - pALRegionToolStripMenuItem.Name = "pALRegionToolStripMenuItem"; - pALRegionToolStripMenuItem.Size = new Size(224, 26); - pALRegionToolStripMenuItem.Text = "PAL Region?"; - pALRegionToolStripMenuItem.Click += pALRegionToolStripMenuItem_Click; + 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"; // // ParsonsForm1 // @@ -197,6 +208,8 @@ private ToolStripMenuItem vRAMViewerToolStripMenuItem; private ToolStripMenuItem saveStateToolStripMenuItem; private ToolStripMenuItem loadStateToolStripMenuItem; - private ToolStripMenuItem pALRegionToolStripMenuItem; + private ToolStripMenuItem turboModeToolStripMenuItem; + private ToolStripMenuItem masterSystemToolStripMenuItem; + private ToolStripMenuItem gameGearToolStripMenuItem; } } diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 0e1e00a..78823e3 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -18,14 +18,14 @@ namespace Desktop private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb); private NAudioPlayer _audioPlayer; private Task _emulatorTask; - private double TargetFrameTime = 16.667f; //NTSC - //private double TargetFrameTime = 20; //PAL public int TotalFrameCount = 0; public double FrameTime { get; private set; } = 0; public double FramesPerSecond { get; private set; } = 0; private string _currentRomName = "No ROM"; private string _currentRomPath = ""; - private Stopwatch _stopwatch = new System.Diagnostics.Stopwatch(); + private Stopwatch _stopwatch = new Stopwatch(); + private string _romType = ""; + private bool isTurboMode = false; public bool IsRunning { get; private set; } = false; public ushort? Breakpoint @@ -37,21 +37,17 @@ namespace Desktop public ParsonsForm1() { InitializeComponent(); - - // These are perfectly safe for the Visual Studio Designer! this.KeyPreview = true; this.KeyDown += Form1_KeyDown; this.KeyUp += Form1_KeyUp; this.DoubleBuffered = true; this.ResizeRedraw = true; } - - // The Designer ignores this completely, but the compiled game runs it instantly! protected override void OnLoad(EventArgs e) { base.OnLoad(e); - this.Text = $"Parsons Master System - {_currentRomName}"; + this.Text = $"Parsons Master System 2026 - {_currentRomName}"; this.BackColor = Color.Black; _machine = new SmsMachine(); @@ -70,6 +66,7 @@ namespace Desktop this.Invalidate(); TotalFrameCount++; } + protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); @@ -80,44 +77,90 @@ namespace Desktop e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half; - // 2. Calculate the actual usable window space int topOffset = menuStrip1.Height; int availableWidth = this.ClientSize.Width; int availableHeight = this.ClientSize.Height - topOffset; - // 3. Calculate the maximum scale factor that fits perfectly - float scaleX = (float)availableWidth / 256f; - float scaleY = (float)availableHeight / 192f; + // 2. THE LENS LENS: Determine what part of the VDP buffer we actually want to show! + // (Safely check if the machine/video processor exists yet) + bool isGG = _machine?.VideoProcessor?.IsGameGear ?? false; + + int sourceWidth = isGG ? 160 : 256; + int sourceHeight = isGG ? 144 : 192; + int sourceX = isGG ? 48 : 0; // Skip 48 pixels in! + int sourceY = isGG ? 24 : 0; // Skip 24 pixels down! + + // Create a rectangle defining the specific pixels to extract from the raw frame buffer + Rectangle sourceRect = new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight); + + // 3. Calculate the maximum scale factor for the cropped image + float scaleX = (float)availableWidth / sourceWidth; + float scaleY = (float)availableHeight / sourceHeight; // Pick the smaller scale so the image never bleeds off the edge float scale = Math.Min(scaleX, scaleY); // 4. Calculate the new physical pixel dimensions - int newWidth = (int)(256 * scale); - int newHeight = (int)(192 * scale); + int newWidth = (int)(sourceWidth * scale); + int newHeight = (int)(sourceHeight * scale); // 5. Center the image (Letterboxing / Pillarboxing) int offsetX = (availableWidth - newWidth) / 2; int offsetY = topOffset + ((availableHeight - newHeight) / 2); - Rectangle renderArea = new Rectangle(offsetX, offsetY, newWidth, newHeight); + // Create a rectangle defining exactly where on the Windows form to draw the extracted pixels + Rectangle destRect = new Rectangle(offsetX, offsetY, newWidth, newHeight); - // 6. Draw the scaled image - e.Graphics.DrawImage(_screenBitmap, renderArea); + // 6. Blast the perfectly cropped and scaled bitmap onto the screen! + e.Graphics.DrawImage(_screenBitmap, destRect, sourceRect, GraphicsUnit.Pixel); } } + //protected override void OnPaint(PaintEventArgs e) + //{ + // base.OnPaint(e); + + // if (_screenBitmap != null && menuStrip1 != null) + // { + // // 1. Maintain perfect, chunky retro pixels + // e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; + // e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half; + + // // 2. Calculate the actual usable window space + // int topOffset = menuStrip1.Height; + // int availableWidth = this.ClientSize.Width; + // int availableHeight = this.ClientSize.Height - topOffset; + + // // 3. Calculate the maximum scale factor that fits perfectly + // float scaleX = (float)availableWidth / 256f; + // float scaleY = (float)availableHeight / 192f; + + // // Pick the smaller scale so the image never bleeds off the edge + // float scale = Math.Min(scaleX, scaleY); + + // // 4. Calculate the new physical pixel dimensions + // int newWidth = (int)(256 * scale); + // int newHeight = (int)(192 * scale); + + // // 5. Center the image (Letterboxing / Pillarboxing) + // int offsetX = (availableWidth - newWidth) / 2; + // int offsetY = topOffset + ((availableHeight - newHeight) / 2); + + // Rectangle renderArea = new Rectangle(offsetX, offsetY, newWidth, newHeight); + + // // 6. Draw the scaled image + // e.Graphics.DrawImage(_screenBitmap, renderArea); + // } + //} public void StartEmulator() { if (IsRunning) return; IsRunning = true; TotalFrameCount = 0; - - double targetFps = _machine.VideoProcessor.IsPalRegion ? 50.0 : 59.92274; - double TargetFrameTime = 1000.0 / targetFps; - _emulatorTask = Task.Run(() => { + double targetFps = 59.94; + double TargetFrameTime = 1000.0 / targetFps; _stopwatch.Restart(); double nextFrameTargetTime = _stopwatch.Elapsed.TotalMilliseconds + TargetFrameTime; @@ -126,7 +169,13 @@ namespace Desktop while (IsRunning) { - // Mark exactly when the emulator starts thinking + //targetFps = isTurboMode ? 1000 : 59.94; + //TargetFrameTime = 1000.0 / targetFps; + BeginInvoke((System.Windows.Forms.MethodInvoker)delegate + { + targetFps = isTurboMode ? 1000 : 59.94; + TargetFrameTime = 1000.0 / targetFps; + }); double frameStartTime = _stopwatch.Elapsed.TotalMilliseconds; // --- POLL PHYSICAL CONTROLLER --- @@ -187,7 +236,7 @@ namespace Desktop BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { - this.Text = $"Parsons Master System 2026 - {_currentRomName} [FPS/FT: {FramesPerSecond:F0}/{FrameTime:F1}]"; + this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName} [FPS: {FramesPerSecond:F0}]"; }); } @@ -212,6 +261,18 @@ namespace Desktop { 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(); @@ -225,7 +286,7 @@ namespace Desktop // 4. Update the path tracking _currentRomPath = filePath; _currentRomName = Path.GetFileNameWithoutExtension(filePath); - this.Text = $"Parsons Master System - {_currentRomName}"; + //this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName}"; // 5. LOAD THE NEW SAVE DATA FROM THE EXE FOLDER! string savPath = GetSaveFilePath(); @@ -263,13 +324,11 @@ namespace Desktop 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 @@ -280,8 +339,20 @@ namespace Desktop romMenuItem.Click += (sender, e) => LoadRomAndStart(file); // Add it to the "Included" submenu (make sure the name matches your designer element!) - includedToolStripMenuItem.DropDownItems.Add(romMenuItem); + masterSystemToolStripMenuItem.DropDownItems.Add(romMenuItem); } + romFiles = Directory.GetFiles(romsDirectory, "*.gg"); + foreach (string file in romFiles) + { + // Create a new dropdown item for each .gg file it finds + string romName = Path.GetFileNameWithoutExtension(file); + ToolStripMenuItem romMenuItem = new ToolStripMenuItem(romName); + + // When clicked, pass the file path to the helper method + romMenuItem.Click += (sender, e) => LoadRomAndStart(file); + gameGearToolStripMenuItem.DropDownItems.Add(romMenuItem); + } + } } @@ -290,7 +361,7 @@ namespace Desktop using (OpenFileDialog ofd = new OpenFileDialog()) { ofd.Title = "Select Master System ROM"; - ofd.Filter = "SMS ROMs (*.sms)|*.sms|All Files (*.*)|*.*"; + ofd.Filter = "SMS ROMs (*.sms)|*.sms|GG ROMs (*.gg)|*.gg|All Files (*.*)|*.*"; if (ofd.ShowDialog() == DialogResult.OK) { @@ -324,11 +395,7 @@ namespace Desktop _vramViewer.Show(); } - private void includedToolStripMenuItem_Click(object sender, EventArgs e) - { - - } - + private void exitToolStripMenuItem_Click(object sender, EventArgs e) { this.Close(); @@ -412,14 +479,10 @@ namespace Desktop StartEmulator(); } - private void pALRegionToolStripMenuItem_Click(object sender, EventArgs e) + private void turboModeToolStripMenuItem_Click(object sender, EventArgs e) { - if (pALRegionToolStripMenuItem.Checked) - _machine.VideoProcessor.IsPalRegion = true; - else _machine.VideoProcessor.IsPalRegion= false; - - pALRegionToolStripMenuItem.Checked = !pALRegionToolStripMenuItem.Checked; - + turboModeToolStripMenuItem.Checked = !turboModeToolStripMenuItem.Checked; + isTurboMode = turboModeToolStripMenuItem.Checked ? true : false; } } } diff --git a/Desktop/VramViewerForm.cs b/Desktop/VramViewerForm.cs index 25199ab..57f0d42 100644 --- a/Desktop/VramViewerForm.cs +++ b/Desktop/VramViewerForm.cs @@ -20,7 +20,7 @@ namespace Desktop this.Text = "VRAM Viewer (512 Tiles)"; // 256x128 native resolution, scaled up by 2 for visibility! - this.ClientSize = new Size(1024, 512); + this.ClientSize = new Size(512, 256); this.DoubleBuffered = true; this.FormBorderStyle = FormBorderStyle.FixedToolWindow; @@ -45,7 +45,6 @@ namespace Desktop int tileX = (tile % 32) * 8; int tileY = (tile / 32) * 8; int tileAddress = tile * 32; - // Decode the 8x8 pixels for (int y = 0; y < 8; y++) { @@ -57,19 +56,19 @@ namespace Desktop for (int x = 0; x < 8; x++) { int shift = 7 - x; - int colorIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) | + int colourIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) | (((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3); // Use the Sprite Palette (Offset 16) so characters and enemies are colored correctly - byte smsColor = _vdp.CRAM[16 + colorIndex]; - int r = (smsColor & 0x03) * 85; - int g = ((smsColor >> 2) & 0x03) * 85; - int b = ((smsColor >> 4) & 0x03) * 85; + int colour = _vdp.GetRgbColour(16, colourIndex); + //int r = (smsColor & 0x03) * 85; + //int g = ((smsColor >> 2) & 0x03) * 85; + //int b = ((smsColor >> 4) & 0x03) * 85; // Background color (Index 0) is technically transparent for sprites, so we render it as magenta - if (colorIndex == 0) { r = 255; g = 0; b = 255; } + //if (colourIndex == 0) { int r = 255; int g = 0; int b = 255; } - _pixelData[((tileY + y) * 256) + (tileX + x)] = (255 << 24) | (r << 16) | (g << 8) | b; + _pixelData[((tileY + y) * 256) + (tileX + x)] = colourIndex == 0 ? (255 << 24) | (255 << 16) | (0 << 8) | 255 : colour; } } }