diff --git a/Emux.MonoGame/Content/Content.mgcb b/Emux.MonoGame/Content/Content.mgcb new file mode 100644 index 0000000..ddc4c36 --- /dev/null +++ b/Emux.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 ---------------------------------# + diff --git a/Emux.MonoGame/Emux.MonoGame.csproj b/Emux.MonoGame/Emux.MonoGame.csproj new file mode 100644 index 0000000..5f55475 --- /dev/null +++ b/Emux.MonoGame/Emux.MonoGame.csproj @@ -0,0 +1,16 @@ + + + WinExe + netcoreapp2.0 + + + + + + + + + + + + \ No newline at end of file diff --git a/Emux.MonoGame/EmuxHost.cs b/Emux.MonoGame/EmuxHost.cs new file mode 100644 index 0000000..304af9b --- /dev/null +++ b/Emux.MonoGame/EmuxHost.cs @@ -0,0 +1,116 @@ +using System; +using Emux.GameBoy.Cpu; +using Emux.GameBoy.Graphics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Color = Microsoft.Xna.Framework.Color; + +namespace Emux.MonoGame +{ + public class EmuxHost : Game, IClock, IVideoOutput + { + public new event EventHandler Tick; + + private GraphicsDeviceManager _graphics; + private SpriteBatch _spriteBatch; + private Texture2D _video; + private bool _clockEnabled = false; + + public EmuxHost() + { + _graphics = new GraphicsDeviceManager(this); + Content.RootDirectory = "Content"; + IsMouseVisible = true; + } + + public GameBoy.GameBoy GameBoy + { + get; + set; + } + + protected override void Initialize() + { + base.Initialize(); + } + + protected override void LoadContent() + { + _spriteBatch = new SpriteBatch(GraphicsDevice); + + _video = new Texture2D(GraphicsDevice, 160, 144); + GameBoy.Cpu.Run(); + } + + protected override void Update(GameTime gameTime) + { + if (_clockEnabled) + Tick?.Invoke(this, EventArgs.Empty); + + if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || + Keyboard.GetState().IsKeyDown(Keys.Escape)) + Exit(); + + base.Update(gameTime); + } + + protected override void Draw(GameTime gameTime) + { + GraphicsDevice.Clear(Color.Black); + + _spriteBatch.Begin(); + + float aspectRatio = 160f / 144f; + + int screenHeight = _graphics.PreferredBackBufferHeight; + int screenWidth = _graphics.PreferredBackBufferWidth; + + int frameWidth; + int frameHeight; + if (screenHeight > screenWidth) + { + frameWidth = screenWidth; + frameHeight = (int) (frameWidth / aspectRatio); + } + else + { + frameHeight = screenHeight; + frameWidth = (int) (frameHeight / aspectRatio); + } + + _spriteBatch.Draw(_video, + new Rectangle((screenWidth - frameWidth) / 2, (screenHeight - frameHeight) / 2, frameWidth, frameHeight), + Color.White); + + _spriteBatch.End(); + + base.Draw(gameTime); + } + + public void Start() + { + _clockEnabled = true; + } + + public void Stop() + { + _clockEnabled = false; + } + + public void RenderFrame(byte[] pixelData) + { + var rawData = new byte[160 * 144 * sizeof(int)]; + + for (int i = 0, j = 0; j < pixelData.Length; i+=4, j+=3) + { + rawData[i] = pixelData[j + 2]; + rawData[i + 1] = pixelData[j + 1]; + rawData[i + 2] = pixelData[j]; + rawData[i + 3] = 255; + } + + _video.SetData(rawData); + } + } +} \ No newline at end of file diff --git a/Emux.MonoGame/Program.cs b/Emux.MonoGame/Program.cs new file mode 100644 index 0000000..a959187 --- /dev/null +++ b/Emux.MonoGame/Program.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; +using Emux.GameBoy.Audio; +using Emux.GameBoy.Cartridge; + +namespace Emux.MonoGame +{ + internal class AudioOutput : IAudioChannelOutput + { + public int SampleRate + { + get { return 1; } + } + + public void BufferSoundSamples(float[] sampleData, int offset, int length) + { + + } + } + + public static class Program + { + private static void PrintAbout() + { + Console.WriteLine("Emux.MonoGame: v{0}, Core: v{1}", + typeof(Program).Assembly.GetName().Version, + typeof(GameBoy.GameBoy).Assembly.GetName().Version); + Console.WriteLine("Copyright © Washi 2017-2018"); + Console.WriteLine("Repository and issue tracker: https://www.github.com/Washi1337/Emux"); + } + + [STAThread] + private static void Main(string[] args) + { + PrintAbout(); + + string romFile; + string saveFile; + + switch (args.Length) + { + default: + Console.WriteLine("Usage: Emux.MonoGame.exe romfile [savefile]"); + return; + case 1: + romFile = args[0].Replace("\"", ""); + saveFile = Path.ChangeExtension(romFile, ".sav"); + break; + case 2: + romFile = args[0].Replace("\"", ""); + saveFile = args[1].Replace("\"", ""); + break; + } + + if (!File.Exists(romFile)) + { + Console.WriteLine("ROM could not be found!"); + return; + } + + using (var game = new EmuxHost()) + using (var saveFs = File.Open(saveFile, FileMode.OpenOrCreate)) + { + var cartridge = new EmulatedCartridge(File.ReadAllBytes(romFile), new StreamedExternalMemory(saveFs)); + var device = new GameBoy.GameBoy(cartridge, game, false); + game.GameBoy = device; + device.Gpu.VideoOutput = game; + + for (var i = 0; i < device.Spu.Channels.Count; i++) + { + var channel = device.Spu.Channels[i]; + channel.ChannelOutput = new AudioOutput(); + channel.ChannelVolume = 0.05f; + } + + game.Run(); + } + } + } +} \ No newline at end of file diff --git a/Emux.sln b/Emux.sln index 8ddea0b..486be79 100644 --- a/Emux.sln +++ b/Emux.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emux", "Emux\Emux.csproj", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emux.GameBoy.Tests", "Emux.GameBoy.Tests\Emux.GameBoy.Tests.csproj", "{365C6CF7-99E3-4861-B63B-E9D352E386AF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emux.MonoGame", "Emux.MonoGame\Emux.MonoGame.csproj", "{A8CDE19D-11F0-47AB-8391-EFF20F3532FB}" +EndProject Global GlobalSection(Performance) = preSolution HasPerformanceSessions = true @@ -30,6 +32,10 @@ Global {365C6CF7-99E3-4861-B63B-E9D352E386AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {365C6CF7-99E3-4861-B63B-E9D352E386AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {365C6CF7-99E3-4861-B63B-E9D352E386AF}.Release|Any CPU.Build.0 = Release|Any CPU + {A8CDE19D-11F0-47AB-8391-EFF20F3532FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8CDE19D-11F0-47AB-8391-EFF20F3532FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8CDE19D-11F0-47AB-8391-EFF20F3532FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8CDE19D-11F0-47AB-8391-EFF20F3532FB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE