From 873454a65f75533ab96dd787fc7496047323faee Mon Sep 17 00:00:00 2001 From: Nils Drescher Date: Thu, 23 Apr 2020 20:58:06 +0200 Subject: [PATCH 1/7] add monogame project crude game1.cs implementation --- CoreBoy.MonoGame/CoreBoy.MonoGame.csproj | 30 +++++ CoreBoy.MonoGame/Game1.cs | 140 +++++++++++++++++++++++ CoreBoy.MonoGame/Program.cs | 14 +++ CoreBoy.sln | 10 +- 4 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 CoreBoy.MonoGame/CoreBoy.MonoGame.csproj create mode 100644 CoreBoy.MonoGame/Game1.cs create mode 100644 CoreBoy.MonoGame/Program.cs diff --git a/CoreBoy.MonoGame/CoreBoy.MonoGame.csproj b/CoreBoy.MonoGame/CoreBoy.MonoGame.csproj new file mode 100644 index 0000000..0b497eb --- /dev/null +++ b/CoreBoy.MonoGame/CoreBoy.MonoGame.csproj @@ -0,0 +1,30 @@ + + + + WinExe + netcoreapp3.1 + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/CoreBoy.MonoGame/Game1.cs b/CoreBoy.MonoGame/Game1.cs new file mode 100644 index 0000000..3287c54 --- /dev/null +++ b/CoreBoy.MonoGame/Game1.cs @@ -0,0 +1,140 @@ +using CoreBoy.controller; +using CoreBoy.gui; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace CoreBoy.MonoGame +{ + public class Game1 : Game, IController + { + GraphicsDeviceManager graphics; + SpriteBatch spriteBatch; + + private IButtonListener _listener; + + private Texture2D _currentFrame; + private readonly Emulator _emulator; + private readonly GameboyOptions _gameboyOptions; + private CancellationTokenSource _cancellation; + + private readonly object _updateLock = new object(); + + public Game1() + { + graphics = new GraphicsDeviceManager(this); + Content.RootDirectory = "Content"; + IsMouseVisible = true; + Window.AllowUserResizing = true; + + _cancellation = new CancellationTokenSource(); + _gameboyOptions = new GameboyOptions(); + _emulator = new Emulator(_gameboyOptions); + + Exiting += Game1_Exiting; + } + + private void Game1_Exiting(object sender, EventArgs e) + { + _emulator.Stop(_cancellation); + _cancellation.Cancel(); + } + + protected override void Initialize() + { + _emulator.Controller = this; + _emulator.Display.OnFrameProduced += UpdateDisplay; + + _gameboyOptions.Rom = Path.Combine(Environment.CurrentDirectory, "Content", "pokemon.gb"); + _emulator.Run(_cancellation.Token); + + base.Initialize(); + } + + protected override void LoadContent() + { + spriteBatch = new SpriteBatch(GraphicsDevice); + } + + private void UpdateDisplay(object _, byte[] frame) + { + if (!Monitor.TryEnter(_updateLock)) return; + + try + { + using var memoryStream = new MemoryStream(frame); + _currentFrame = Texture2D.FromStream(GraphicsDevice, memoryStream); + } + catch + { + // YOLO + } + finally + { + Monitor.Exit(_updateLock); + } + } + + private List _downKeys = new List(); + private Dictionary _buttonKeyMap = new Dictionary() + { + { Button.Up, Keys.Up }, + { Button.Down, Keys.Down }, + { Button.Left, Keys.Left }, + { Button.Right, Keys.Right }, + { Button.Start, Keys.Enter }, + { Button.Select, Keys.Space }, + { Button.A, Keys.Z }, + { Button.B, Keys.X }, + }; + + protected override void Update(GameTime gameTime) + { + var kState = Keyboard.GetState(); + + if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || kState.IsKeyDown(Keys.Escape)) + Exit(); + + foreach (var buttonKey in _buttonKeyMap) + { + var button = buttonKey.Key; + var key = buttonKey.Value; + if (!_downKeys.Contains(key) && kState.IsKeyDown(key)) + { + _downKeys.Add(key); + _listener.OnButtonPress(button); + } + else if (_downKeys.Contains(key) && kState.IsKeyUp(key)) + { + _downKeys.Remove(key); + _listener.OnButtonRelease(button); + } + } + + + base.Update(gameTime); + } + + protected override void Draw(GameTime gameTime) + { + if (_currentFrame != null) + { + GraphicsDevice.Clear(Color.Black); + + spriteBatch.Begin(samplerState: SamplerState.PointClamp); + + spriteBatch.Draw(_currentFrame, new Rectangle(0, 0, Window.ClientBounds.Width, Window.ClientBounds.Height), Color.White); + + spriteBatch.End(); + } + + base.Draw(gameTime); + } + + public void SetButtonListener(IButtonListener listener) => _listener = listener; + } +} diff --git a/CoreBoy.MonoGame/Program.cs b/CoreBoy.MonoGame/Program.cs new file mode 100644 index 0000000..6bbc053 --- /dev/null +++ b/CoreBoy.MonoGame/Program.cs @@ -0,0 +1,14 @@ +using System; + +namespace CoreBoy.MonoGame +{ + public static class Program + { + [STAThread] + static void Main() + { + using (var game = new Game1()) + game.Run(); + } + } +} diff --git a/CoreBoy.sln b/CoreBoy.sln index 44191b6..bd24ab2 100644 --- a/CoreBoy.sln +++ b/CoreBoy.sln @@ -9,9 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreBoy.Test.Unit", "CoreBo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreBoy.Test.Integration", "CoreBoy.Test.Integration\CoreBoy.Test.Integration.csproj", "{EAB44CCA-A65A-48CC-8ABB-096205788883}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreBoy.Windows", "CoreBoy.Windows\CoreBoy.Windows.csproj", "{66AA473C-2F18-4A72-8F96-5F5A3E05F706}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreBoy.Windows", "CoreBoy.Windows\CoreBoy.Windows.csproj", "{66AA473C-2F18-4A72-8F96-5F5A3E05F706}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreBoy.Cli", "CoreBoy.Cli\CoreBoy.Cli.csproj", "{FB968AD4-425E-4BE5-8467-0DAD35591C62}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreBoy.Cli", "CoreBoy.Cli\CoreBoy.Cli.csproj", "{FB968AD4-425E-4BE5-8467-0DAD35591C62}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreBoy.MonoGame", "CoreBoy.MonoGame\CoreBoy.MonoGame.csproj", "{BCF1BF6D-E340-400A-B29C-1FFDC14E7D6B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,6 +41,10 @@ Global {FB968AD4-425E-4BE5-8467-0DAD35591C62}.Debug|Any CPU.Build.0 = Debug|Any CPU {FB968AD4-425E-4BE5-8467-0DAD35591C62}.Release|Any CPU.ActiveCfg = Release|Any CPU {FB968AD4-425E-4BE5-8467-0DAD35591C62}.Release|Any CPU.Build.0 = Release|Any CPU + {BCF1BF6D-E340-400A-B29C-1FFDC14E7D6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCF1BF6D-E340-400A-B29C-1FFDC14E7D6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCF1BF6D-E340-400A-B29C-1FFDC14E7D6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCF1BF6D-E340-400A-B29C-1FFDC14E7D6B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 6f9699a8b15ad73eb339db202037e21ec0fb47cb Mon Sep 17 00:00:00 2001 From: Nils Drescher Date: Thu, 23 Apr 2020 20:59:22 +0200 Subject: [PATCH 2/7] add content file --- CoreBoy.MonoGame/Content/Content.mgcb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 CoreBoy.MonoGame/Content/Content.mgcb diff --git a/CoreBoy.MonoGame/Content/Content.mgcb b/CoreBoy.MonoGame/Content/Content.mgcb new file mode 100644 index 0000000..ddc4c36 --- /dev/null +++ b/CoreBoy.MonoGame/Content/Content.mgcb @@ -0,0 +1,15 @@ + +#----------------------------- Global Properties ----------------------------# + +/outputDir:bin/$(Platform) +/intermediateDir:obj/$(Platform) +/platform:DesktopGL +/config: +/profile:Reach +/compress:False + +#-------------------------------- References --------------------------------# + + +#---------------------------------- Content ---------------------------------# + From c0a174b7fbf57b9b97032948fe87f18ac84eb979 Mon Sep 17 00:00:00 2001 From: Nils Drescher Date: Thu, 23 Apr 2020 21:00:06 +0200 Subject: [PATCH 3/7] remove rom references --- CoreBoy.MonoGame/CoreBoy.MonoGame.csproj | 9 --------- 1 file changed, 9 deletions(-) diff --git a/CoreBoy.MonoGame/CoreBoy.MonoGame.csproj b/CoreBoy.MonoGame/CoreBoy.MonoGame.csproj index 0b497eb..31fd919 100644 --- a/CoreBoy.MonoGame/CoreBoy.MonoGame.csproj +++ b/CoreBoy.MonoGame/CoreBoy.MonoGame.csproj @@ -18,13 +18,4 @@ - - - PreserveNewest - - - PreserveNewest - - - From 6cf08cbb346b06c9a578c67d5c63288bd76b6aea Mon Sep 17 00:00:00 2001 From: Nils Drescher Date: Thu, 23 Apr 2020 23:54:56 +0200 Subject: [PATCH 4/7] Update code, add config --- CoreBoy.MonoGame/Config.cs | 84 +++++++++++++ CoreBoy.MonoGame/Config.json | 21 ++++ CoreBoy.MonoGame/Content/Content.mgcb | 15 --- CoreBoy.MonoGame/CoreBoy.MonoGame.csproj | 10 ++ .../{Game1.cs => MonoGameEmulatorSurface.cs} | 115 +++++++++--------- CoreBoy.MonoGame/Program.cs | 4 +- 6 files changed, 175 insertions(+), 74 deletions(-) create mode 100644 CoreBoy.MonoGame/Config.cs create mode 100644 CoreBoy.MonoGame/Config.json delete mode 100644 CoreBoy.MonoGame/Content/Content.mgcb rename CoreBoy.MonoGame/{Game1.cs => MonoGameEmulatorSurface.cs} (63%) diff --git a/CoreBoy.MonoGame/Config.cs b/CoreBoy.MonoGame/Config.cs new file mode 100644 index 0000000..93bc426 --- /dev/null +++ b/CoreBoy.MonoGame/Config.cs @@ -0,0 +1,84 @@ +using CoreBoy.controller; +using Microsoft.Xna.Framework.Input; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; + +namespace CoreBoy.MonoGame +{ + internal struct ConfigWindow + { + private const int GB_WIDTH = 160; + private const int GB_HEIGHT = 144; + + public int Width { get; set; } + public int Height { get; set; } + public double Scale { get; set; } + + public int WindowWidth => (int)Math.Clamp(Width * Scale, GB_WIDTH, 3840); + public int WindowHeight => (int)Math.Clamp(Height * Scale, GB_HEIGHT, 2160); + } + + internal struct ConfigKeymap + { + public string A { get; set; } + public string B { get; set; } + public string Left { get; set; } + public string Right { get; set; } + public string Up { get; set; } + public string Down { get; set; } + public string Start { get; set; } + public string Select { get; set; } + + private static (Button button, Keys key) GetButtonKeyPair(Button button, string keyStr, Keys fallbackKey) + { + var canParse = Enum.TryParse(keyStr, out var key); + if (!canParse) + { + key = fallbackKey; + } + + return (button, key); + } + + internal IReadOnlyDictionary GetButtonKeyMap() + { + return new[] + { + GetButtonKeyPair(Button.A, A, Keys.Z), + GetButtonKeyPair(Button.B, B, Keys.X), + GetButtonKeyPair(Button.Left, Left, Keys.Left), + GetButtonKeyPair(Button.Right, Right, Keys.Right), + GetButtonKeyPair(Button.Down, Down, Keys.Down), + GetButtonKeyPair(Button.Up, Up, Keys.Up), + GetButtonKeyPair(Button.Start, Start, Keys.Enter), + GetButtonKeyPair(Button.Select, Select, Keys.Space), + }.ToDictionary(k => k.button, v => v.key); + } + } + + internal sealed class Config + { + public string Game { get; set; } + public ConfigWindow Window { get; set; } + public ConfigKeymap Keymap { get; set; } + + internal static Config Load() + { + var filePath = Path.Combine(Environment.CurrentDirectory, "Config.json"); + var json = File.ReadAllText(filePath); + + var options = new JsonSerializerOptions + { + AllowTrailingCommas = true, + PropertyNameCaseInsensitive = true, + ReadCommentHandling = JsonCommentHandling.Skip + }; + + var config = JsonSerializer.Deserialize(json, options); + return config; + } + } +} diff --git a/CoreBoy.MonoGame/Config.json b/CoreBoy.MonoGame/Config.json new file mode 100644 index 0000000..109b894 --- /dev/null +++ b/CoreBoy.MonoGame/Config.json @@ -0,0 +1,21 @@ +{ + // Game file located in ./Games/ + "game": "game.gb", + // Window size at startup + "window": { + "width": 160, + "height": 144, + "scale": 4 + }, + // Game Boy buttons mapped to keyboard buttons + "keymap": { + "a": "Z", + "b": "X", + "left": "Left", + "right": "Right", + "up": "Up", + "down": "Down", + "start": "Enter", + "select": "Space" + } +} \ No newline at end of file diff --git a/CoreBoy.MonoGame/Content/Content.mgcb b/CoreBoy.MonoGame/Content/Content.mgcb deleted file mode 100644 index ddc4c36..0000000 --- a/CoreBoy.MonoGame/Content/Content.mgcb +++ /dev/null @@ -1,15 +0,0 @@ - -#----------------------------- Global Properties ----------------------------# - -/outputDir:bin/$(Platform) -/intermediateDir:obj/$(Platform) -/platform:DesktopGL -/config: -/profile:Reach -/compress:False - -#-------------------------------- References --------------------------------# - - -#---------------------------------- Content ---------------------------------# - diff --git a/CoreBoy.MonoGame/CoreBoy.MonoGame.csproj b/CoreBoy.MonoGame/CoreBoy.MonoGame.csproj index 31fd919..09f597f 100644 --- a/CoreBoy.MonoGame/CoreBoy.MonoGame.csproj +++ b/CoreBoy.MonoGame/CoreBoy.MonoGame.csproj @@ -18,4 +18,14 @@ + + + PreserveNewest + + + + + + + diff --git a/CoreBoy.MonoGame/Game1.cs b/CoreBoy.MonoGame/MonoGameEmulatorSurface.cs similarity index 63% rename from CoreBoy.MonoGame/Game1.cs rename to CoreBoy.MonoGame/MonoGameEmulatorSurface.cs index 3287c54..3b131d7 100644 --- a/CoreBoy.MonoGame/Game1.cs +++ b/CoreBoy.MonoGame/MonoGameEmulatorSurface.cs @@ -10,38 +10,40 @@ namespace CoreBoy.MonoGame { - public class Game1 : Game, IController + internal class MonoGameEmulatorSurface : Game, IController { - GraphicsDeviceManager graphics; - SpriteBatch spriteBatch; - - private IButtonListener _listener; - - private Texture2D _currentFrame; + private readonly Config _config; private readonly Emulator _emulator; private readonly GameboyOptions _gameboyOptions; - private CancellationTokenSource _cancellation; - + private readonly List _downKeys = new List(); + private readonly IReadOnlyDictionary _buttonKeyMap; private readonly object _updateLock = new object(); + private readonly GraphicsDeviceManager _graphics; + private readonly CancellationTokenSource _cancellation; + + private Texture2D _currentFrame; + private SpriteBatch _spriteBatch; + private IButtonListener _listener; - public Game1() + internal MonoGameEmulatorSurface() { - graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; + _graphics = new GraphicsDeviceManager(this); IsMouseVisible = true; Window.AllowUserResizing = true; + _config = Config.Load(); + _buttonKeyMap = _config.Keymap.GetButtonKeyMap(); + + _graphics.PreferredBackBufferWidth = _config.Window.WindowWidth; + _graphics.PreferredBackBufferHeight = _config.Window.WindowHeight; + _graphics.ApplyChanges(); + _cancellation = new CancellationTokenSource(); _gameboyOptions = new GameboyOptions(); _emulator = new Emulator(_gameboyOptions); - Exiting += Game1_Exiting; - } - - private void Game1_Exiting(object sender, EventArgs e) - { - _emulator.Stop(_cancellation); - _cancellation.Cancel(); + Exiting += Game_Exiting; } protected override void Initialize() @@ -49,48 +51,24 @@ protected override void Initialize() _emulator.Controller = this; _emulator.Display.OnFrameProduced += UpdateDisplay; - _gameboyOptions.Rom = Path.Combine(Environment.CurrentDirectory, "Content", "pokemon.gb"); - _emulator.Run(_cancellation.Token); - - base.Initialize(); - } - - protected override void LoadContent() - { - spriteBatch = new SpriteBatch(GraphicsDevice); - } - - private void UpdateDisplay(object _, byte[] frame) - { - if (!Monitor.TryEnter(_updateLock)) return; - try { - using var memoryStream = new MemoryStream(frame); - _currentFrame = Texture2D.FromStream(GraphicsDevice, memoryStream); - } - catch - { - // YOLO + _gameboyOptions.Rom = Path.Combine(Environment.CurrentDirectory, "Games", _config.Game); + _emulator.Run(_cancellation.Token); } - finally + catch (Exception ex) { - Monitor.Exit(_updateLock); + Console.WriteLine("Failed to start emulator. " + ex.Message); + Exit(); } + + base.Initialize(); } - private List _downKeys = new List(); - private Dictionary _buttonKeyMap = new Dictionary() + protected override void LoadContent() { - { Button.Up, Keys.Up }, - { Button.Down, Keys.Down }, - { Button.Left, Keys.Left }, - { Button.Right, Keys.Right }, - { Button.Start, Keys.Enter }, - { Button.Select, Keys.Space }, - { Button.A, Keys.Z }, - { Button.B, Keys.X }, - }; + _spriteBatch = new SpriteBatch(GraphicsDevice); + } protected override void Update(GameTime gameTime) { @@ -125,16 +103,39 @@ protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.Black); - spriteBatch.Begin(samplerState: SamplerState.PointClamp); - - spriteBatch.Draw(_currentFrame, new Rectangle(0, 0, Window.ClientBounds.Width, Window.ClientBounds.Height), Color.White); - - spriteBatch.End(); + _spriteBatch.Begin(samplerState: SamplerState.PointClamp); + _spriteBatch.Draw(_currentFrame, new Rectangle(0, 0, Window.ClientBounds.Width, Window.ClientBounds.Height), Color.White); + _spriteBatch.End(); } base.Draw(gameTime); } + private void Game_Exiting(object sender, EventArgs e) + { + _emulator.Stop(_cancellation); + _cancellation.Cancel(); + } + + private void UpdateDisplay(object _, byte[] frame) + { + if (!Monitor.TryEnter(_updateLock)) return; + + try + { + using var memoryStream = new MemoryStream(frame); + _currentFrame = Texture2D.FromStream(GraphicsDevice, memoryStream); + } + catch (Exception ex) + { + Console.WriteLine("Received error reading frame " + ex.Message); + } + finally + { + Monitor.Exit(_updateLock); + } + } + public void SetButtonListener(IButtonListener listener) => _listener = listener; } } diff --git a/CoreBoy.MonoGame/Program.cs b/CoreBoy.MonoGame/Program.cs index 6bbc053..e270c41 100644 --- a/CoreBoy.MonoGame/Program.cs +++ b/CoreBoy.MonoGame/Program.cs @@ -7,8 +7,8 @@ public static class Program [STAThread] static void Main() { - using (var game = new Game1()) - game.Run(); + using var game = new MonoGameEmulatorSurface(); + game.Run(); } } } From c47d61452ad28bf05ebc95dd15816c8eb07a3b67 Mon Sep 17 00:00:00 2001 From: Nils Drescher Date: Thu, 23 Apr 2020 23:58:42 +0200 Subject: [PATCH 5/7] don't use windows for lib --- CoreBoy/CoreBoy.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CoreBoy/CoreBoy.csproj b/CoreBoy/CoreBoy.csproj index 41b3030..ad2b2dd 100644 --- a/CoreBoy/CoreBoy.csproj +++ b/CoreBoy/CoreBoy.csproj @@ -1,4 +1,4 @@ - + Library @@ -7,7 +7,6 @@ CoreBoy CoreBoy - true From 01d77545757689ead26e93c68946859abfa6eea7 Mon Sep 17 00:00:00 2001 From: Nils Drescher Date: Fri, 24 Apr 2020 00:04:48 +0200 Subject: [PATCH 6/7] disable win sound --- CoreBoy/gui/WinSound.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CoreBoy/gui/WinSound.cs b/CoreBoy/gui/WinSound.cs index 1f5254f..bf6c218 100644 --- a/CoreBoy/gui/WinSound.cs +++ b/CoreBoy/gui/WinSound.cs @@ -23,7 +23,7 @@ public WinSound() public void Start() { - _engine = new AudioPlaybackEngine(SampleRate, 2); + // _engine = new AudioPlaybackEngine(SampleRate, 2); } public void Stop() @@ -39,7 +39,7 @@ public void Play(int left, int right) return; } - //Beep((uint)left, 5);*/ + //Beep((uint)left, 5);*/ } } From 2c57a435c4e466b9a6cb08718543fa364107d20a Mon Sep 17 00:00:00 2001 From: Nils Drescher Date: Fri, 24 Apr 2020 00:11:10 +0200 Subject: [PATCH 7/7] only enable sound for windows --- CoreBoy/gui/WinSound.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CoreBoy/gui/WinSound.cs b/CoreBoy/gui/WinSound.cs index bf6c218..79376e7 100644 --- a/CoreBoy/gui/WinSound.cs +++ b/CoreBoy/gui/WinSound.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using CoreBoy.sound; using NAudio.Wave; using NAudio.Wave.SampleProviders; @@ -23,7 +24,11 @@ public WinSound() public void Start() { - // _engine = new AudioPlaybackEngine(SampleRate, 2); + // This is Windows-only for now. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _engine = new AudioPlaybackEngine(SampleRate, 2); + } } public void Stop()