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