Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions CoreBoy.MonoGame/Config.cs
Original file line number Diff line number Diff line change
@@ -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<Keys>(keyStr, out var key);
if (!canParse)
{
key = fallbackKey;
}

return (button, key);
}

internal IReadOnlyDictionary<Button, Keys> 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<Config>(json, options);
return config;
}
}
}
21 changes: 21 additions & 0 deletions CoreBoy.MonoGame/Config.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
31 changes: 31 additions & 0 deletions CoreBoy.MonoGame/CoreBoy.MonoGame.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<MonoGameContentReference Include="**\*.mgcb" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="MonoGame.Content.Builder" Version="3.7.0.4" />
<PackageReference Include="MonoGame.Framework.DesktopGL.Core" Version="3.7.0.7" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CoreBoy\CoreBoy.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="Config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
<Folder Include="Games\" />
</ItemGroup>

</Project>
141 changes: 141 additions & 0 deletions CoreBoy.MonoGame/MonoGameEmulatorSurface.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
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
{
internal class MonoGameEmulatorSurface : Game, IController
{
private readonly Config _config;
private readonly Emulator _emulator;
private readonly GameboyOptions _gameboyOptions;
private readonly List<Keys> _downKeys = new List<Keys>();
private readonly IReadOnlyDictionary<Button, Keys> _buttonKeyMap;
private readonly object _updateLock = new object();
private readonly GraphicsDeviceManager _graphics;
private readonly CancellationTokenSource _cancellation;

private Texture2D _currentFrame;
private SpriteBatch _spriteBatch;
private IButtonListener _listener;

internal MonoGameEmulatorSurface()
{
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 += Game_Exiting;
}

protected override void Initialize()
{
_emulator.Controller = this;
_emulator.Display.OnFrameProduced += UpdateDisplay;

try
{
_gameboyOptions.Rom = Path.Combine(Environment.CurrentDirectory, "Games", _config.Game);
_emulator.Run(_cancellation.Token);
}
catch (Exception ex)
{
Console.WriteLine("Failed to start emulator. " + ex.Message);
Exit();
}

base.Initialize();
}

protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
}

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

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;
}
}
14 changes: 14 additions & 0 deletions CoreBoy.MonoGame/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace CoreBoy.MonoGame
{
public static class Program
{
[STAThread]
static void Main()
{
using var game = new MonoGameEmulatorSurface();
game.Run();
}
}
}
10 changes: 8 additions & 2 deletions CoreBoy.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions CoreBoy/CoreBoy.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
Expand All @@ -7,7 +7,6 @@
<RootNamespace>CoreBoy</RootNamespace>
<AssemblyName>CoreBoy</AssemblyName>
<StartupObject></StartupObject>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>

<ItemGroup>
Expand Down
9 changes: 7 additions & 2 deletions CoreBoy/gui/WinSound.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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()
Expand All @@ -39,7 +44,7 @@ public void Play(int left, int right)
return;
}

//Beep((uint)left, 5);*/
//Beep((uint)left, 5);*/
}
}

Expand Down