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())