First OS Agnostic Commit

This commit is contained in:
2026-05-31 00:24:14 +01:00
parent 2f332ff224
commit 2300e2931e
4 changed files with 182 additions and 1 deletions

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PublishAot>true</PublishAot>
<OptimizationPreference>Size</OptimizationPreference>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Raylib-cs" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,125 @@
using System;
using System.IO;
using System.Numerics;
using Core;
using Raylib_cs;
namespace Parsons.SteamDeck
{
class Program
{
static void Main(string[] args)
{
// 1. Initialize Cross-Platform OpenGL Window
// THE FIX: Use PascalCase C# enums
Raylib.SetConfigFlags(ConfigFlags.ResizableWindow);
Raylib.InitWindow(1280, 800, "Parsons Master System"); // Native Steam Deck Resolution
Raylib.InitAudioDevice();
// 2. Initialize Hardware-Agnostic Audio Stream (44.1kHz, 32-bit float, Mono)
AudioStream audioStream = Raylib.LoadAudioStream(44100, 32, 1);
Raylib.PlayAudioStream(audioStream);
// 3. Boot the Core Emulator
SmsMachine machine = new SmsMachine();
RaylibAudioPlayer audioPlayer = new RaylibAudioPlayer();
machine.AudioProcessor.AudioDevice = audioPlayer;
// Load a ROM (Passed via command line argument, e.g. ./Parsons.SteamDeck sonic.sms)
if (args.Length > 0 && File.Exists(args[0]))
{
machine.LoadCartridge(File.ReadAllBytes(args[0]));
bool isGG = args[0].EndsWith(".gg", StringComparison.OrdinalIgnoreCase);
machine.VideoProcessor.IsGameGear = isGG;
}
// Lock the engine loop to 60 FPS natively
Raylib.SetTargetFPS(60);
// 4. Prepare the Video Texture Pipeline
// THE FIX: Use Color.Black
Image img = Raylib.GenImageColor(256, 192, Color.Black);
Texture2D vdpTexture = Raylib.LoadTextureFromImage(img);
byte[] pixelBuffer = new byte[256 * 192 * 4]; // Fast RGBA translation array
// --- THE MAIN GAME LOOP ---
while (!Raylib.WindowShouldClose())
{
// A. OS-Agnostic Input Polling (Works on Keyboard AND Steam Deck Controller)
// THE FIX: PascalCase KeyboardKey and GamepadButton enums
byte pad = 0xFF;
if (Raylib.IsKeyDown(KeyboardKey.Up) || Raylib.IsGamepadButtonDown(0, GamepadButton.LeftFaceUp)) pad &= 0xFE;
if (Raylib.IsKeyDown(KeyboardKey.Down) || Raylib.IsGamepadButtonDown(0, GamepadButton.LeftFaceDown)) pad &= 0xFD;
if (Raylib.IsKeyDown(KeyboardKey.Left) || Raylib.IsGamepadButtonDown(0, GamepadButton.LeftFaceLeft)) pad &= 0xFB;
if (Raylib.IsKeyDown(KeyboardKey.Right) || Raylib.IsGamepadButtonDown(0, GamepadButton.LeftFaceRight)) pad &= 0xF7;
// Steam Deck "A" Button is RightFaceDown. "B" Button is RightFaceRight.
if (Raylib.IsKeyDown(KeyboardKey.Z) || Raylib.IsGamepadButtonDown(0, GamepadButton.RightFaceDown)) pad &= 0xEF; // B1 / A Button
if (Raylib.IsKeyDown(KeyboardKey.X) || Raylib.IsGamepadButtonDown(0, GamepadButton.RightFaceRight)) pad &= 0xDF; // B2 / B Button
machine.IoBus.Joypad1Keyboard = pad;
// B. Execute exactly one frame of T-States
machine.RunFrame();
// C. Flush Audio Buffer to the Sound Card
if (Raylib.IsAudioStreamProcessed(audioStream))
{
float[] samples = audioPlayer.GetQueuedSamples();
if (samples.Length > 0)
{
unsafe
{
fixed (float* p = samples)
{
Raylib.UpdateAudioStream(audioStream, p, samples.Length);
}
}
}
}
// D. Fast Pixel Translation (ARGB int -> RGBA bytes)
int[] frameBuffer = machine.VideoProcessor.FrameBuffer;
for (int i = 0; i < frameBuffer.Length; i++)
{
int argb = frameBuffer[i];
pixelBuffer[i * 4 + 0] = (byte)((argb >> 16) & 0xFF); // R
pixelBuffer[i * 4 + 1] = (byte)((argb >> 8) & 0xFF); // G
pixelBuffer[i * 4 + 2] = (byte)(argb & 0xFF); // B
pixelBuffer[i * 4 + 3] = 255; // A
}
// Push bytes directly to the GPU Texture
unsafe
{
fixed (byte* p = pixelBuffer)
{
Raylib.UpdateTexture(vdpTexture, p);
}
}
// E. Render to Screen (Perfect Aspect Ratio Scaling)
Raylib.BeginDrawing();
Raylib.ClearBackground(Color.Black);
float scale = Math.Min((float)Raylib.GetScreenWidth() / 256, (float)Raylib.GetScreenHeight() / 192);
Rectangle sourceRec = new Rectangle(0, 0, 256, 192);
Rectangle destRec = new Rectangle(
(Raylib.GetScreenWidth() - (256 * scale)) * 0.5f,
(Raylib.GetScreenHeight() - (192 * scale)) * 0.5f,
256 * scale, 192 * scale);
// Draw the VDP Frame
Raylib.DrawTexturePro(vdpTexture, sourceRec, destRec, new Vector2(0, 0), 0.0f, Color.White);
Raylib.EndDrawing();
}
// Clean up hardware resources on exit
Raylib.UnloadTexture(vdpTexture);
Raylib.UnloadAudioStream(audioStream);
Raylib.CloseAudioDevice();
Raylib.CloseWindow();
}
}
}

View File

@@ -0,0 +1,28 @@
using Core.Interfaces;
using System.Collections.Concurrent;
namespace Parsons.SteamDeck
{
public class RaylibAudioPlayer : IAudioDevice
{
private ConcurrentQueue<float> _sampleQueue = new ConcurrentQueue<float>();
public void AddSample(float sample)
{
// Queue the sample from the emulator core
_sampleQueue.Enqueue(sample);
}
public float[] GetQueuedSamples()
{
// Pull all pending samples out of the queue to send to the sound card
int count = _sampleQueue.Count;
float[] buffer = new float[count];
for (int i = 0; i < count; i++)
{
_sampleQueue.TryDequeue(out buffer[i]);
}
return buffer;
}
}
}

View File

@@ -1,12 +1,14 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.37216.2 d17.14
VisualStudioVersion = 17.14.37216.2
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop", "Desktop\Desktop.csproj", "{E245F3A5-E541-43BB-B64E-B4B2BB3D8C51}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{B0A741E4-CE3E-4DF0-96B2-E314973F9201}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Parsons.Steamdeck", "Parsons.Steamdeck\Parsons.Steamdeck.csproj", "{B619DF68-7EBF-4C6A-AC24-AD5250404943}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
{B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Release|Any CPU.Build.0 = Release|Any CPU
{B619DF68-7EBF-4C6A-AC24-AD5250404943}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B619DF68-7EBF-4C6A-AC24-AD5250404943}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B619DF68-7EBF-4C6A-AC24-AD5250404943}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B619DF68-7EBF-4C6A-AC24-AD5250404943}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE