using System; using System.IO; using System.Numerics; using Core; using Raylib_cs; namespace Parsons.SteamDeck { class Program { static void Main(string[] args) { Raylib.SetConfigFlags(ConfigFlags.ResizableWindow); Raylib.InitWindow(1280, 800, "Parsons Master System 2026"); Raylib.InitAudioDevice(); //Raylib.SetAudioStreamBufferSizeDefault(1024); AudioStream audioStream = Raylib.LoadAudioStream(44100, 32, 1); Raylib.PlayAudioStream(audioStream); SmsMachine machine = new SmsMachine(); RaylibAudioPlayer audioPlayer = new RaylibAudioPlayer(); machine.AudioProcessor.AudioDevice = audioPlayer; Raylib.SetTargetFPS(60); Image img = Raylib.GenImageColor(256, 192, Color.Black); Texture2D vdpTexture = Raylib.LoadTextureFromImage(img); byte[] pixelBuffer = new byte[256 * 192 * 4]; bool isMenuState = true; string[] embeddedRoms = GetEmbeddedRoms(); int selectedRomIndex = 0; bool buttonLock = false; while (!Raylib.WindowShouldClose()) { if (isMenuState) { // --- MENU INPUT LOGIC --- if (!buttonLock) { if (Raylib.IsKeyDown(KeyboardKey.S) || Raylib.IsGamepadButtonDown(0, GamepadButton.LeftFaceDown)) { selectedRomIndex++; if (selectedRomIndex >= embeddedRoms.Length) selectedRomIndex = 0; buttonLock = true; } else if (Raylib.IsKeyDown(KeyboardKey.W) || Raylib.IsGamepadButtonDown(0, GamepadButton.LeftFaceUp)) { selectedRomIndex--; if (selectedRomIndex < 0) selectedRomIndex = embeddedRoms.Length - 1; buttonLock = true; } else if (Raylib.IsKeyDown(KeyboardKey.O) || Raylib.IsGamepadButtonDown(0, GamepadButton.RightFaceDown)) { string targetRom = embeddedRoms[selectedRomIndex]; machine.LoadCartridge(LoadEmbeddedRomBytes(targetRom)); machine.VideoProcessor.IsGameGear = targetRom.EndsWith(".gg", StringComparison.OrdinalIgnoreCase); isMenuState = false; } } if (Raylib.IsKeyUp(KeyboardKey.S) && Raylib.IsKeyUp(KeyboardKey.W) && Raylib.IsGamepadButtonUp(0, GamepadButton.LeftFaceDown) && Raylib.IsGamepadButtonUp(0, GamepadButton.LeftFaceUp)) { buttonLock = false; } // --- SCROLLING MENU DRAW LOGIC --- Raylib.BeginDrawing(); Raylib.ClearBackground(new Color(20, 20, 40, 255)); Raylib.DrawText("PARSONS MASTER SYSTEM", 50, 50, 40, Color.White); Raylib.DrawText("Select: D-Pad Start: 'A' Button Return to Menu: Select + Start", 50, 100, 20, Color.LightGray); // THE FIX: Sliding Window Camera Math int maxVisibleItems = 16; int startIndex = selectedRomIndex - (maxVisibleItems / 2); if (startIndex < 0) startIndex = 0; if (startIndex > embeddedRoms.Length - maxVisibleItems) startIndex = Math.Max(0, embeddedRoms.Length - maxVisibleItems); for (int i = 0; i < maxVisibleItems && (startIndex + i) < embeddedRoms.Length; i++) { int actualIndex = startIndex + i; string cleanName = embeddedRoms[actualIndex].Replace("Parsons.Steamdeck.ROMS.", ""); Color textColor = (actualIndex == selectedRomIndex) ? Color.Yellow : Color.White; string prefix = (actualIndex == selectedRomIndex) ? ">> " : " "; Raylib.DrawText(prefix + cleanName, 80, 160 + (i * 35), 20, textColor); } Raylib.EndDrawing(); } else { // --- EMULATOR HOTKEYS --- // If the user holds the "Select" button (or Shift on Keyboard) if (Raylib.IsGamepadButtonDown(0, GamepadButton.MiddleLeft) || Raylib.IsKeyDown(KeyboardKey.LeftShift)) { // Select + Start = Return to Menu if (Raylib.IsGamepadButtonPressed(0, GamepadButton.MiddleRight) || Raylib.IsKeyPressed(KeyboardKey.Enter)) { isMenuState = true; machine.Reset(); } // Select + R1 = Save State if (Raylib.IsGamepadButtonPressed(0, GamepadButton.RightTrigger1) || Raylib.IsKeyPressed(KeyboardKey.F5)) { machine.SaveState("steamdeck.state"); } // Select + L1 = Load State if (Raylib.IsGamepadButtonPressed(0, GamepadButton.LeftTrigger1) || Raylib.IsKeyPressed(KeyboardKey.F8)) { machine.LoadState("steamdeck.state"); } } // --- EMULATOR LOGIC --- 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; if (Raylib.IsKeyDown(KeyboardKey.O) || Raylib.IsGamepadButtonDown(0, GamepadButton.RightFaceDown)) pad &= 0xEF; if (Raylib.IsKeyDown(KeyboardKey.P) || Raylib.IsGamepadButtonDown(0, GamepadButton.RightFaceRight)) pad &= 0xDF; machine.IoBus.Joypad1Keyboard = pad; machine.RunFrame(); // C. Flush Audio Buffer to the Sound Card // THE FIX: Do not wait for Raylib to ask! Shove the exact number of // samples we generated this frame directly into the ring buffer. float[] samples = audioPlayer.GetQueuedSamples(); if (samples.Length > 0) { unsafe { fixed (float* p = samples) { Raylib.UpdateAudioStream(audioStream, p, samples.Length); } } } //if (Raylib.IsAudioStreamProcessed(audioStream)) //{ // if (audioPlayer.AvailableSamples >= 1024) // { // float[] samples = audioPlayer.GetSamples(1024); // unsafe // { // fixed (float* p = samples) // { // Raylib.UpdateAudioStream(audioStream, p, 1024); // } // } // } //} 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); pixelBuffer[i * 4 + 1] = (byte)((argb >> 8) & 0xFF); pixelBuffer[i * 4 + 2] = (byte)(argb & 0xFF); pixelBuffer[i * 4 + 3] = 255; } unsafe { fixed (byte* p = pixelBuffer) { Raylib.UpdateTexture(vdpTexture, p); } } 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); Raylib.DrawTexturePro(vdpTexture, sourceRec, destRec, new Vector2(0, 0), 0.0f, Color.White); Raylib.EndDrawing(); } } Raylib.UnloadTexture(vdpTexture); Raylib.UnloadAudioStream(audioStream); Raylib.CloseAudioDevice(); Raylib.CloseWindow(); } private static string[] GetEmbeddedRoms() { // Pulls all embedded files and filters for Master System and Game Gear return System.Reflection.Assembly.GetExecutingAssembly() .GetManifestResourceNames() .Where(r => r.EndsWith(".sms", StringComparison.OrdinalIgnoreCase) || r.EndsWith(".gg", StringComparison.OrdinalIgnoreCase)) .ToArray(); } private static byte[] LoadEmbeddedRomBytes(string resourceName) { using (Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) using (MemoryStream ms = new MemoryStream()) { stream.CopyTo(ms); return ms.ToArray(); } } } }