diff --git a/Core/Audio/SmsApu.cs b/Core/Audio/SmsApu.cs index e261d62..a37a8bf 100644 --- a/Core/Audio/SmsApu.cs +++ b/Core/Audio/SmsApu.cs @@ -33,7 +33,9 @@ namespace Core.Audio 0.1584f, 0.1258f, 0.1000f, 0.0794f, 0.0630f, 0.0501f, 0.0398f, 0.0f }; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. public SmsApu() +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. { Registers[1] = 0x0F; Registers[3] = 0x0F; diff --git a/Core/Io/SmsIoBus.cs b/Core/Io/SmsIoBus.cs index 234244b..d94b02b 100644 --- a/Core/Io/SmsIoBus.cs +++ b/Core/Io/SmsIoBus.cs @@ -6,8 +6,12 @@ namespace Core.Io { public class SmsIoBus : IIoBus { +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. public SmsVdp VideoProcessor { get; set; } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. public SmsApu AudioProcessor { get; set; } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. // Joypad State (0xFF means no buttons pressed - the SMS uses Active-Low logic!) public byte Joypad1Keyboard = 0xFF; diff --git a/Desktop/Desktop.csproj b/Desktop/Desktop.csproj index c992ea4..46efdae 100644 --- a/Desktop/Desktop.csproj +++ b/Desktop/Desktop.csproj @@ -14,10 +14,10 @@ true win-x64 Desktop.Program - Parsons Master System + Parsons Master System 2026 favicon.ico Sega Master System EMulator 2026 - 0.9 + 1.0 Marc Parsons Parsons Limited Parsons Master System 2026 diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 78823e3..111a9ce 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -34,12 +34,18 @@ namespace Desktop set { if (_machine != null) _machine.Breakpoint = value; } } +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. public ParsonsForm1() +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. { InitializeComponent(); this.KeyPreview = true; +#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). this.KeyDown += Form1_KeyDown; +#pragma warning restore CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). +#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). this.KeyUp += Form1_KeyUp; +#pragma warning restore CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). this.DoubleBuffered = true; this.ResizeRedraw = true; } @@ -115,42 +121,7 @@ namespace Desktop 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() { @@ -204,7 +175,9 @@ namespace Desktop if (_machine.Breakpoint.HasValue && _machine.Cpu.PC == _machine.Breakpoint.Value) { IsRunning = false; +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { _debugger?.uiUpdateTimer_Tick(null, EventArgs.Empty); }); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. break; } @@ -302,7 +275,9 @@ namespace Desktop { // Don't try to save if a game hasn't been loaded yet! if (string.IsNullOrEmpty(_currentRomName) || _currentRomName == "No ROM") +#pragma warning disable CS8603 // Possible null reference return. return null; +#pragma warning restore CS8603 // Possible null reference return. // Application.StartupPath is the exact folder containing your .exe string exeFolder = Application.StartupPath; @@ -324,38 +299,85 @@ namespace Desktop private void PopulateIncludedRomsMenu() { - string romsDirectory = @"C:\Parsons\Local Code Projects\ParsonsMasterSystem2026\Desktop\ROMS\"; + string romsDirectory = "Desktop.ROMS"; + string[] files = GetFilesInResourceDirectory(romsDirectory); - if (Directory.Exists(romsDirectory)) + foreach (string resourceName in files) { - string[] romFiles = Directory.GetFiles(romsDirectory, "*.sms"); - foreach (string file in romFiles) + // 1. Strip the "Desktop.ROMS." prefix + string cleanFileName = resourceName.Replace("Desktop.ROMS.", ""); + + // 2. Strip the extension for a beautiful menu item (e.g. "Sonic") + string menuName = Path.GetFileNameWithoutExtension(cleanFileName); + + ToolStripMenuItem romMenuItem = new ToolStripMenuItem(menuName); + + // 3. Point the click event to our new extraction helper! + romMenuItem.Click += (sender, e) => ExtractAndLoadEmbeddedRom(resourceName); + + // 4. Sort into the correct menu based on extension! + if (resourceName.EndsWith(".gg", StringComparison.OrdinalIgnoreCase)) { - // 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!) - 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); } - + else if (resourceName.EndsWith(".sms", StringComparison.OrdinalIgnoreCase)) + { + masterSystemToolStripMenuItem.DropDownItems.Add(romMenuItem); + } } } + private void ExtractAndLoadEmbeddedRom(string resourceName) + { + // 1. Strip off the ugly "Desktop.ROMS." prefix to get the real file name + string cleanFileName = resourceName.Replace("Desktop.ROMS.", ""); + + // 2. Create a physical path right next to your emulator .exe + string physicalPath = Path.Combine(Application.StartupPath, cleanFileName); + + // 3. If we haven't extracted this game yet, extract it now! + if (!File.Exists(physicalPath)) + { + 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. + } + + // 4. Now that it is a REAL file on the hard drive, pass it to your normal loader! + LoadRomAndStart(physicalPath); + } + + public static string[] GetFilesInResourceDirectory(string directoryPrefix) + { + // Get the assembly containing the embedded resources. + // Change this if your resources are in a different project/assembly. + Assembly assembly = Assembly.GetExecutingAssembly(); + + // Ensure the prefix ends with a dot so we don't accidentally match + // similarly named folders (e.g., matching "Folder" but not "FolderTwo") + if (!directoryPrefix.EndsWith(".")) + { + directoryPrefix += "."; + } + + // Get all resource names and filter by our "directory" prefix + string[] allResources = assembly.GetManifestResourceNames(); + + string[] filesInDirectory = allResources + .Where(resource => resource.StartsWith(directoryPrefix, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + return filesInDirectory; + } + private void selectROMToolStripMenuItem_Click(object sender, EventArgs e) { using (OpenFileDialog ofd = new OpenFileDialog())