├── Emux
├── packages.config
├── DeviceEventArgs.cs
├── App.xaml
├── Expressions
│ ├── Token.cs
│ ├── Terminal.cs
│ └── ExpressionLexer.cs
├── SettingsBinding.cs
├── App.xaml.cs
├── Gui
│ ├── Converters
│ │ ├── InverseConverter.cs
│ │ └── HexadecimalConverter.cs
│ ├── VideoWindow.xaml
│ ├── InputDialog.xaml.cs
│ ├── AboutDialog.xaml.cs
│ ├── InputDialog.xaml
│ ├── BreakpointDialog.xaml.cs
│ ├── FlagsListBox.xaml
│ ├── BreakpointInfo.cs
│ ├── RegisterItem.cs
│ ├── BreakpointDialog.xaml
│ ├── KeypadWindow.xaml.cs
│ ├── KeypadWindow.xaml
│ ├── TitledOverlay.xaml.cs
│ ├── TitledOverlay.xaml
│ ├── FlagItem.cs
│ ├── AboutDialog.xaml
│ ├── CheatsWindow.xaml
│ ├── InstructionItem.cs
│ ├── AudioMixerWindow.xaml
│ ├── VideoWindow.xaml.cs
│ ├── AudioMixerWindow.xaml.cs
│ ├── CheatsWindow.xaml.cs
│ ├── FlagsListBox.xaml.cs
│ └── OptionsDialog.xaml.cs
├── WinMmTimer.cs
├── EnumDescriptions.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Settings.settings
│ └── Resources.resx
├── App.config
└── DeviceManager.cs
├── Emux.GameBoy
├── IGameBoyComponent.cs
├── Cpu
│ ├── IClock.cs
│ ├── StepEventArgs.cs
│ ├── RegisterFlags.cs
│ ├── InterruptFlags.cs
│ ├── InterruptVector.cs
│ ├── Breakpoint.cs
│ ├── Z80Disassembler.cs
│ ├── Z80OpCode.cs
│ ├── Z80Instruction.cs
│ └── RegisterBank.cs
├── Audio
│ ├── IAudioChannelOutput.cs
│ ├── SpuOutputSelection.cs
│ ├── ISoundChannel.cs
│ ├── LfsRegister.cs
│ ├── VolumeEnvelope.cs
│ ├── SquareSweepChannel.cs
│ ├── WaveSoundChannel.cs
│ └── NoiseChannel.cs
├── Timer
│ ├── TimerControlFlags.cs
│ └── GameBoyTimer.cs
├── Cartridge
│ ├── GameBoyColorFlag.cs
│ ├── IMemoryBankController.cs
│ ├── IExternalMemory.cs
│ ├── RomOnlyBankController.cs
│ ├── StreamedExternalMemory.cs
│ ├── BufferedExternalMemory.cs
│ ├── MemoryBankController2.cs
│ ├── MemoryBankController5.cs
│ ├── MemoryBankController3.cs
│ ├── MemoryBankController1.cs
│ ├── ICartridge.cs
│ ├── EmulatedCartridge.cs
│ └── CartridgeType.cs
├── Input
│ ├── GameBoyPadButton.cs
│ └── GameBoyPad.cs
├── Emux.GameBoy.csproj
├── Graphics
│ ├── Color.cs
│ ├── IVideoOutput.cs
│ ├── SpriteData.cs
│ ├── LcdStatusFlags.cs
│ └── LcdControlFlags.cs
├── Cheating
│ ├── GamesharkCode.cs
│ └── GamesharkController.cs
└── Memory
│ └── DmaController.cs
├── Emux.GameBoy.Tests
├── packages.config
├── Properties
│ └── AssemblyInfo.cs
└── Emux.GameBoy.Tests.csproj
├── Emux.sln.DotSettings
├── Emux.MonoGame
├── Content
│ ├── Content.mgcb
│ └── Calibri.spritefont
├── Emux.MonoGame.csproj
├── Settings.cs
└── Program.cs
├── Emux.NAudio
├── Emux.NAudio.csproj
├── NAudioChannelOutput.cs
└── GameBoyNAudioMixer.cs
├── README.md
└── Emux.sln
/Emux/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Emux.GameBoy/IGameBoyComponent.cs:
--------------------------------------------------------------------------------
1 | namespace Emux.GameBoy
2 | {
3 | public interface IGameBoyComponent
4 | {
5 | void Initialize();
6 |
7 | void Reset();
8 |
9 | void Shutdown();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cpu/IClock.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cpu
4 | {
5 | public interface IClock
6 | {
7 | event EventHandler Tick;
8 |
9 | void Start();
10 |
11 | void Stop();
12 | }
13 | }
--------------------------------------------------------------------------------
/Emux.GameBoy.Tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Audio/IAudioChannelOutput.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Audio
4 | {
5 | public interface IAudioChannelOutput
6 | {
7 | int SampleRate
8 | {
9 | get;
10 | }
11 |
12 | void BufferSoundSamples(Span sampleData, int offset, int length);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Emux/DeviceEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux
4 | {
5 | public class DeviceEventArgs : EventArgs
6 | {
7 | public DeviceEventArgs(GameBoy.GameBoy device)
8 | {
9 | Device = device;
10 | }
11 |
12 | public GameBoy.GameBoy Device
13 | {
14 | get;
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/Emux.GameBoy/Timer/TimerControlFlags.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Timer
4 | {
5 | [Flags]
6 | public enum TimerControlFlags : byte
7 | {
8 | Clock4096Hz = 0b00,
9 | Clock262144Hz = 0b01,
10 | Clock65536Hz = 0b10,
11 | Clock16384Hz = 0b11,
12 |
13 | ClockMask = 0b11,
14 |
15 | EnableTimer = (1 << 2),
16 | }
17 | }
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/GameBoyColorFlag.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Emux.GameBoy.Cartridge
8 | {
9 | public enum GameBoyColorFlag : byte
10 | {
11 | OriginalGameBoy = 0,
12 | SupportsColor = 0x80,
13 | GameBoyColorOnly = 0xC0,
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cpu/StepEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cpu
4 | {
5 | public delegate void StepEventHandler(object sender, StepEventArgs args);
6 |
7 | public class StepEventArgs : EventArgs
8 | {
9 | public StepEventArgs(int cycles)
10 | {
11 | Cycles = cycles;
12 | }
13 |
14 | public int Cycles
15 | {
16 | get;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Emux.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
--------------------------------------------------------------------------------
/Emux.GameBoy/Cpu/RegisterFlags.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cpu
4 | {
5 | ///
6 | /// Provides members for representing all the register flags known by the GameBoy CPU.
7 | ///
8 | [Flags]
9 | public enum RegisterFlags : byte
10 | {
11 | None = 0,
12 | Z = 1 << 7,
13 | N = 1 << 6,
14 | H = 1 << 5,
15 | C = 1 << 4,
16 | All = Z | N | H | C
17 | }
18 | }
--------------------------------------------------------------------------------
/Emux.GameBoy/Cpu/InterruptFlags.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cpu
4 | {
5 | ///
6 | /// Provides valid interrupt flags used by the interrupt registers IF and IE.
7 | ///
8 | [Flags]
9 | public enum InterruptFlags : byte
10 | {
11 | None = 0,
12 | VBlank = (1 << 0),
13 | LcdStat = (1 << 1),
14 | Timer = (1 << 2),
15 | Serial = (1 << 3),
16 | Joypad = (1 << 4)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/IMemoryBankController.cs:
--------------------------------------------------------------------------------
1 | namespace Emux.GameBoy.Cartridge
2 | {
3 | ///
4 | /// Provides methods for emulation of a memory bank controller (MBC).
5 | ///
6 | public interface IMemoryBankController : IGameBoyComponent
7 | {
8 | byte ReadByte(ushort address);
9 |
10 | void ReadBytes(ushort address, byte[] buffer, int bufferOffset, int length);
11 |
12 | void WriteByte(ushort address, byte value);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Emux/App.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Audio/SpuOutputSelection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Audio
4 | {
5 | [Flags]
6 | public enum SpuOutputSelection : byte
7 | {
8 | OutputChannel1ToS01 = 1 << 0,
9 | OutputChannel2ToS01 = 1 << 1,
10 | OutputChannel3ToS01 = 1 << 2,
11 | OutputChannel4ToS01 = 1 << 3,
12 | OutputChannel1ToS02 = 1 << 4,
13 | OutputChannel2ToS02 = 1 << 5,
14 | OutputChannel3ToS02 = 1 << 6,
15 | OutputChannel4ToS02 = 1 << 7,
16 | }
17 | }
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/IExternalMemory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cartridge
4 | {
5 | public interface IExternalMemory : IDisposable {
6 | bool IsActive
7 | {
8 | get;
9 | }
10 |
11 | void Activate();
12 | void Deactivate();
13 | void SetBufferSize(int length);
14 | byte ReadByte(int address);
15 | void ReadBytes(int address, byte[] buffer, int offset, int length);
16 | void WriteByte(int address, byte value);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Input/GameBoyPadButton.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Input
4 | {
5 | ///
6 | /// Provides members that represent all the buttons on a GameBoy device.
7 | ///
8 | [Flags]
9 | public enum GameBoyPadButton : byte
10 | {
11 | None = 0,
12 |
13 | Right = 0x01,
14 | Left = 0x02,
15 | Up = 0x04,
16 | Down = 0x08,
17 |
18 | A = 0x10,
19 | B = 0x20,
20 | Select = 0x40,
21 | Start = 0x80,
22 | }
23 | }
--------------------------------------------------------------------------------
/Emux/Expressions/Token.cs:
--------------------------------------------------------------------------------
1 | namespace Emux.Expressions
2 | {
3 | public class Token
4 | {
5 | public Token(Terminal terminal, string text)
6 | {
7 | Terminal = terminal;
8 | Text = text;
9 | }
10 |
11 | public Terminal Terminal
12 | {
13 | get;
14 | }
15 |
16 | public string Text
17 | {
18 | get;
19 | }
20 |
21 | public override string ToString()
22 | {
23 | return $"{Text} ({Terminal})";
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/Emux/SettingsBinding.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Data;
2 | using Emux.Properties;
3 |
4 | namespace Emux
5 | {
6 | public class SettingBinding : Binding
7 | {
8 | public SettingBinding()
9 | {
10 | Initialize();
11 | }
12 |
13 | public SettingBinding(string path)
14 | : base(path)
15 | {
16 | Initialize();
17 | }
18 |
19 | private void Initialize()
20 | {
21 | Source = Settings.Default;
22 | Mode = BindingMode.TwoWay;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Emux.GameBoy.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 | Emux.GameBoy
5 | 0.1.0.0
6 | GameBoy emulator engine
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Graphics/Color.cs:
--------------------------------------------------------------------------------
1 | namespace Emux.GameBoy.Graphics
2 | {
3 | ///
4 | /// Represents a 24 bit color.
5 | ///
6 | public struct Color
7 | {
8 | public byte R;
9 | public byte G;
10 | public byte B;
11 |
12 | public Color(byte r, byte g, byte b)
13 | {
14 | R = r;
15 | G = g;
16 | B = b;
17 | }
18 |
19 | public override string ToString()
20 | {
21 | return $"{nameof(R)}: {R}, {nameof(G)}: {G}, {nameof(B)}: {B}";
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Emux/Expressions/Terminal.cs:
--------------------------------------------------------------------------------
1 | namespace Emux.Expressions
2 | {
3 | public enum Terminal
4 | {
5 | Not,
6 |
7 | Equals,
8 | NotEquals,
9 | LessThan,
10 | LessThanOrEqual,
11 | GreaterThan,
12 | GreaterThanOrEqual,
13 |
14 | Plus,
15 | Minus,
16 |
17 | BooleanAnd,
18 | BitwiseAnd,
19 | BooleanOr,
20 | BitwiseOr,
21 |
22 | Register,
23 | Flag,
24 |
25 | Decimal,
26 | Hexadecimal,
27 | RPar,
28 | LPar
29 | }
30 | }
--------------------------------------------------------------------------------
/Emux.GameBoy/Cpu/InterruptVector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Emux.GameBoy.Cpu
6 | {
7 | public enum InterruptVector : ushort
8 | {
9 | Custom1= 0x08,
10 | Custom2 = 0x10,
11 | Custom3= 0x18,
12 | Custom4= 0x20,
13 | Custom5= 0x28,
14 | Custom6= 0x30,
15 | Custom7= 0x38,
16 | VBlank = GameBoyCpu.InterruptStartAddr,
17 | LcdStat = GameBoyCpu.InterruptStartAddr + 8, // 0x48
18 | Timer = GameBoyCpu.InterruptStartAddr + 16, // 0x50
19 | Serial = GameBoyCpu.InterruptStartAddr + 24, // 0x58
20 | Joypad = GameBoyCpu.InterruptStartAddr + 32 // 0x60
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Emux.MonoGame/Content/Content.mgcb:
--------------------------------------------------------------------------------
1 |
2 | #----------------------------- Global Properties ----------------------------#
3 |
4 | /outputDir:bin/$(Platform)
5 | /intermediateDir:obj/$(Platform)
6 | /platform:DesktopGL
7 | /config:
8 | /profile:Reach
9 | /compress:False
10 |
11 | #-------------------------------- References --------------------------------#
12 |
13 |
14 | #---------------------------------- Content ---------------------------------#
15 |
16 | #begin Calibri.spritefont
17 | /importer:FontDescriptionImporter
18 | /processor:FontDescriptionProcessor
19 | /processorParam:PremultiplyAlpha=True
20 | /processorParam:TextureFormat=Compressed
21 | /build:Calibri.spritefont
22 |
23 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Graphics/IVideoOutput.cs:
--------------------------------------------------------------------------------
1 | namespace Emux.GameBoy.Graphics
2 | {
3 | ///
4 | /// Represents a video output device.
5 | ///
6 | public interface IVideoOutput
7 | {
8 | ///
9 | /// Renders a frame to the output device.
10 | ///
11 | /// The 24bit RGB pixel data that represents the 160x144 bitmap to render.
12 | void RenderFrame(byte[] pixelData);
13 | }
14 |
15 | ///
16 | /// Represents a virtual video output device that does not output anything.
17 | ///
18 | public sealed class EmptyVideoOutput : IVideoOutput
19 | {
20 | public void RenderFrame(byte[] pixelData)
21 | {
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cpu/Breakpoint.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cpu
4 | {
5 | public class Breakpoint
6 | {
7 | public static readonly Predicate BreakAlways = _ => true;
8 |
9 | public Breakpoint(ushort offset)
10 | : this(offset, BreakAlways)
11 | {
12 | }
13 |
14 | public Breakpoint(ushort offset, Predicate condition)
15 | {
16 | Offset = offset;
17 | Condition = condition;
18 | }
19 |
20 | public ushort Offset
21 | {
22 | get;
23 | }
24 |
25 | public Predicate Condition
26 | {
27 | get;
28 | set;
29 | }
30 |
31 | public override string ToString()
32 | {
33 | return Offset.ToString("X4");
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/Emux.MonoGame/Emux.MonoGame.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | netcoreapp2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ..\packages\NAudio.1.9.0-preview1\lib\net35\NAudio.dll
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Emux/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using Emux.Expressions;
3 | using Emux.GameBoy.Cpu;
4 | using Emux.Properties;
5 |
6 | namespace Emux
7 | {
8 | ///
9 | /// Interaction logic for App.xaml
10 | ///
11 | public partial class App : Application
12 | {
13 | public App()
14 | {
15 | System.Windows.Forms.Application.EnableVisualStyles();
16 | DeviceManager = new DeviceManager();
17 | Settings.Default.Reload();
18 | }
19 |
20 | public new static App Current
21 | {
22 | get { return (App) Application.Current; }
23 | }
24 |
25 | public DeviceManager DeviceManager
26 | {
27 | get;
28 | }
29 |
30 | protected override void OnExit(ExitEventArgs e)
31 | {
32 | Settings.Default.Save();
33 | base.OnExit(e);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Graphics/SpriteData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace Emux.GameBoy.Graphics
5 | {
6 | ///
7 | /// Represents the structure of a sprite located in the Object Attribute Memory (OAM).
8 | ///
9 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
10 | public struct SpriteData
11 | {
12 | public byte Y;
13 | public byte X;
14 | public byte TileDataIndex;
15 | public SpriteDataFlags Flags;
16 | }
17 |
18 | ///
19 | /// Provides members for representing all the valid sprite atrributes.
20 | ///
21 | [Flags]
22 | public enum SpriteDataFlags : byte
23 | {
24 | None = 0,
25 | PaletteNumberMask = 0b111,
26 | TileVramBank = (1 << 3),
27 | UsePalette1 = (1 << 4),
28 | XFlip = (1 << 5),
29 | YFlip = (1 << 6),
30 | BelowBackground = (1 << 7)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Emux/Gui/Converters/InverseConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 |
5 | namespace Emux.Gui.Converters
6 | {
7 | public class InverseConverter : IValueConverter
8 | {
9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | if (targetType != typeof(bool))
12 | throw new NotSupportedException();
13 | if (value == null)
14 | throw new ArgumentNullException(nameof(value));
15 | return !((bool) value);
16 | }
17 |
18 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
19 | {
20 | if (targetType != typeof(bool))
21 | throw new NotSupportedException();
22 | if (value == null)
23 | throw new ArgumentNullException(nameof(value));
24 | return !((bool)value);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Emux.NAudio/Emux.NAudio.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 |
5 |
6 | true
7 |
8 |
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | ..\packages\NAudio.1.9.0-preview1\lib\net35\NAudio.dll
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cheating/GamesharkCode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cheating
4 | {
5 | public class GamesharkCode
6 | {
7 | public GamesharkCode(byte[] code)
8 | {
9 | RawCode = new byte[4];
10 | Buffer.BlockCopy(code, 0, RawCode, 0, RawCode.Length);
11 | Enabled = true;
12 | }
13 |
14 | public byte CodeType
15 | {
16 | get { return RawCode[0]; }
17 | }
18 |
19 | public byte Value
20 | {
21 | get { return RawCode[1]; }
22 | }
23 |
24 | public ushort Address
25 | {
26 | get { return (ushort) (RawCode[2] | (RawCode[3] << 8)); }
27 | }
28 |
29 | public bool Enabled
30 | {
31 | get;
32 | set;
33 | }
34 |
35 | public byte[] RawCode
36 | {
37 | get;
38 | }
39 |
40 | public string Description
41 | {
42 | get;
43 | set;
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/Emux/Gui/VideoWindow.xaml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Emux/Gui/InputDialog.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace Emux.Gui
4 | {
5 | ///
6 | /// Interaction logic for InputDialog.xaml
7 | ///
8 | public partial class InputDialog : Window
9 | {
10 | public InputDialog()
11 | {
12 | InitializeComponent();
13 | }
14 |
15 | public string Text
16 | {
17 | get { return ContentsTextBox.Text; }
18 | set { ContentsTextBox.Text = value; }
19 | }
20 |
21 | private void CancelButtonOnClick(object sender, RoutedEventArgs e)
22 | {
23 | DialogResult = false;
24 | Close();
25 | }
26 |
27 | private void OkButtonOnClick(object sender, RoutedEventArgs e)
28 | {
29 | DialogResult = true;
30 | Close();
31 | }
32 |
33 | private void InputDialogOnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
34 | {
35 | ContentsTextBox.SelectAll();
36 | ContentsTextBox.Focus();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Emux/Gui/AboutDialog.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 |
4 | namespace Emux.Gui
5 | {
6 | ///
7 | /// Interaction logic for AboutWindow.xaml
8 | ///
9 | public partial class AboutDialog : Window
10 | {
11 | public AboutDialog()
12 | {
13 | InitializeComponent();
14 | VersionLabel.Content = typeof(AboutDialog).Assembly.GetName().Version.ToString();
15 | }
16 |
17 | private void SourceCodeHyperlinkOnRequestNavigate(object sender, RoutedEventArgs routedEventArgs)
18 | {
19 | Process.Start(Properties.Settings.Default.Repository);
20 | }
21 |
22 | private void LicenseHyperlinkOnRequestNavigate(object sender, RoutedEventArgs routedEventArgs)
23 | {
24 | Process.Start(Properties.Settings.Default.Repository + "/blob/master/LICENSE");
25 | }
26 |
27 | private void NAudioHyperlinkOnRequestNavigate(object sender, RoutedEventArgs e)
28 | {
29 | Process.Start("https://github.com/naudio/NAudio");
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Emux/Gui/Converters/HexadecimalConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 |
5 | namespace Emux.Gui.Converters
6 | {
7 | public class HexadecimalConverter : IValueConverter
8 | {
9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | if (value == null)
12 | throw new ArgumentNullException(nameof(value));
13 | if (targetType != typeof(string))
14 | throw new NotSupportedException();
15 |
16 | if (value is byte)
17 | return ((byte)value).ToString("X2");
18 | if (value is ushort)
19 | return ((ushort)value).ToString("X4");
20 | if (value is byte[])
21 | return BitConverter.ToString((byte[]) value).Replace("-", "");
22 |
23 | throw new NotSupportedException();
24 | }
25 |
26 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
27 | {
28 | throw new NotImplementedException();
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Audio/ISoundChannel.cs:
--------------------------------------------------------------------------------
1 | namespace Emux.GameBoy.Audio
2 | {
3 | public interface ISoundChannel
4 | {
5 | GameBoySpu Spu
6 | {
7 | get;
8 | }
9 |
10 | int ChannelNumber
11 | {
12 | get;
13 | }
14 |
15 | byte NR0
16 | {
17 | get;
18 | set;
19 | }
20 |
21 | byte NR1
22 | {
23 | get;
24 | set;
25 | }
26 |
27 | byte NR2
28 | {
29 | get;
30 | set;
31 | }
32 |
33 | byte NR3
34 | {
35 | get;
36 | set;
37 | }
38 |
39 | byte NR4
40 | {
41 | get;
42 | set;
43 | }
44 |
45 | bool Active
46 | {
47 | get;
48 | set;
49 | }
50 |
51 | float ChannelVolume
52 | {
53 | get;
54 | set;
55 | }
56 |
57 | IAudioChannelOutput ChannelOutput
58 | {
59 | get;
60 | set;
61 | }
62 |
63 | void ChannelStep(int cycles);
64 | }
65 | }
--------------------------------------------------------------------------------
/Emux/Gui/InputDialog.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cpu/Z80Disassembler.cs:
--------------------------------------------------------------------------------
1 | using Emux.GameBoy.Memory;
2 |
3 | namespace Emux.GameBoy.Cpu
4 | {
5 | ///
6 | /// Provides a mechanism for reading Z80 instructions from the memory of a GameBoy device.
7 | ///
8 | public class Z80Disassembler
9 | {
10 | private readonly GameBoyMemory _memory;
11 |
12 | public Z80Disassembler(GameBoyMemory memory)
13 | {
14 | _memory = memory;
15 | }
16 |
17 | ///
18 | /// Reads the next instruction from memory.
19 | ///
20 | /// The disassembled instruction.
21 | public void ReadInstruction(ref ushort location, Z80Instruction outInstruction)
22 | {
23 | ushort offset = location;
24 | byte code = _memory.ReadByte(location++);
25 |
26 | var opcode = code != Z80OpCodes.ExtendedTableOpcode
27 | ? Z80OpCodes.SingleByteOpCodes[code]
28 | : Z80OpCodes.PrefixedOpCodes[_memory.ReadByte(location++)];
29 |
30 | var operands = _memory.ReadBytes(location, opcode.OperandLength);
31 | location += (ushort)operands.Length;
32 |
33 | outInstruction.Set(offset, opcode, operands);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Emux/Gui/BreakpointDialog.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data;
3 | using System.Windows;
4 | using Emux.Expressions;
5 | using Emux.GameBoy.Cpu;
6 |
7 | namespace Emux.Gui
8 | {
9 | ///
10 | /// Interaction logic for BreakpointDialog.xaml
11 | ///
12 | public partial class BreakpointDialog : Window
13 | {
14 | private readonly BreakpointInfo _info;
15 |
16 | public BreakpointDialog(BreakpointInfo info)
17 | {
18 | _info = info;
19 | InitializeComponent();
20 | AddressTextBox.Text = info.Address.ToString("X4");
21 | ConditionTextBox.Text = info.ConditionString;
22 | }
23 |
24 | private void OkButtonOnClick(object sender, RoutedEventArgs e)
25 | {
26 | try
27 | {
28 | _info.ConditionString = ConditionTextBox.Text;
29 | Close();
30 | }
31 | catch (Exception ex)
32 | {
33 | MessageBox.Show(ex.Message, "Parser error", MessageBoxButton.OK, MessageBoxImage.Error);
34 | }
35 | }
36 |
37 | private void CancelButtonOnClick(object sender, RoutedEventArgs e)
38 | {
39 | Close();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Emux/WinMmTimer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using Emux.GameBoy;
4 | using Emux.GameBoy.Cpu;
5 |
6 | namespace Emux
7 | {
8 | internal class WinMmTimer : IClock
9 | {
10 | public delegate void MmTimerProc(uint timerid, uint msg, IntPtr user, uint dw1, uint dw2);
11 |
12 | [DllImport("winmm.dll")]
13 | private static extern uint timeSetEvent(
14 | uint uDelay,
15 | uint uResolution,
16 | [MarshalAs(UnmanagedType.FunctionPtr)] MmTimerProc lpTimeProc,
17 | uint dwUser,
18 | int fuEvent
19 | );
20 |
21 | [DllImport("winmm.dll")]
22 | private static extern uint timeKillEvent(uint timerId);
23 |
24 | private readonly MmTimerProc _callback;
25 | private readonly int _frequency;
26 | private uint _timerId;
27 |
28 | public WinMmTimer(int frequency)
29 | {
30 | _callback = (timerid, msg, user, dw1, dw2) => Tick?.Invoke(null, EventArgs.Empty);
31 | _frequency = frequency;
32 | }
33 |
34 | public event EventHandler Tick;
35 |
36 | public void Start()
37 | {
38 | _timerId = timeSetEvent((uint) (1000 / _frequency), 0, _callback, 0, 1);
39 | }
40 |
41 | public void Stop()
42 | {
43 | timeKillEvent(_timerId);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Emux/Gui/FlagsListBox.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cheating/GamesharkController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using System.Linq;
4 |
5 | namespace Emux.GameBoy.Cheating
6 | {
7 | public class GamesharkController
8 | {
9 | private GameBoy _device;
10 |
11 | public GamesharkController()
12 | {
13 | Codes = new ObservableCollection();
14 | }
15 |
16 | public GameBoy Device
17 | {
18 | get { return _device; }
19 | set
20 | {
21 | if (_device != value)
22 | {
23 | if (_device != null)
24 | _device.Gpu.VBlankStarted -= GpuOnVBlankStarted;
25 | _device = value;
26 | if (value != null)
27 | value.Gpu.VBlankStarted += GpuOnVBlankStarted;
28 | }
29 | }
30 | }
31 |
32 | public ObservableCollection Codes
33 | {
34 | get;
35 | }
36 |
37 | private void GpuOnVBlankStarted(object sender, EventArgs e)
38 | {
39 | lock (Codes)
40 | {
41 | foreach (var code in Codes)
42 | {
43 | if (code.Enabled)
44 | _device.Memory.WriteByte(code.Address, code.Value);
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Audio/LfsRegister.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Audio
4 | {
5 | ///
6 | /// Represents either a 7 bit or a 15 bit linear-feedback shift register used in the GameBoy.
7 | /// The taps that the GameBoy uses are 0 and 1.
8 | ///
9 | public class LfsRegister
10 | {
11 | private readonly ISoundChannel _channel;
12 | private short _state = 0x7F;
13 |
14 | public LfsRegister(ISoundChannel channel)
15 | {
16 | _channel = channel ?? throw new ArgumentNullException(nameof(channel));
17 | }
18 |
19 | public bool CurrentValue
20 | {
21 | get { return (_state & 1) == 1; }
22 | }
23 |
24 | public bool Use7BitStepWidth
25 | {
26 | get { return (_channel.NR3 & (1 << 3)) != 0; }
27 | set
28 | {
29 | _channel.NR3 = (byte) ((_channel.NR3 & ~(1 << 3)) | (value ? (1 << 3) : 0));
30 | Reset();
31 | }
32 | }
33 |
34 | public void Reset()
35 | {
36 | _state = (short) (Use7BitStepWidth ? 0x7F : 0x7FFF);
37 | }
38 |
39 | public void PerformShift()
40 | {
41 | byte nextBit = (byte) (((_state >> 1) & 1) ^ (_state & 1));
42 | _state >>= 1;
43 | _state |= (short) (nextBit << (Use7BitStepWidth ? 6 : 14));
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/Emux/EnumDescriptions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Windows;
3 | using System.Windows.Media;
4 |
5 | namespace Emux
6 | {
7 | public class EnumDescriptions
8 | {
9 | public EnumDescriptions()
10 | {
11 | BitmapScalingModeItems = new Dictionary
12 | {
13 | [BitmapScalingMode.Fant] = "Fant",
14 | [BitmapScalingMode.Linear] = "Linear",
15 | [BitmapScalingMode.NearestNeighbor] = "Nearest Neighbor"
16 | };
17 | StretchItems = new Dictionary
18 | {
19 | [Stretch.None] = "None",
20 | [Stretch.Fill] = "Fill",
21 | [Stretch.Uniform] = "Uniform",
22 | [Stretch.UniformToFill] = "Uniform to fill"
23 | };
24 | Scales = new Dictionary
25 | {
26 | [0] = "Custom",
27 | [1] = "1x",
28 | [2] = "2x",
29 | [4] = "4x",
30 | [8] = "8x",
31 | };
32 | }
33 |
34 | public Dictionary BitmapScalingModeItems
35 | {
36 | get;
37 | }
38 |
39 | public Dictionary StretchItems
40 | {
41 | get;
42 | }
43 |
44 | public Dictionary Scales
45 | {
46 | get;
47 | }
48 |
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Emux.GameBoy.Tests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Emux.GameBoy.Tests")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Emux.GameBoy.Tests")]
13 | [assembly: AssemblyCopyright("Copyright © 2017")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("365c6cf7-99e3-4861-b63b-e9d352e386af")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Emux/Gui/BreakpointInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using Emux.Expressions;
4 | using Emux.GameBoy.Cpu;
5 |
6 | namespace Emux.Gui
7 | {
8 | public class BreakpointInfo : INotifyPropertyChanged
9 | {
10 | public event PropertyChangedEventHandler PropertyChanged;
11 |
12 | private string _conditionString;
13 |
14 | public BreakpointInfo(Breakpoint breakpoint)
15 | {
16 | Breakpoint = breakpoint ?? throw new ArgumentNullException(nameof(breakpoint));
17 | }
18 |
19 | public Breakpoint Breakpoint
20 | {
21 | get;
22 | }
23 |
24 | public ushort Address
25 | {
26 | get { return Breakpoint.Offset; }
27 | }
28 |
29 | public string ConditionString
30 | {
31 | get { return _conditionString; }
32 | set
33 | {
34 | if (_conditionString != value)
35 | {
36 | Breakpoint.Condition = string.IsNullOrEmpty(value)
37 | ? Breakpoint.BreakAlways
38 | : ExpressionParser.CompileExpression(value);
39 |
40 | _conditionString = value;
41 | OnPropertyChanged(nameof(ConditionString));
42 | }
43 | }
44 | }
45 |
46 | protected virtual void OnPropertyChanged(string propertyName = null)
47 | {
48 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/Emux/Gui/RegisterItem.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace Emux.Gui
4 | {
5 | public class RegisterItem : DependencyObject
6 | {
7 | public event DependencyPropertyChangedEventHandler ValueChanged;
8 |
9 | public static readonly DependencyProperty OffsetProperty = DependencyProperty.Register(
10 | "Offset", typeof(ushort), typeof(RegisterItem));
11 |
12 | public ushort Offset
13 | {
14 | get { return (ushort) GetValue(OffsetProperty); }
15 | set { SetValue(OffsetProperty, value); }
16 | }
17 |
18 | public static readonly DependencyProperty DisplayNameProperty = DependencyProperty.Register(
19 | "DisplayName", typeof(string), typeof(RegisterItem), new PropertyMetadata(default(string)));
20 |
21 | public string DisplayName
22 | {
23 | get { return (string) GetValue(DisplayNameProperty); }
24 | set { SetValue(DisplayNameProperty, value); }
25 | }
26 |
27 | public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
28 | "Value", typeof(byte), typeof(RegisterItem), new PropertyMetadata((o, args) => ((RegisterItem)o).OnValueChanged(args)));
29 |
30 | public byte Value
31 | {
32 | get { return (byte) GetValue(ValueProperty); }
33 | set { SetValue(ValueProperty, value); }
34 | }
35 |
36 | private void OnValueChanged(DependencyPropertyChangedEventArgs args)
37 | {
38 | ValueChanged?.Invoke(this, args);
39 | }
40 |
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Emux/Gui/BreakpointDialog.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Emux.MonoGame/Settings.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Runtime.Serialization;
3 | using Emux.GameBoy.Input;
4 | using Microsoft.Xna.Framework.Input;
5 |
6 | namespace Emux.MonoGame
7 | {
8 | [DataContract]
9 | public class Settings
10 | {
11 | [DataMember]
12 | public Dictionary ControllerBindings
13 | {
14 | get;
15 | } = new Dictionary
16 | {
17 | [Buttons.A] = GameBoyPadButton.A,
18 | [Buttons.B] = GameBoyPadButton.B,
19 | [Buttons.DPadUp] = GameBoyPadButton.Up,
20 | [Buttons.DPadDown] = GameBoyPadButton.Down,
21 | [Buttons.DPadLeft] = GameBoyPadButton.Left,
22 | [Buttons.DPadRight] = GameBoyPadButton.Right,
23 | [Buttons.Start] = GameBoyPadButton.Start,
24 | [Buttons.Back] = GameBoyPadButton.Select,
25 | };
26 |
27 | [DataMember]
28 | public Dictionary KeyboardBindings
29 | {
30 | get;
31 | } = new Dictionary
32 | {
33 | [Keys.X] = GameBoyPadButton.A,
34 | [Keys.Z] = GameBoyPadButton.B,
35 | [Keys.Up] = GameBoyPadButton.Up,
36 | [Keys.Down] = GameBoyPadButton.Down,
37 | [Keys.Left] = GameBoyPadButton.Left,
38 | [Keys.Right] = GameBoyPadButton.Right,
39 | [Keys.Enter] = GameBoyPadButton.Start,
40 | [Keys.LeftShift] = GameBoyPadButton.Select,
41 | };
42 | public const int FrameScaler = 2;
43 | public const bool FitVideo = false;
44 | }
45 | }
--------------------------------------------------------------------------------
/Emux.GameBoy/Input/GameBoyPad.cs:
--------------------------------------------------------------------------------
1 | using Emux.GameBoy.Cpu;
2 |
3 | namespace Emux.GameBoy.Input
4 | {
5 | ///
6 | /// Represents the Keypad driver of a GameBoy device.
7 | ///
8 | public class GameBoyPad : IGameBoyComponent
9 | {
10 | private readonly GameBoy _device;
11 | private GameBoyPadButton _pressedButtons;
12 | private byte _joyP;
13 |
14 | public GameBoyPad(GameBoy device)
15 | {
16 | _device = device;
17 | }
18 |
19 | ///
20 | /// Gets or sets a value indicating all the buttons that are currently pressed.
21 | ///
22 | public GameBoyPadButton PressedButtons
23 | {
24 | get { return _pressedButtons; }
25 | set
26 | {
27 | if (_pressedButtons < value)
28 | _device.Cpu.Registers.IF |= InterruptFlags.Joypad;
29 | _pressedButtons = value;
30 | }
31 | }
32 |
33 | public byte JoyP
34 | {
35 | get
36 | {
37 | if ((_joyP & 0x10) == 0x10)
38 | return (byte)(0xD0 | (~((byte)PressedButtons >> 4) & 0xF));
39 | if ((_joyP & 0x20) == 0x20)
40 | return (byte)(0xE0 | (~(byte)PressedButtons & 0xF));
41 | return 0xFE;
42 | }
43 | set { _joyP = value; }
44 | }
45 | public void Initialize()
46 | {
47 | }
48 |
49 | public void Reset()
50 | {
51 | _joyP = 0;
52 | _pressedButtons = GameBoyPadButton.None;
53 | }
54 |
55 | public void Shutdown()
56 | {
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/RomOnlyBankController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cartridge
4 | {
5 | public class RomOnlyBankController : IMemoryBankController
6 | {
7 | private readonly IFullyAccessibleCartridge _cartridge;
8 | private readonly byte[] _ramBank;
9 | private bool _ramEnabled = false;
10 |
11 | public RomOnlyBankController(IFullyAccessibleCartridge cartridge)
12 | {
13 | if (cartridge == null)
14 | throw new ArgumentNullException(nameof(cartridge));
15 | _cartridge = cartridge;
16 |
17 | if (cartridge.CartridgeType.HasRam())
18 | _ramBank = new byte[0x2000];
19 | }
20 |
21 | public void Initialize()
22 | {
23 | Reset();
24 | }
25 |
26 | public void Reset()
27 | {
28 | _ramEnabled = false;
29 | }
30 |
31 | public void Shutdown()
32 | {
33 | }
34 |
35 | public byte ReadByte(ushort address)
36 | {
37 | if (address < 0x8000)
38 | return _cartridge.ReadFromAbsoluteAddress(address);
39 | if (_ramEnabled && address >= 0xA000 && address <= 0xBFFF)
40 | return _ramBank[address - 0xA000];
41 | return 0;
42 | }
43 |
44 | public void ReadBytes(ushort address, byte[] buffer, int bufferOffset, int length)
45 | {
46 | if (address < 0x8000)
47 | _cartridge.ReadFromAbsoluteAddress(address, buffer, bufferOffset, length);
48 | if (_ramEnabled && address >= 0xA000 && address <= 0xBFFF)
49 | Buffer.BlockCopy(_ramBank, address - 0xA000, buffer, bufferOffset, length);
50 | }
51 |
52 | public void WriteByte(ushort address, byte value)
53 | {
54 | // TODO: ram enable
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Emux/Gui/KeypadWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using Emux.GameBoy.Input;
6 |
7 | namespace Emux.Gui
8 | {
9 | ///
10 | /// Interaction logic for KeypadDialog.xaml
11 | ///
12 | public partial class KeypadWindow : Window
13 | {
14 | private readonly IDictionary _keypadMapping = new Dictionary();
15 | private GameBoy.GameBoy _device;
16 |
17 | public KeypadWindow()
18 | {
19 | InitializeComponent();
20 |
21 | _keypadMapping[UpCheckBox] = GameBoyPadButton.Up;
22 | _keypadMapping[DownCheckBox] = GameBoyPadButton.Down;
23 | _keypadMapping[LeftCheckBox] = GameBoyPadButton.Left;
24 | _keypadMapping[RightCheckBox] = GameBoyPadButton.Right;
25 | _keypadMapping[ACheckBox] = GameBoyPadButton.A;
26 | _keypadMapping[BCheckBox] = GameBoyPadButton.B;
27 | _keypadMapping[StartCheckBox] = GameBoyPadButton.Start;
28 | _keypadMapping[SelectCheckBox] = GameBoyPadButton.Select;
29 | }
30 |
31 | public GameBoy.GameBoy Device
32 | {
33 | get { return _device; }
34 | set { _device = value; }
35 | }
36 |
37 | private void ButtonCheckBoxOnChecked(object sender, RoutedEventArgs e)
38 | {
39 | _device.KeyPad.PressedButtons |= _keypadMapping[(CheckBox) sender];
40 | }
41 |
42 | private void ButtonCheckBoxOnUnchecked(object sender, RoutedEventArgs e)
43 | {
44 | _device.KeyPad.PressedButtons &= ~_keypadMapping[(CheckBox) sender];
45 | }
46 |
47 | private void KeypadWindowOnClosing(object sender, CancelEventArgs e)
48 | {
49 | e.Cancel = Device != null;
50 | Hide();
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Graphics/LcdStatusFlags.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Graphics
4 | {
5 | ///
6 | /// Provides members for representing all valid LCD status flags provided by the STAT register.
7 | ///
8 | [Flags]
9 | public enum LcdStatusFlags : byte
10 | {
11 | ///
12 | /// Specifies the GPU is finishing up a horizontal blank and moving to the next scan line.
13 | ///
14 | HBlankMode = 0b00,
15 |
16 | ///
17 | /// Specifies the GPU is in VBlank mode.
18 | ///
19 | VBlankMode = 0b01,
20 |
21 | ///
22 | /// Specifies the GPU is scanning the Object Attribute Memory (OAM).
23 | ///
24 | ScanLineOamMode = 0b10,
25 |
26 | ///
27 | /// Specifies the GPU is scanning the Video Memory.
28 | ///
29 | ScanLineVRamMode = 0b11,
30 |
31 | ///
32 | /// Provides a bitmask for getting the current GPU mode.
33 | ///
34 | ModeMask = 0b11,
35 |
36 | ///
37 | /// When this bit is set, the LY register equals the LYC register.
38 | ///
39 | Coincidence = (1 << 2),
40 |
41 | ///
42 | /// When this bit is set, HBlank interrupts are enabled.
43 | ///
44 | HBlankModeInterrupt = (1 << 3),
45 |
46 | ///
47 | /// When this bit is set, VBlank interrupts are enabled.
48 | ///
49 | VBlankModeInterrupt = (1 << 4),
50 |
51 | ///
52 | /// When this bit is set, OAM scan interrupts are enabled.
53 | ///
54 | OamBlankModeInterrupt = (1 << 5),
55 |
56 | ///
57 | /// When this bit is set, VRAM scan interrupts are enabled.
58 | ///
59 | CoincidenceInterrupt = (1 << 6),
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Audio/VolumeEnvelope.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Emux.GameBoy.Cpu;
3 |
4 | namespace Emux.GameBoy.Audio
5 | {
6 | public class VolumeEnvelope
7 | {
8 | private readonly ISoundChannel _channel;
9 | private double _timer;
10 |
11 | public VolumeEnvelope(ISoundChannel channel)
12 | {
13 | _channel = channel ?? throw new ArgumentNullException(nameof(channel));
14 | }
15 |
16 | public int Volume
17 | {
18 | get;
19 | private set;
20 | }
21 |
22 | public int InitialVolume
23 | {
24 | get { return _channel.NR2 >> 4; }
25 | }
26 |
27 | public bool EnvelopeIncrease
28 | {
29 | get { return (_channel.NR2 & (1 << 3)) != 0; }
30 | }
31 |
32 | public int EnvelopeSweepCount
33 | {
34 | get { return _channel.NR2 & 7; }
35 | set { _channel.NR2 = (byte) ((_channel.NR2 & ~7) | value & 7); }
36 | }
37 |
38 | public void Reset()
39 | {
40 | Volume = InitialVolume;
41 | _timer = 0;
42 | }
43 |
44 | public void Update(int cycles)
45 | {
46 | if (EnvelopeSweepCount > 0)
47 | {
48 | double timeDelta = (cycles / GameBoyCpu.OfficialClockFrequency) / _channel.Spu.Device.SpeedFactor;
49 | _timer += timeDelta;
50 |
51 | double stepInterval = EnvelopeSweepCount / 64.0;
52 | while (_timer >= stepInterval)
53 | {
54 | _timer -= stepInterval;
55 | if (EnvelopeIncrease)
56 | Volume++;
57 | else
58 | Volume--;
59 |
60 | if (Volume < 0)
61 | Volume = 0;
62 | if (Volume > 15)
63 | Volume = 15;
64 | }
65 |
66 | }
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/Emux/Gui/KeypadWindow.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/StreamedExternalMemory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Emux.GameBoy.Cartridge
5 | {
6 | public class StreamedExternalMemory : IExternalMemory
7 | {
8 | public StreamedExternalMemory(Stream baseStream)
9 | {
10 | if (baseStream == null)
11 | throw new ArgumentNullException(nameof(baseStream));
12 | if (!baseStream.CanRead || !baseStream.CanWrite || !baseStream.CanSeek)
13 | throw new ArgumentException("Stream must be readable, writeable and seekable.");
14 | BaseStream = baseStream;
15 | }
16 |
17 | public Stream BaseStream
18 | {
19 | get;
20 | }
21 |
22 | public bool IsActive
23 | {
24 | get;
25 | private set;
26 | }
27 |
28 | public void Activate()
29 | {
30 | IsActive = true;
31 | }
32 |
33 | public void Deactivate()
34 | {
35 | BaseStream.Flush();
36 | IsActive = false;
37 | }
38 |
39 | public void SetBufferSize(int length)
40 | {
41 | BaseStream.SetLength(length);
42 | }
43 |
44 | public byte ReadByte(int address)
45 | {
46 | if (IsActive)
47 | {
48 | BaseStream.Position = address;
49 | return (byte) BaseStream.ReadByte();
50 | }
51 | return 0;
52 | }
53 |
54 | public void ReadBytes(int address, byte[] buffer, int offset, int length)
55 | {
56 | BaseStream.Position = address;
57 | BaseStream.Read(buffer, offset, length);
58 | }
59 |
60 | public void WriteByte(int address, byte value)
61 | {
62 | if (IsActive)
63 | {
64 | BaseStream.Position = address;
65 | BaseStream.WriteByte(value);
66 | }
67 | }
68 |
69 | public void Dispose()
70 | {
71 | BaseStream.Flush();
72 | BaseStream.Close();
73 | BaseStream.Dispose();
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/Emux/Gui/TitledOverlay.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Navigation;
15 | using System.Windows.Shapes;
16 |
17 | namespace Emux.Gui
18 | {
19 | ///
20 | /// Interaction logic for DisabledOverlay.xaml
21 | ///
22 | public partial class TitledOverlay : UserControl
23 | {
24 | private readonly ManualResetEvent _cancel = new ManualResetEvent(false);
25 |
26 | public TitledOverlay()
27 | {
28 | InitializeComponent();
29 | }
30 |
31 | public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
32 | "Title", typeof(string), typeof(TitledOverlay), new PropertyMetadata(default(string)));
33 |
34 | public string Title
35 | {
36 | get { return (string) GetValue(TitleProperty); }
37 | set { SetValue(TitleProperty, value); }
38 | }
39 |
40 | public static readonly DependencyProperty SubtitleProperty = DependencyProperty.Register(
41 | "Subtitle", typeof(string), typeof(TitledOverlay), new PropertyMetadata(default(string)));
42 |
43 | public string Subtitle
44 | {
45 | get { return (string) GetValue(SubtitleProperty); }
46 | set { SetValue(SubtitleProperty, value); }
47 | }
48 |
49 | public void EnableOverlay(int delay)
50 | {
51 | new Thread(() =>
52 | {
53 | _cancel.Reset();
54 | if (!_cancel.WaitOne(delay))
55 | Dispatcher.Invoke(() => Visibility = Visibility.Visible);
56 | }).Start();
57 | }
58 |
59 | public void DisableOverlay()
60 | {
61 | _cancel.Set();
62 | Visibility = Visibility.Hidden;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Graphics/LcdControlFlags.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Graphics
4 | {
5 | ///
6 | /// Provides members for representing the valid LCD control flags used by the LCDC register.
7 | ///
8 | [Flags]
9 | public enum LcdControlFlags : byte
10 | {
11 | ///
12 | /// When this bit is set, the GPU will render the background upon each scan line.
13 | ///
14 | EnableBackground = 1 << 0,
15 |
16 | ///
17 | /// When this bit is set, the GPU will render sprites upon each scan line.
18 | ///
19 | EnableSprites = 1 << 1,
20 |
21 | ///
22 | /// When this bit is set, the GPU will interpret sprites as 8x16 images instead of 8x8.
23 | ///
24 | Sprite8By16Mode = 1 << 2,
25 |
26 | ///
27 | /// When this bit is set, the GPU will use 0x9C00 as the start address for reading tile indices of the background.
28 | /// When this bit is reset, the starting address is 0x9800.
29 | ///
30 | BgTileMapSelect = 1 << 3,
31 |
32 | ///
33 | /// When this bit is set, the GPU will use 0x8000 as the start address for reading tile data of the background. Tile indices are unsigned numbers ranging from 0 to 256. When this bit is reset, the starting address is 0x8800 and the indices range from -128 to 127.
34 | ///
35 | BgWindowTileDataSelect = 1 << 4,
36 |
37 | ///
38 | /// When this bit is set, the GPU will render the window layer.
39 | ///
40 | EnableWindow = 1 << 5,
41 |
42 | ///
43 | /// When this bit is set, the GPU will use 0x9C00 as the start address for reading tile indices of the window.
44 | /// When this bit is reset, the starting address is 0x9800.
45 | ///
46 | WindowTileMapSelect = 1 << 6,
47 |
48 | ///
49 | /// When this bit is set, the GPU will be enabled, otherwise disabled.
50 | ///
51 | EnableLcd = 1 << 7
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Emux/Gui/TitledOverlay.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
25 |
26 |
27 |
29 |
30 |
37 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Audio/SquareSweepChannel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Emux.GameBoy.Cpu;
3 |
4 | namespace Emux.GameBoy.Audio
5 | {
6 | public class SquareSweepChannel : SquareChannel
7 | {
8 | private readonly GameBoySpu _spu;
9 | private double _frequencySweepClock;
10 |
11 | public SquareSweepChannel(GameBoySpu spu)
12 | : base(spu)
13 | {
14 | _spu = spu;
15 | }
16 |
17 | public override int ChannelNumber
18 | {
19 | get { return 1; }
20 | }
21 |
22 | public override byte NR0
23 | {
24 | get { return base.NR0; }
25 | set
26 | {
27 | base.NR0 = value;
28 | _frequencySweepClock = 0;
29 | }
30 | }
31 |
32 | public float SweepTime
33 | {
34 | get { return ((NR0 >> 4) & 7) / 128f; }
35 | }
36 |
37 | public bool SweepIncrease
38 | {
39 | get { return ((NR0 >> 3) & 1) == 0; }
40 | }
41 |
42 | public int SweepShiftCount
43 | {
44 | get { return NR0 & 0b111; }
45 | set { NR0 = (byte) ((NR0 & ~0b111) | value & 0b111); }
46 | }
47 |
48 | protected void UpdateFrequency(int cycles)
49 | {
50 | if (SweepTime > 0 && _spu.Device.SpeedFactor > 0.5)
51 | {
52 | double timeDelta = (cycles / GameBoyCpu.OfficialClockFrequency) / _spu.Device.SpeedFactor;
53 | _frequencySweepClock += timeDelta;
54 |
55 | while (_frequencySweepClock >= SweepTime)
56 | {
57 | _frequencySweepClock -= SweepTime;
58 |
59 | int delta = (int) (FrequencyRegister / Math.Pow(2, SweepShiftCount));
60 | if (!SweepIncrease)
61 | delta = -delta;
62 | FrequencyRegister = FrequencyRegister + delta;
63 | }
64 | }
65 | }
66 |
67 | public override void ChannelStep(int cycles)
68 | {
69 | UpdateFrequency(cycles);
70 | base.ChannelStep(cycles);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Emux.MonoGame/Content/Calibri.spritefont:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
14 | Calibri
15 |
16 |
20 | 12
21 |
22 |
26 | 0
27 |
28 |
32 | true
33 |
34 |
38 |
39 |
40 |
44 |
45 |
46 |
53 |
54 |
55 |
56 | ~
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/BufferedExternalMemory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 |
6 | namespace Emux.GameBoy.Cartridge
7 | {
8 | public class BufferedExternalMemory : IExternalMemory
9 | {
10 | private byte[] _externalMemory;
11 | private FileStream _fileStream;
12 |
13 | public BufferedExternalMemory(string filePath) : this(File.Open(filePath, FileMode.OpenOrCreate))
14 | { }
15 |
16 | public BufferedExternalMemory(FileStream fileStream)
17 | {
18 | _fileStream = fileStream;
19 |
20 | SetBufferSize((int)_fileStream.Length);
21 | _externalMemory = new byte[_fileStream.Length];
22 |
23 | _fileStream.Read(_externalMemory, 0, _externalMemory.Length);
24 | }
25 |
26 | public bool IsActive
27 | {
28 | get;
29 | private set;
30 | }
31 |
32 | public void Activate()
33 | {
34 | IsActive = true;
35 | }
36 |
37 | public void Deactivate()
38 | {
39 | IsActive = false;
40 | }
41 |
42 | public void SetBufferSize(int length)
43 | {
44 | if (_externalMemory != null)
45 | {
46 | var backup = _externalMemory;
47 | _externalMemory = new byte[length];
48 | Array.Copy(backup, _externalMemory, Math.Min(backup.Length, length));
49 | }
50 | else
51 | {
52 | _externalMemory = new byte[length];
53 | }
54 | }
55 |
56 | public byte ReadByte(int address)
57 | {
58 | if (_externalMemory != null && IsActive)
59 | return _externalMemory[address];
60 |
61 | return 0;
62 | }
63 |
64 | public void ReadBytes(int address, byte[] buffer, int offset, int length)
65 | {
66 | if (_externalMemory != null)
67 | Array.Copy(_externalMemory, address, buffer, 0, length);
68 | }
69 |
70 | public void WriteByte(int address, byte value)
71 | {
72 | if (_externalMemory != null && IsActive)
73 | _externalMemory[address] = value;
74 | }
75 |
76 | public void Dispose()
77 | {
78 | if (_externalMemory == null || _fileStream == null) // Already disposed
79 | return;
80 |
81 | var em = _externalMemory;
82 | var fs = _fileStream;
83 |
84 | _externalMemory = null;
85 | _fileStream = null;
86 |
87 | fs.Position = 0;
88 | fs.Write(em, 0, em.Length);
89 | fs.Flush();
90 | fs.Close();
91 | fs.Dispose();
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Emux/Gui/FlagItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Windows;
4 |
5 | namespace Emux.Gui
6 | {
7 | public class FlagItem : DependencyObject, INotifyPropertyChanged
8 | {
9 | public event EventHandler IsSetChanged;
10 |
11 | public static readonly DependencyProperty BitIndexProperty = DependencyProperty.Register(
12 | "BitIndex", typeof(int), typeof(FlagItem));
13 |
14 | public static readonly DependencyProperty FlagNameProperty = DependencyProperty.Register(
15 | "FlagName", typeof(string), typeof(FlagItem));
16 |
17 | public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
18 | "IsReadOnly", typeof(bool), typeof(FlagItem));
19 |
20 | public static readonly DependencyProperty IsSetProperty = DependencyProperty.Register(
21 | "IsSet", typeof(bool), typeof(FlagItem), new PropertyMetadata(PropertyChangedCallback));
22 |
23 | private static void PropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs e)
24 | {
25 | ((FlagItem)o).OnIsSetChanged();
26 | }
27 |
28 | public int BitIndex
29 | {
30 | get { return (int) GetValue(BitIndexProperty); }
31 | set { SetValue(BitIndexProperty, value); }
32 | }
33 |
34 | public string FlagName
35 | {
36 | get { return (string) GetValue(FlagNameProperty); }
37 | set { SetValue(FlagNameProperty, value); }
38 | }
39 |
40 | public bool IsReadOnly
41 | {
42 | get { return (bool) GetValue(IsReadOnlyProperty); }
43 | set { SetValue(IsReadOnlyProperty, value); }
44 | }
45 |
46 | public bool IsSet
47 | {
48 | get { return (bool) GetValue(IsSetProperty); }
49 | set
50 | {
51 | if (value != IsSet)
52 | {
53 | SetValue(IsSetProperty, value);
54 | OnIsSetChanged();
55 | }
56 | }
57 | }
58 |
59 | protected virtual void OnIsSetChanged()
60 | {
61 | IsSetChanged?.Invoke(this, EventArgs.Empty);
62 | }
63 |
64 | public event PropertyChangedEventHandler PropertyChanged;
65 |
66 | protected virtual void OnPropertyChanged(string propertyName = null)
67 | {
68 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/Emux/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("Emux")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("Emux")]
15 | [assembly: AssemblyCopyright("Copyright © 2017")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("0.1.0.0")]
55 | [assembly: AssemblyFileVersion("0.1.0.0")]
56 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cpu/Z80OpCode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cpu
4 | {
5 | ///
6 | /// Represents a reference to a method that evaluates a Z80 instruction.
7 | ///
8 | /// The device to run the instruction on.
9 | /// The instruction to run.
10 | public delegate void Z80OpCodeOperation(GameBoy device, Z80Instruction z80Instruction);
11 |
12 | ///
13 | /// Represents a reference to a method that evaluates a Z80 instruction that evaluates in a variable amount of clock cycles.
14 | ///
15 | /// The device to run the instruction on.
16 | /// The instruction to run.
17 | /// The amount of clock cycles that it took to evaluate the instruction.
18 | public delegate int Z80OpCodeOperationAlt(GameBoy device, Z80Instruction z80Instruction);
19 |
20 | ///
21 | /// Represents a single operation code of the Z80 instruction set.
22 | ///
23 | public struct Z80OpCode
24 | {
25 | public static readonly Z80OpCodeOperation NotSupported = (_, i) => throw new NotSupportedException("Instruction '" + i.ToString() + "' not supported.");
26 | public static readonly Z80OpCodeOperation InvalidOpcode = (_, i) => throw new NotSupportedException("Invalid OpCode " + i.ToString() + ".");
27 |
28 | public readonly string Disassembly;
29 |
30 | public readonly byte Op1;
31 | public readonly byte Op2;
32 | public readonly int OperandLength;
33 | public readonly int ClockCycles;
34 | public readonly int ClockCyclesAlt;
35 | public readonly Z80OpCodeOperationAlt Operation;
36 |
37 | internal Z80OpCode(string disassembly, byte op1, byte op2, int operandLength, int clockCycles,
38 | Z80OpCodeOperation operation)
39 | : this(disassembly, op1, op2, operandLength, clockCycles, clockCycles, (d, i) => { operation(d, i); return clockCycles; })
40 | {
41 | }
42 |
43 | internal Z80OpCode(string disassembly, byte op1, byte op2, int operandLength, int clockCycles, int clockCyclesAlt, Z80OpCodeOperationAlt operation)
44 | {
45 | Disassembly = disassembly;
46 | Op1 = op1;
47 | Op2 = op2;
48 | OperandLength = operandLength;
49 | ClockCycles = clockCycles;
50 | ClockCyclesAlt = clockCyclesAlt;
51 | Operation = operation;
52 | }
53 |
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Emux/Gui/AboutDialog.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
38 |
39 |
40 |
43 |
44 |
45 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/Emux.NAudio/NAudioChannelOutput.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Runtime.CompilerServices;
4 | using Emux.GameBoy.Audio;
5 | using NAudio.Wave;
6 |
7 | namespace Emux.NAudio
8 | {
9 | public class NAudioChannelOutput : BufferedWaveProvider, IAudioChannelOutput, INotifyPropertyChanged
10 | {
11 | private static readonly byte[] _floatBuffer = new byte[sizeof(float)];
12 | private readonly byte[] _newSampleData;
13 |
14 | public event PropertyChangedEventHandler PropertyChanged;
15 |
16 | private readonly GameBoyNAudioMixer _mixer;
17 | private bool _enabled;
18 |
19 | public NAudioChannelOutput(GameBoyNAudioMixer mixer, string name)
20 | : base(mixer.WaveFormat)
21 | {
22 | if (mixer == null)
23 | throw new ArgumentNullException(nameof(mixer));
24 | _mixer = mixer;
25 | Name = name;
26 | Enabled = true;
27 |
28 | _newSampleData = new byte[BufferLength];
29 | }
30 |
31 | public bool Enabled
32 | {
33 | get { return _enabled; }
34 | set
35 | {
36 | if (_enabled != value)
37 | {
38 | _enabled = value;
39 | OnPropertyChanged(nameof(Enabled));
40 | }
41 | }
42 | }
43 |
44 | public string Name
45 | {
46 | get;
47 | }
48 |
49 | public int SampleRate
50 | {
51 | get { return WaveFormat.SampleRate; }
52 | }
53 |
54 | public void BufferSoundSamples(Span sampleData, int offset, int length)
55 | {
56 | if (Enabled)
57 | {
58 | for (int i = 0, j = 0; j GetBytes(*(int*)&value, bytes);
71 | [System.Security.SecuritySafeCritical]
72 | public unsafe static void GetBytes(int value, byte[] bytes)
73 | {
74 | fixed (byte* b = bytes)
75 | *(int*)b = value;
76 | }
77 |
78 | protected virtual void OnPropertyChanged(string propertyName = null)
79 | {
80 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/Emux/Gui/CheatsWindow.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cpu/Z80Instruction.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cpu
4 | {
5 | ///
6 | /// Represents an instruction in the Z80 instruction set.
7 | ///
8 | public class Z80Instruction
9 | {
10 | public Z80Instruction(ushort offset, Z80OpCode opCode, byte[] operand)
11 | {
12 | Set(offset, opCode, operand);
13 | }
14 |
15 | public void Set(ushort offset, Z80OpCode opCode, byte[] operand)
16 | {
17 | Offset = offset;
18 | OpCode = opCode;
19 | RawOperand = operand;
20 | }
21 |
22 | ///
23 | /// The memory address the instruction is located at.
24 | ///
25 | public ushort Offset
26 | {
27 | get;
28 | private set;
29 | }
30 |
31 | ///
32 | /// The operation code of the instructon.
33 | ///
34 | public Z80OpCode OpCode
35 | {
36 | get;
37 | private set;
38 | }
39 |
40 | ///
41 | /// The bytes that form the operand of the instruction.
42 | ///
43 | public byte[] RawOperand
44 | {
45 | get;
46 | private set;
47 | }
48 |
49 | ///
50 | /// The operand interpreted as a single unsigned 8 bit integer.
51 | ///
52 | public byte Operand8 => RawOperand[0];
53 |
54 | ///
55 | /// The operand interpreted as an unsigned 16 bit integer.
56 | ///
57 | public ushort Operand16 => BitConverter.ToUInt16(RawOperand, 0);
58 |
59 | ///
60 | /// Gets the assembler code representing the instruction.
61 | ///
62 | public string Disassembly
63 | {
64 | get
65 | {
66 | switch (RawOperand.Length)
67 | {
68 | default:
69 | return OpCode.Disassembly;
70 | case 1:
71 | return string.Format(OpCode.Disassembly, Operand8);
72 | case 2:
73 | return string.Format(OpCode.Disassembly, Operand16);
74 | }
75 | }
76 | }
77 |
78 | public override string ToString() => Offset.ToString("X4") + ": " + Disassembly;
79 |
80 | ///
81 | /// Executes the instruction on the given device.
82 | ///
83 | /// The device to execute the instruction on.
84 | /// The clock cycles it took to evaluate the instruction.
85 | public int Execute(GameBoy device) => OpCode.Operation(device, this);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Emux
2 | ====
3 | Emux is a free and open source GameBoy emulator written in C# that allows you to play the original GameBoy games on your computer. Emux is released under the GPLv3 license.
4 |
5 | Features
6 | ========
7 | - Play GameBoy and GameBoy Color games and relive your nostalgic memories!
8 | - Open ROM dumps and execute them like the original GameBoy does.
9 | - Support for cartridges that contain memory bank controllers v1, v2, v3 and v5.
10 | - Support for cartridges that contain external memory (for e.g. save files).
11 | - Disable the frame limit for the boring parts of the game that take forever and you'd rather skip (such as training your Pokémon, I won't judge).
12 | - Listen and record to the good ol' tunes and sound effects that the original GameBoy produced.
13 | - Debugging capabilities
14 | - Break and continue execution whenever you want
15 | - View disassembly of the GameBoy memory.
16 | - Step through the code one instruction at a time.
17 | - Set breakpoints on specific memory addresses.
18 | - View and edit the values of various registers.
19 | - Virtual keypad for easier emulation of keypresses when paused or stepping through the code.
20 | - Oh and probably a lot of funny glitches because the emulation is far from completed at this time.
21 |
22 | Default keybindings
23 | ===================
24 |
25 | | GameBoy Key | Keyboard binidng
26 | |-------------|:-------------|
27 | | Up | Up |
28 | | Down | Down |
29 | | Left | Left |
30 | | Right | Right |
31 | | A | X |
32 | | B | Z |
33 | | Start | Enter |
34 | | Select | Left Shift |
35 |
36 |
37 | Want to contribute?
38 | ===========
39 | There is still much to be done so any help is welcome! Here is a couple of things you can do to contribute to the Emux project:
40 | - Be a follower! Star the project and get the nice fuzzles of contributing without having to do much.
41 | - Be a developer! Fork the project, make your changes and make a pull request.
42 | - Be a tester! Try out games and open issues in the GitHub issue tracker about games that cause glitches or completely fail to work.
43 | - Try to provide as much information as you can. If possible, find out where and when the error occurs. The more details, the easier it is to reproduce and fix.
44 | - IMPORTANT: Please do not upload the ROM itself if the cartridge is licensed. This project is not meant to spread (stolen) copies of games or whatsoever.
45 |
46 | References
47 | ==========
48 | This project is based on the specifications of the following papers (Therefore a big shoutout to the authors!):
49 | - http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf
50 | - http://bgb.bircd.org/pandocs.htm
51 | - http://pastraiser.com/cpu/gameboy/gameboy_opcodes.html
52 |
53 | External libraries
54 | =======================
55 | - [NAudio](https://github.com/naudio/NAudio) for the sound rendering.
--------------------------------------------------------------------------------
/Emux/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Emux.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Emux.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/MemoryBankController2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cartridge
4 | {
5 | public class MemoryBankController2 : IMemoryBankController
6 | {
7 | private readonly IFullyAccessibleCartridge _cartridge;
8 | private readonly byte[] _romBank = new byte[0x4000];
9 | private int _romBankIndex = 0;
10 |
11 | public MemoryBankController2(IFullyAccessibleCartridge cartridge)
12 | {
13 | if (cartridge == null)
14 | throw new ArgumentNullException(nameof(cartridge));
15 | _cartridge = cartridge;
16 | }
17 |
18 | public void Initialize()
19 | {
20 | Reset();
21 | }
22 |
23 | public void Reset()
24 | {
25 | SwitchRomBank(1);
26 | }
27 |
28 | public void Shutdown()
29 | {
30 | }
31 |
32 | public byte ReadByte(ushort address)
33 | {
34 | if (address < 0x4000)
35 | return _cartridge.ReadFromAbsoluteAddress(address);
36 | if (address < 0x8000)
37 | return _romBank[address - 0x4000];
38 | if (_cartridge.ExternalMemory.IsActive && address < 0xA200)
39 | return _cartridge.ExternalMemory.ReadByte(address - 0xA000);
40 | return 0;
41 | }
42 |
43 | public void ReadBytes(ushort address, byte[] buffer, int bufferOffset, int length)
44 | {
45 | if (address < 0x4000)
46 | _cartridge.ReadFromAbsoluteAddress(address, buffer, bufferOffset, length);
47 | else if (address < 0x8000)
48 | Buffer.BlockCopy(_romBank, address - 0x4000, buffer, bufferOffset, length);
49 | else if (_cartridge.ExternalMemory.IsActive && address < 0xA200)
50 | _cartridge.ExternalMemory.ReadBytes(address - 0xA000, buffer, bufferOffset, length);
51 | }
52 |
53 | public void WriteByte(ushort address, byte value)
54 | {
55 | if (address < 0x2000 && (address & 0x0100) == 0)
56 | {
57 | if ((value & 0xF) == 0xA)
58 | _cartridge.ExternalMemory.Activate();
59 | else
60 | _cartridge.ExternalMemory.Deactivate();
61 | }
62 | else if (address < 0x4000 && (address & 0x0100) == 0x0100)
63 | SwitchRomBank(value & 0b1111);
64 | else if (_cartridge.ExternalMemory.IsActive && address >= 0xA000 && address < 0xA200)
65 | _cartridge.ExternalMemory.WriteByte(address - 0xA000, (byte) (value & 0b1111));
66 | }
67 |
68 | private void SwitchRomBank(int index)
69 | {
70 | if (index == 0)
71 | index++;
72 | if (_romBankIndex != index)
73 | {
74 | _romBankIndex = index;
75 | _cartridge.ReadFromAbsoluteAddress(_romBank.Length * index, _romBank, 0, _romBank.Length);
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Emux.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26430.16
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emux.GameBoy", "Emux.GameBoy\Emux.GameBoy.csproj", "{BC59C22A-6BA1-45E8-9E39-B995B748B8DE}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emux", "Emux\Emux.csproj", "{8D23BA7C-0EB8-4C73-BF04-0EDFC0542F17}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emux.GameBoy.Tests", "Emux.GameBoy.Tests\Emux.GameBoy.Tests.csproj", "{365C6CF7-99E3-4861-B63B-E9D352E386AF}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emux.MonoGame", "Emux.MonoGame\Emux.MonoGame.csproj", "{A8CDE19D-11F0-47AB-8391-EFF20F3532FB}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emux.NAudio", "Emux.NAudio\Emux.NAudio.csproj", "{D22215C8-3A0F-49DE-A663-D8FF12349215}"
15 | EndProject
16 | Global
17 | GlobalSection(Performance) = preSolution
18 | HasPerformanceSessions = true
19 | EndGlobalSection
20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
21 | Debug|Any CPU = Debug|Any CPU
22 | Release|Any CPU = Release|Any CPU
23 | EndGlobalSection
24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
25 | {BC59C22A-6BA1-45E8-9E39-B995B748B8DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {BC59C22A-6BA1-45E8-9E39-B995B748B8DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {BC59C22A-6BA1-45E8-9E39-B995B748B8DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {BC59C22A-6BA1-45E8-9E39-B995B748B8DE}.Release|Any CPU.Build.0 = Release|Any CPU
29 | {8D23BA7C-0EB8-4C73-BF04-0EDFC0542F17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30 | {8D23BA7C-0EB8-4C73-BF04-0EDFC0542F17}.Debug|Any CPU.Build.0 = Debug|Any CPU
31 | {8D23BA7C-0EB8-4C73-BF04-0EDFC0542F17}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {8D23BA7C-0EB8-4C73-BF04-0EDFC0542F17}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {365C6CF7-99E3-4861-B63B-E9D352E386AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34 | {365C6CF7-99E3-4861-B63B-E9D352E386AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
35 | {365C6CF7-99E3-4861-B63B-E9D352E386AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
36 | {365C6CF7-99E3-4861-B63B-E9D352E386AF}.Release|Any CPU.Build.0 = Release|Any CPU
37 | {A8CDE19D-11F0-47AB-8391-EFF20F3532FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {A8CDE19D-11F0-47AB-8391-EFF20F3532FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {A8CDE19D-11F0-47AB-8391-EFF20F3532FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
40 | {A8CDE19D-11F0-47AB-8391-EFF20F3532FB}.Release|Any CPU.Build.0 = Release|Any CPU
41 | {D22215C8-3A0F-49DE-A663-D8FF12349215}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42 | {D22215C8-3A0F-49DE-A663-D8FF12349215}.Debug|Any CPU.Build.0 = Debug|Any CPU
43 | {D22215C8-3A0F-49DE-A663-D8FF12349215}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {D22215C8-3A0F-49DE-A663-D8FF12349215}.Release|Any CPU.Build.0 = Release|Any CPU
45 | EndGlobalSection
46 | GlobalSection(SolutionProperties) = preSolution
47 | HideSolutionNode = FALSE
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/Emux.GameBoy.Tests/Emux.GameBoy.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {365C6CF7-99E3-4861-B63B-E9D352E386AF}
8 | Library
9 | Properties
10 | Emux.GameBoy.Tests
11 | Emux.GameBoy.Tests
12 | v4.6.1
13 | 512
14 |
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll
44 |
45 |
46 | ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | {BC59C22A-6BA1-45E8-9E39-B995B748B8DE}
58 | Emux.GameBoy
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Emux/Gui/InstructionItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Specialized;
3 | using System.ComponentModel;
4 | using System.Runtime.CompilerServices;
5 | using System.Windows;
6 | using Emux.GameBoy.Cpu;
7 |
8 | namespace Emux.Gui
9 | {
10 | public class InstructionItem : INotifyPropertyChanged
11 | {
12 | public event PropertyChangedEventHandler PropertyChanged;
13 |
14 | private readonly GameBoy.GameBoy _gameBoy;
15 | private readonly Z80Instruction _instruction;
16 |
17 | public InstructionItem(GameBoy.GameBoy gameBoy, Z80Instruction instruction)
18 | {
19 | if (gameBoy == null)
20 | throw new ArgumentNullException(nameof(gameBoy));
21 | if (instruction == null)
22 | throw new ArgumentNullException(nameof(instruction));
23 | _gameBoy = gameBoy;
24 | _instruction = instruction;
25 | }
26 |
27 | public bool IsBreakpoint
28 | {
29 | get { return Breakpoint != null; }
30 | set
31 | {
32 | if (value)
33 | _gameBoy.SetBreakpoint(_instruction.Offset);
34 | else
35 | _gameBoy.RemoveBreakpoint(_instruction.Offset);
36 | OnPropertyChanged(nameof(IsBreakpoint));
37 | }
38 | }
39 |
40 | public BreakpointInfo Breakpoint
41 | {
42 | get
43 | {
44 | var bp = _gameBoy.GetBreakpointAtAddress(_instruction.Offset);
45 | if (bp == null)
46 | return null;
47 | App.Current.DeviceManager.Breakpoints.TryGetValue(_instruction.Offset, out var breakpointInfo);
48 | if (breakpointInfo == null || breakpointInfo.Breakpoint != bp)
49 | {
50 | breakpointInfo = new BreakpointInfo(bp);
51 | breakpointInfo.PropertyChanged += (sender, args) => OnPropertyChanged(nameof(IsBreakpoint));
52 | }
53 |
54 | return breakpointInfo;
55 | }
56 | }
57 |
58 | public bool IsCurrentInstruction
59 | {
60 | get { return _gameBoy.Cpu.Registers.PC == Offset; }
61 | }
62 |
63 | public bool IsReturn
64 | {
65 | get { return _instruction.OpCode.Disassembly.StartsWith("ret"); }
66 | }
67 |
68 | public bool IsCall
69 | {
70 | get { return _instruction.OpCode.Disassembly.StartsWith("call"); }
71 | }
72 |
73 | public bool IsJump
74 | {
75 | get { return _instruction.OpCode.Disassembly.StartsWith("j"); }
76 | }
77 |
78 | public int Offset
79 | {
80 | get { return _instruction.Offset; }
81 | }
82 |
83 | public string Disassembly
84 | {
85 | get { return _instruction.Disassembly; }
86 | }
87 |
88 | public int Cycles
89 | {
90 | get { return _instruction.OpCode.ClockCycles; }
91 | }
92 |
93 | public string Comment
94 | {
95 | get;
96 | set;
97 | }
98 |
99 | protected virtual void OnPropertyChanged(string propertyName = null)
100 | {
101 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/MemoryBankController5.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cartridge
4 | {
5 | public class MemoryBankController5 : IMemoryBankController
6 | {
7 | private readonly IFullyAccessibleCartridge _cartridge;
8 | private readonly byte[] _romBank = new byte[0x4000];
9 | private int _romBankIndex;
10 | private int _ramBankIndex;
11 |
12 | public MemoryBankController5(IFullyAccessibleCartridge cartridge)
13 | {
14 | if (cartridge == null)
15 | throw new ArgumentNullException(nameof(cartridge));
16 | _cartridge = cartridge;
17 | }
18 |
19 | public void Initialize()
20 | {
21 | Reset();
22 | }
23 |
24 | public void Reset()
25 | {
26 | SwitchRomBank(1);
27 | }
28 |
29 | public void Shutdown()
30 | {
31 | }
32 |
33 | public byte ReadByte(ushort address)
34 | {
35 | if (address < 0x4000)
36 | return _cartridge.ReadFromAbsoluteAddress(address);
37 | if (address < 0x8000)
38 | return _romBank[address - 0x4000];
39 | if (_cartridge.ExternalMemory.IsActive && address >= 0xA000 && address <= 0xBFFF)
40 | return _cartridge.ExternalMemory.ReadByte(address - 0xA000 + GetRamOffset());
41 | return 0;
42 | }
43 |
44 | public void ReadBytes(ushort address, byte[] buffer, int bufferOffset, int length)
45 | {
46 | if (address < 0x4000)
47 | _cartridge.ReadFromAbsoluteAddress(address, buffer, bufferOffset, length);
48 | else if (address < 0x8000)
49 | Buffer.BlockCopy(_romBank, address - 0x4000, buffer, bufferOffset, length);
50 | else if (_cartridge.ExternalMemory.IsActive && address >= 0xA000 && address <= 0xBFFF)
51 | _cartridge.ExternalMemory.ReadBytes(address - 0xA000 + GetRamOffset(), buffer, bufferOffset, length);
52 | }
53 |
54 | public void WriteByte(ushort address, byte value)
55 | {
56 | if (address < 0x2000)
57 | {
58 | if ((value & 0xA) == 0xA)
59 | _cartridge.ExternalMemory.Activate();
60 | else
61 | _cartridge.ExternalMemory.Deactivate();
62 | }
63 | else if (address < 0x3000)
64 | {
65 | SwitchRomBank((_romBankIndex & 0x100) | value);
66 | }
67 | else if (address < 0x4000)
68 | {
69 | SwitchRomBank((_romBankIndex & 0xFF) | ((value & 1) << 8));
70 | }
71 | else if (address < 0x6000)
72 | {
73 | _ramBankIndex = value & 0xF;
74 | }
75 | else if (_cartridge.ExternalMemory.IsActive && address >= 0xA000 && address - 0xA000 < _cartridge.ExternalRamSize)
76 | {
77 | _cartridge.ExternalMemory.WriteByte(address - 0xA000 + GetRamOffset(), value);
78 | }
79 | }
80 |
81 | private void SwitchRomBank(int index)
82 | {
83 | if (_romBankIndex != index)
84 | {
85 | _romBankIndex = index;
86 | _cartridge.ReadFromAbsoluteAddress(_romBank.Length * _romBankIndex, _romBank, 0, _romBank.Length);
87 | }
88 | }
89 |
90 | private int GetRamOffset()
91 | {
92 | return _ramBankIndex * 0x2000;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Emux/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | https://github.com/Washi1337/Emux
7 |
8 |
9 | NearestNeighbor
10 |
11 |
12 | Uniform
13 |
14 |
15 | False
16 |
17 |
18 | #FFE0F8D0
19 |
20 |
21 | #FF88C070
22 |
23 |
24 | #FF346856
25 |
26 |
27 | #FF081820
28 |
29 |
30 | <?xml version="1.0" encoding="utf-16"?>
31 | <Key>S</Key>
32 |
33 |
34 | <?xml version="1.0" encoding="utf-16"?>
35 | <Key>LeftShift</Key>
36 |
37 |
38 | <?xml version="1.0" encoding="utf-16"?>
39 | <Key>Up</Key>
40 |
41 |
42 | <?xml version="1.0" encoding="utf-16"?>
43 | <Key>Down</Key>
44 |
45 |
46 | <?xml version="1.0" encoding="utf-16"?>
47 | <Key>Left</Key>
48 |
49 |
50 | <?xml version="1.0" encoding="utf-16"?>
51 | <Key>Right</Key>
52 |
53 |
54 | <?xml version="1.0" encoding="utf-16"?>
55 | <Key>Return</Key>
56 |
57 |
58 | <?xml version="1.0" encoding="utf-16"?>
59 | <Key>A</Key>
60 |
61 |
62 | 320
63 |
64 |
65 | 288
66 |
67 |
68 |
--------------------------------------------------------------------------------
/Emux/Gui/AudioMixerWindow.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Emux/Gui/VideoWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Timers;
6 | using System.Windows;
7 | using System.Windows.Input;
8 | using System.Windows.Media;
9 | using System.Windows.Media.Imaging;
10 | using System.Windows.Threading;
11 | using Emux.GameBoy.Graphics;
12 | using Emux.GameBoy.Input;
13 |
14 | namespace Emux.Gui
15 | {
16 | ///
17 | /// Interaction logic for VideoWindow.xaml
18 | ///
19 | public partial class VideoWindow : IVideoOutput
20 | {
21 | private readonly WriteableBitmap _bitmap = new WriteableBitmap(GameBoyGpu.FrameWidth, GameBoyGpu.FrameHeight, 96, 96, PixelFormats.Rgb24, null);
22 | private readonly DispatcherTimer _frameRateTimer;
23 |
24 | private GameBoy.GameBoy _device;
25 |
26 | public VideoWindow()
27 | {
28 | InitializeComponent();
29 | _frameRateTimer = new DispatcherTimer();
30 | _frameRateTimer.Tick += FrameRateTimerOnTick;
31 | _frameRateTimer.Interval = new TimeSpan(0, 0, 1);
32 | _frameRateTimer.Start();
33 |
34 | VideoImage.Source = _bitmap;
35 | }
36 |
37 | private bool GetBindedButton(Key key, out GameBoyPadButton button)
38 | {
39 | foreach (var name in Enum.GetValues(typeof(GameBoyPadButton)).Cast().Where(x => x != GameBoyPadButton.None))
40 | {
41 | var bindedKey = (Key) Properties.Settings.Default["KeyBinding" + name];
42 | if (bindedKey == key)
43 | {
44 | button = name;
45 | return true;
46 | }
47 | }
48 | button = GameBoyPadButton.A;
49 | return false;
50 | }
51 |
52 | private void FrameRateTimerOnTick(object sender, EventArgs eventArgs)
53 | {
54 | if (Device != null)
55 | {
56 | lock (this)
57 | {
58 | Dispatcher.Invoke(() => Title = string.Format("Video Output ({0:0.00}x)",
59 | _device.SpeedFactor));
60 | }
61 | }
62 | }
63 |
64 | public GameBoy.GameBoy Device
65 | {
66 | get { return _device; }
67 | set
68 | {
69 | _device = value;
70 | }
71 | }
72 |
73 | public void RenderFrame(byte[] pixelData)
74 | {
75 | // Should really await this but theres no async context here. No idea how it would throw anyway
76 | Dispatcher.InvokeAsync(() =>
77 | {
78 | _bitmap.WritePixels(new Int32Rect(0, 0, 160, 144), pixelData, _bitmap.BackBufferStride, 0);
79 | });
80 | }
81 |
82 | private void VideoWindowOnKeyDown(object sender, KeyEventArgs e)
83 | {
84 | if (e.Key == Key.Space)
85 | Device.EnableFrameLimit = false;
86 | else if (GetBindedButton(e.Key, out var button))
87 | Device.KeyPad.PressedButtons |= button;
88 | }
89 |
90 |
91 | private void VideoWindowOnKeyUp(object sender, KeyEventArgs e)
92 | {
93 | if (e.Key == Key.Space)
94 | Device.EnableFrameLimit = true;
95 | else if (GetBindedButton(e.Key, out var button))
96 | Device.KeyPad.PressedButtons &= ~button;
97 | }
98 |
99 | private void VideoWindowOnClosing(object sender, CancelEventArgs e)
100 | {
101 | lock (this)
102 | {
103 | _frameRateTimer.Stop();
104 | e.Cancel = Device != null;
105 | Hide();
106 | }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Emux/Gui/AudioMixerWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.IO;
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using Emux.NAudio;
6 | using Microsoft.Win32;
7 |
8 | namespace Emux.Gui
9 | {
10 | ///
11 | /// Interaction logic for AudioMixerWindow.xaml
12 | ///
13 | public partial class AudioMixerWindow : Window
14 | {
15 | public static readonly DependencyProperty MixerProperty = DependencyProperty.Register(
16 | "Mixer", typeof(GameBoyNAudioMixer), typeof(AudioMixerWindow), new PropertyMetadata(default(GameBoyNAudioMixer)));
17 |
18 | public static readonly RoutedUICommand StartRecordingCommand = new RoutedUICommand(
19 | "Start a new audio recording.",
20 | "Start Recording",
21 | typeof(AudioMixerWindow));
22 |
23 | public static readonly RoutedUICommand StopRecordingCommand = new RoutedUICommand(
24 | "Stop the current audio recording.",
25 | "Stop Recording",
26 | typeof(AudioMixerWindow));
27 |
28 | private Stream _outputStream;
29 | public AudioMixerWindow()
30 | {
31 | InitializeComponent();
32 | }
33 |
34 | public GameBoyNAudioMixer Mixer
35 | {
36 | get { return (GameBoyNAudioMixer) GetValue(MixerProperty); }
37 | set { SetValue(MixerProperty, value); }
38 | }
39 |
40 | private void CommandBindingOnCanAlwaysExecute(object sender, CanExecuteRoutedEventArgs e)
41 | {
42 | e.CanExecute = true;
43 | }
44 |
45 | private void CloseCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
46 | {
47 | Close();
48 | }
49 |
50 | private void AudioMixerWindowOnClosing(object sender, CancelEventArgs e)
51 | {
52 | e.Cancel = Mixer != null;
53 | Hide();
54 | }
55 |
56 | private void SaveAsCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
57 | {
58 | var dialog = new SaveFileDialog();
59 | dialog.Filter = "Wave files (*.wav)|*.wav";
60 | var result = dialog.ShowDialog();
61 | if (result.HasValue && result.Value)
62 | {
63 | FileTextBox.Text = dialog.FileName;
64 | }
65 | }
66 |
67 | private void StartRecordingCommandOnExecuted(object sender, RoutedEventArgs routedEventArgs)
68 | {
69 | string fileName = FileTextBox.Text;
70 | string directory = Path.GetDirectoryName(fileName);
71 | string name = Path.GetFileNameWithoutExtension(fileName);
72 |
73 | if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory))
74 | {
75 | MessageBox.Show("Please specify an output path.");
76 | }
77 | else
78 | {
79 | int x = 0;
80 | while (File.Exists(fileName = Path.Combine(directory, name + x + Path.GetExtension(fileName))))
81 | {
82 | x++;
83 | }
84 |
85 | _outputStream = File.Create(fileName);
86 | Mixer.StartRecording(_outputStream);
87 | }
88 | }
89 |
90 | private void StopRecordingCommandOnExecuted(object sender, RoutedEventArgs routedEventArgs)
91 | {
92 | Mixer.StopRecording();
93 | }
94 |
95 | private void StartRecordingCommandOnCanExecute(object sender, CanExecuteRoutedEventArgs e)
96 | {
97 | e.CanExecute = Mixer != null && !Mixer.IsRecording;
98 | }
99 |
100 | private void StopRecordingCommandOnCanExecute(object sender, CanExecuteRoutedEventArgs e)
101 | {
102 | e.CanExecute = Mixer != null && Mixer.IsRecording;
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Emux/Gui/CheatsWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Globalization;
4 | using System.Windows;
5 | using System.Windows.Input;
6 | using Emux.GameBoy.Cheating;
7 |
8 | namespace Emux.Gui
9 | {
10 | ///
11 | /// Interaction logic for CheatsWindow.xaml
12 | ///
13 | public partial class CheatsWindow : Window
14 | {
15 | public static readonly DependencyProperty GamesharkControllerProperty = DependencyProperty.Register(
16 | "GamesharkController", typeof(GamesharkController), typeof(CheatsWindow), new PropertyMetadata(default(GamesharkController)));
17 |
18 | public CheatsWindow()
19 | {
20 | InitializeComponent();
21 | }
22 |
23 | public GamesharkController GamesharkController
24 | {
25 | get { return (GamesharkController) GetValue(GamesharkControllerProperty); }
26 | set { SetValue(GamesharkControllerProperty, value); }
27 | }
28 |
29 | private void CommandBindingOnCanAlwaysExecute(object sender, CanExecuteRoutedEventArgs e)
30 | {
31 | e.CanExecute = true;
32 | }
33 |
34 | private void CloseCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
35 | {
36 | Close();
37 | }
38 |
39 | private void NewCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
40 | {
41 | bool exit = false;
42 | string text = "00000000";
43 | while (!exit)
44 | {
45 | try
46 | {
47 | var dialog = new InputDialog();
48 | dialog.Title = "Enter gameshark code";
49 | dialog.Text = text;
50 | bool? result = dialog.ShowDialog();
51 | if (!result.HasValue || !result.Value)
52 | {
53 | exit = true;
54 | }
55 | else
56 | {
57 | text = dialog.Text.Replace(" ", "");
58 |
59 | if (text.Length != 8)
60 | throw new ArgumentException("Gameshark codes are 8 characters long.");
61 |
62 | byte[] rawcode = new byte[4];
63 | for (int i = 0; i < 4; i++)
64 | {
65 | byte value;
66 | if (!byte.TryParse(text.Substring(i * 2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out value))
67 | throw new ArgumentException("Gameshark codes can only contain hexadecimal representation of bytes.");
68 |
69 | rawcode[i] = value;
70 | }
71 |
72 | GamesharkController.Codes.Add(new GamesharkCode(rawcode));
73 | exit = true;
74 | }
75 | }
76 | catch (Exception ex)
77 | {
78 | MessageBox.Show("Invalid gameshark code. " + ex.Message, "Emux", MessageBoxButton.OK,
79 | MessageBoxImage.Error);
80 | }
81 | }
82 | }
83 |
84 | private void DeleteCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
85 | {
86 | lock (GamesharkController.Codes)
87 | {
88 | var items = new GamesharkCode[GamesharkCodesView.SelectedItems.Count];
89 | GamesharkCodesView.SelectedItems.CopyTo(items, 0);
90 | foreach (var item in items)
91 | GamesharkController.Codes.Remove(item);
92 | }
93 | }
94 |
95 | private void CheatsWindowOnClosing(object sender, CancelEventArgs e)
96 | {
97 | e.Cancel = GamesharkController != null;
98 | Hide();
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Emux/Gui/FlagsListBox.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using System.ComponentModel;
4 | using System.Runtime.CompilerServices;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 | using System.Windows.Markup;
8 |
9 | namespace Emux.Gui
10 | {
11 | ///
12 | /// Interaction logic for FlagsListBox.xaml
13 | ///
14 | public partial class FlagsListBox : UserControl
15 | {
16 | public sealed class FlagItemCollection : ObservableCollection, IAddChild
17 | {
18 | private readonly FlagsListBox _owner;
19 |
20 | public FlagItemCollection(FlagsListBox owner)
21 | {
22 | _owner = owner ?? throw new ArgumentNullException(nameof(owner));
23 | }
24 |
25 | protected override void InsertItem(int index, FlagItem item)
26 | {
27 | item.IsSetChanged += ItemOnIsSetChanged;
28 | base.InsertItem(index, item);
29 | }
30 |
31 | protected override void SetItem(int index, FlagItem item)
32 | {
33 | this[index].IsSetChanged -= ItemOnIsSetChanged;
34 | item.IsSetChanged += ItemOnIsSetChanged;
35 | base.SetItem(index, item);
36 | }
37 |
38 | protected override void RemoveItem(int index)
39 | {
40 | this[index].IsSetChanged -= ItemOnIsSetChanged;
41 | base.RemoveItem(index);
42 | }
43 |
44 | protected override void ClearItems()
45 | {
46 | foreach (var item in this)
47 | item.IsSetChanged -= ItemOnIsSetChanged;
48 | base.ClearItems();
49 | }
50 |
51 | private void ItemOnIsSetChanged(object sender, EventArgs eventArgs)
52 | {
53 | var item = (FlagItem)sender;
54 | _owner.RawValue = (byte)((_owner.RawValue & ~(1 << item.BitIndex)) | (item.IsSet ? (1 << item.BitIndex) : 0));
55 | }
56 |
57 | public void AddChild(object value)
58 | {
59 | Add((FlagItem)value);
60 | }
61 |
62 | public void AddText(string text)
63 | {
64 | throw new NotImplementedException();
65 | }
66 | }
67 |
68 |
69 | public event EventHandler RawValueChanged;
70 |
71 | public static readonly DependencyProperty RawValueProperty = DependencyProperty.Register(
72 | "RawValue", typeof(byte), typeof(FlagsListBox));
73 |
74 | public static readonly DependencyProperty FlagItemsProperty = DependencyProperty.Register(
75 | "FlagItems", typeof(FlagItemCollection), typeof(FlagsListBox));
76 |
77 |
78 | public FlagsListBox()
79 | {
80 | InitializeComponent();
81 | SetValue(FlagItemsProperty, new FlagItemCollection(this));
82 | }
83 |
84 | public byte RawValue
85 | {
86 | get { return (byte)GetValue(RawValueProperty); }
87 | set
88 | {
89 | if (RawValue != value)
90 | {
91 | SetValue(RawValueProperty, value);
92 | UpdateFlagItems();
93 | OnRawValueChanged();
94 | }
95 | }
96 | }
97 |
98 | [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
99 | [Bindable(true)]
100 | public FlagItemCollection FlagItems
101 | {
102 | get { return (FlagItemCollection)GetValue(FlagItemsProperty); }
103 | }
104 |
105 | protected virtual void OnRawValueChanged()
106 | {
107 | RawValueChanged?.Invoke(this, EventArgs.Empty);
108 | }
109 |
110 | private void UpdateFlagItems()
111 | {
112 | foreach (var flag in FlagItems)
113 | flag.IsSet = (RawValue & (1 << flag.BitIndex)) != 0;
114 | }
115 |
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/MemoryBankController3.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cartridge
4 | {
5 | public class MemoryBankController3 : IMemoryBankController
6 | {
7 | private readonly IFullyAccessibleCartridge _cartridge;
8 | private readonly byte[] _romBank = new byte[0x4000];
9 | private int _romBankIndex = 0;
10 | private int _ramBankOrRtcIndex;
11 | private readonly byte[] _rtc = new byte[5];
12 |
13 | public MemoryBankController3(IFullyAccessibleCartridge cartridge)
14 | {
15 | if (cartridge == null)
16 | throw new ArgumentNullException(nameof(cartridge));
17 | _cartridge = cartridge;
18 | }
19 |
20 | public void Initialize()
21 | {
22 | Reset();
23 | }
24 |
25 | public void Reset()
26 | {
27 | SwitchRomBank(1);
28 | }
29 |
30 | public void Shutdown()
31 | {
32 | }
33 |
34 | public byte ReadByte(ushort address)
35 | {
36 | if (address < 0x4000)
37 | return _cartridge.ReadFromAbsoluteAddress(address);
38 | if (address < 0x8000)
39 | return _romBank[address - 0x4000];
40 | if (_cartridge.ExternalMemory.IsActive && address >= 0xA000 && address < 0xC000)
41 | return ReadRamOrRtc(address);
42 | return 0;
43 | }
44 |
45 | private byte ReadRamOrRtc(ushort address)
46 | {
47 | return _ramBankOrRtcIndex <= 3
48 | ? (_cartridge.CartridgeType.HasRam()
49 | ? _cartridge.ExternalMemory.ReadByte(address - 0xA000 + GetRamOffset())
50 | : (byte) 0)
51 | : (_cartridge.CartridgeType.HasTimer()
52 | ? _rtc[_ramBankOrRtcIndex - 0x8]
53 | : (byte) 0);
54 | }
55 |
56 | public void ReadBytes(ushort address, byte[] buffer, int bufferOffset, int length)
57 | {
58 | if (address < 0x4000)
59 | _cartridge.ReadFromAbsoluteAddress(address, buffer, bufferOffset, length);
60 | else if (address < 0x8000)
61 | Buffer.BlockCopy(_romBank, address - 0x4000, buffer, bufferOffset, length);
62 | if (_cartridge.ExternalMemory.IsActive && address >= 0xA000 && address <= 0xBFFF)
63 | _cartridge.ExternalMemory.ReadBytes(address - 0xA000 + GetRamOffset(), buffer, bufferOffset, length);
64 | }
65 |
66 | public void WriteByte(ushort address, byte value)
67 | {
68 | if (address < 0x2000)
69 | {
70 | if ((value & 0xA) == 0xA)
71 | _cartridge.ExternalMemory.Activate();
72 | else
73 | _cartridge.ExternalMemory.Deactivate();
74 | }
75 | else if (address < 0x4000)
76 | SwitchRomBank(value & 0x7F);
77 | else if (address < 0x6000)
78 | _ramBankOrRtcIndex = value & 3;
79 | else if (address < 0x8000)
80 | {
81 | // TODO: latch clock data
82 | }
83 | else if (_cartridge.ExternalMemory.IsActive && address >= 0xA000 && address - 0xA000 < _cartridge.ExternalRamSize)
84 | _cartridge.ExternalMemory.WriteByte(address - 0xA000 + GetRamOffset(), value);
85 | }
86 |
87 | private void SwitchRomBank(int index)
88 | {
89 | if (_romBankIndex != index)
90 | {
91 | if (index == 0)
92 | index++;
93 | _romBankIndex = index & 0x7F;
94 | UpdateRomBank();
95 | }
96 | }
97 |
98 | private void UpdateRomBank()
99 | {
100 | _cartridge.ReadFromAbsoluteAddress(_romBank.Length * _romBankIndex, _romBank, 0, _romBank.Length);
101 | }
102 |
103 | private int GetRamOffset()
104 | {
105 | return _ramBankOrRtcIndex * 0x2000;
106 | }
107 |
108 | }
109 | }
--------------------------------------------------------------------------------
/Emux.GameBoy/Cpu/RegisterBank.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable InconsistentNaming
2 |
3 | namespace Emux.GameBoy.Cpu
4 | {
5 | ///
6 | /// Represents the register bank of the GameBoy CPU.
7 | ///
8 | public class RegisterBank
9 | {
10 | public byte A;
11 | public byte F;
12 | public byte B;
13 | public byte C;
14 | public byte D;
15 | public byte E;
16 | public byte H;
17 | public byte L;
18 |
19 | public ushort PC;
20 | public ushort SP;
21 |
22 | public InterruptFlags IE;
23 | public InterruptFlags IF;
24 | public bool IME;
25 | public bool IMESet;
26 |
27 | public ushort AF
28 | {
29 | get { return (ushort)((A << 8) | F); }
30 | set
31 | {
32 | A = (byte)((value >> 8) & 0xFF);
33 | F = (byte)(value & 0xF0);
34 | }
35 | }
36 |
37 | public ushort BC
38 | {
39 | get { return (ushort)((B << 8) | C); }
40 | set
41 | {
42 | B = (byte)((value >> 8) & 0xFF);
43 | C = (byte)(value & 0xFF);
44 | }
45 | }
46 |
47 | public ushort DE
48 | {
49 | get { return (ushort)((D << 8) | E); }
50 | set
51 | {
52 | D = (byte)((value >> 8) & 0xFF);
53 | E = (byte)(value & 0xFF);
54 | }
55 | }
56 |
57 | public ushort HL
58 | {
59 | get { return (ushort)((H << 8) | L); }
60 | set
61 | {
62 | H = (byte)((value >> 8) & 0xFF);
63 | L = (byte)(value & 0xFF);
64 | }
65 | }
66 |
67 | ///
68 | /// Gets a value indicating whether the given flag(s) are set or not.
69 | ///
70 | /// The flag(s) to check.
71 | /// True if all flags specified are set, false otherwise.
72 | public bool GetFlags(RegisterFlags flag)
73 | {
74 | return (F & (byte) flag) == (byte) flag;
75 | }
76 |
77 | ///
78 | /// Overwrites the flags (F) register.
79 | ///
80 | /// The new value.
81 | public void OverwriteFlags(RegisterFlags newFlags)
82 | {
83 | F = (byte) newFlags;
84 | }
85 |
86 | ///
87 | /// Sets the provided flags in the flags (F) register.
88 | ///
89 | /// The flags to set.
90 | public void SetFlags(RegisterFlags flags)
91 | {
92 | F |= (byte) flags;
93 | }
94 |
95 | ///
96 | /// Clears the provided flags in the flags (F) register.
97 | ///
98 | /// The flags to clear.
99 | public void ClearFlags(RegisterFlags flags)
100 | {
101 | unchecked
102 | {
103 | F &= (byte) ~(byte) flags;
104 | }
105 | }
106 |
107 | ///
108 | /// Resets the bank to its begin state.
109 | ///
110 | public void Reset()
111 | {
112 | A = B = C = D = E = F = H = L = 0;
113 | IE = IF = InterruptFlags.None;
114 | IME = false;
115 | }
116 |
117 | public override string ToString()
118 | {
119 | return string.Format("AF: {0:X4}\r\n" +
120 | "BC: {1:X4}\r\n" +
121 | "DE: {2:X4}\r\n" +
122 | "HL: {3:X4}\r\n" +
123 | "PC: {4:X4}\r\n" +
124 | "SP: {5:X4}\r\n" +
125 | "IE: {6:X2} IF: {7:X2} IME: {8} \r\n",
126 | AF, BC, DE, HL, PC, SP, (byte) IE, (byte)IF, IME ? 1 : 0);
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/Emux/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | https://github.com/Washi1337/Emux
18 |
19 |
20 |
21 |
22 |
23 |
24 | NearestNeighbor
25 |
26 |
27 | Uniform
28 |
29 |
30 | False
31 |
32 |
33 | #FFE0F8D0
34 |
35 |
36 | #FF88C070
37 |
38 |
39 | #FF346856
40 |
41 |
42 | #FF081820
43 |
44 |
45 |
46 | S
47 |
48 |
49 |
50 |
51 | LeftShift
52 |
53 |
54 |
55 |
56 | Up
57 |
58 |
59 |
60 |
61 | Down
62 |
63 |
64 |
65 |
66 | Left
67 |
68 |
69 |
70 |
71 | Right
72 |
73 |
74 |
75 |
76 | Return
77 |
78 |
79 |
80 |
81 | A
82 |
83 |
84 |
85 | 320
86 |
87 |
88 | 288
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/Emux.MonoGame/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Reflection;
4 | using System.Runtime.Serialization.Json;
5 | using System.Text;
6 | using Emux.GameBoy.Cartridge;
7 | using Emux.NAudio;
8 | using NAudio.Wave;
9 |
10 | namespace Emux.MonoGame
11 | {
12 | public static class Program
13 | {
14 | [STAThread]
15 | private static void Main(string[] args)
16 | {
17 | PrintAbout();
18 | (string romFile, string saveFile) = ParseArguments(args);
19 | var settings = ReadSettings();
20 |
21 | using (var host = new EmuxHost(settings))
22 | using (var mbc = new BufferedExternalMemory(saveFile))
23 | {
24 | var cartridge = new EmulatedCartridge(File.ReadAllBytes(romFile), mbc);
25 | var device = new GameBoy.GameBoy(cartridge, host, true);
26 | host.GameBoy = device;
27 |
28 | device.Gpu.VideoOutput = host;
29 |
30 | var mixer = new GameBoyNAudioMixer();
31 | mixer.Connect(device.Spu);
32 | var player = new DirectSoundOut();
33 | player.Init(mixer);
34 | player.Play();
35 |
36 | host.Run();
37 | }
38 | }
39 |
40 | private static void PrintAbout()
41 | {
42 | Console.WriteLine("Emux.MonoGame: v{0}, Core: v{1}",
43 | typeof(Program).Assembly.GetName().Version,
44 | typeof(GameBoy.GameBoy).Assembly.GetName().Version);
45 | Console.WriteLine("Copyright © Washi 2017-2018");
46 | Console.WriteLine("Repository and issue tracker: https://www.github.com/Washi1337/Emux");
47 | }
48 |
49 | private static Settings ReadSettings()
50 | {
51 | Settings settings = null;
52 | var serializer = new DataContractJsonSerializer(typeof(Settings));
53 |
54 | string settingsFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
55 | "settings.json");
56 | if (File.Exists(settingsFile))
57 | {
58 | try
59 | {
60 | using (var fs = File.OpenRead(settingsFile))
61 | {
62 | settings = (Settings) serializer.ReadObject(fs);
63 | }
64 | }
65 | catch (Exception ex)
66 | {
67 | Console.Error.WriteLine("Error reading settings file.");
68 | Console.Error.WriteLine(ex.Message);
69 | }
70 | }
71 |
72 | if (settings == null)
73 | {
74 | Console.WriteLine("No valid settings file found. Reverting to default settings.");
75 | settings = new Settings();
76 |
77 | using (var fs = File.Create(settingsFile))
78 | using (var writer = JsonReaderWriterFactory.CreateJsonWriter(fs, Encoding.Default, false, true))
79 | {
80 | serializer.WriteObject(writer, settings);
81 | }
82 | }
83 |
84 | return settings;
85 | }
86 |
87 | private static (string romFile, string saveFile) ParseArguments(string[] args)
88 | {
89 | string romFile = null;
90 | string saveFile = null;
91 |
92 | switch (args.Length)
93 | {
94 | default:
95 | Console.WriteLine("Usage: Emux.MonoGame.exe romfile [savefile]");
96 | Environment.Exit(0);
97 | break;
98 | case 1:
99 | romFile = args[0].Replace("\"", "");
100 | saveFile = Path.ChangeExtension(romFile, ".sav");
101 | break;
102 | case 2:
103 | romFile = args[0].Replace("\"", "");
104 | saveFile = args[1].Replace("\"", "");
105 | break;
106 | }
107 |
108 | if (!File.Exists(romFile))
109 | {
110 | Console.Error.WriteLine("ROM could not be found!");
111 | Environment.Exit(1);
112 | }
113 |
114 | return (romFile, saveFile);
115 | }
116 | }
117 | }
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/MemoryBankController1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Emux.GameBoy.Cartridge
4 | {
5 | public class MemoryBankController1 : IMemoryBankController
6 | {
7 | private readonly IFullyAccessibleCartridge _cartridge;
8 | private readonly byte[] _romBank = new byte[0x4000];
9 | private int _romBankIndex;
10 | private int _ramBankIndex;
11 | private bool _romRamMode;
12 |
13 | public MemoryBankController1(IFullyAccessibleCartridge cartridge)
14 | {
15 | _cartridge = cartridge ?? throw new ArgumentNullException(nameof(cartridge));
16 | }
17 |
18 | public void Initialize()
19 | {
20 | Reset();
21 | }
22 |
23 | public void Reset()
24 | {
25 | SwitchRomBank(1);
26 | }
27 |
28 | public void Shutdown()
29 | {
30 | }
31 |
32 | public byte ReadByte(ushort address)
33 | {
34 | if (address < 0x4000)
35 | return _cartridge.ReadFromAbsoluteAddress(address);
36 | if (address < 0x8000)
37 | return _romBank[address - 0x4000];
38 | if (_cartridge.ExternalMemory.IsActive && address >= 0xA000 && address <= 0xBFFF)
39 | return _cartridge.ExternalMemory.ReadByte(address - 0xA000 + GetRamOffset());
40 | return 0;
41 | }
42 |
43 | public void ReadBytes(ushort address, byte[] buffer, int bufferOffset, int length)
44 | {
45 | if (address < 0x4000)
46 | _cartridge.ReadFromAbsoluteAddress(address, buffer, bufferOffset, length);
47 | else if (address < 0x8000)
48 | Buffer.BlockCopy(_romBank, address - 0x4000, buffer, bufferOffset, length);
49 | if (_cartridge.ExternalMemory.IsActive && address >= 0xA000 && address <= 0xBFFF)
50 | _cartridge.ExternalMemory.ReadBytes(address - 0xA000 + GetRamOffset(), buffer, bufferOffset, length);
51 | }
52 |
53 | public void WriteByte(ushort address, byte value)
54 | {
55 | if (address < 0x2000)
56 | {
57 | if ((value & 0xA) == 0xA)
58 | _cartridge.ExternalMemory.Activate();
59 | else
60 | _cartridge.ExternalMemory.Deactivate();
61 | }
62 | else if (address < 0x4000)
63 | SwitchRomBank(value & 0x1F);
64 | else if (address < 0x6000)
65 | SwitchRamBank(value & 0x3);
66 | else if (address < 0x8000)
67 | SwitchRomRamMode(value);
68 | else if (_cartridge.ExternalMemory.IsActive && address >= 0xA000 && address - 0xA000 < _cartridge.ExternalRamSize)
69 | _cartridge.ExternalMemory.WriteByte(address - 0xA000 + GetRamOffset(), value);
70 | }
71 |
72 | private void SwitchRomRamMode(byte value)
73 | {
74 | bool romRamMode = value == 1;
75 | if (_romRamMode != romRamMode)
76 | {
77 | _romRamMode = romRamMode;
78 | UpdateRomBank();
79 | }
80 | }
81 |
82 | private void SwitchRamBank(int index)
83 | {
84 | if (_ramBankIndex != index)
85 | {
86 | _ramBankIndex = index;
87 | UpdateRomBank();
88 | }
89 | }
90 |
91 | private void SwitchRomBank(int index)
92 | {
93 | if (_romBankIndex != index)
94 | {
95 | if (index == 0 || index == 0x20 || index == 0x40 || index == 0x60)
96 | index++;
97 | _romBankIndex = index;
98 | UpdateRomBank();
99 | }
100 | }
101 |
102 | private void UpdateRomBank()
103 | {
104 | int index = _romBankIndex;
105 | if (_romRamMode)
106 | index |= _ramBankIndex << 5;
107 | _cartridge.ReadFromAbsoluteAddress(_romBank.Length * index, _romBank, 0, _romBank.Length);
108 | }
109 |
110 | private int GetRamOffset()
111 | {
112 | return _ramBankIndex * 0x2000;
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Emux.NAudio/GameBoyNAudioMixer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.ComponentModel;
5 | using System.IO;
6 | using System.Runtime.CompilerServices;
7 | using Emux.GameBoy.Audio;
8 | using NAudio.Wave;
9 |
10 | namespace Emux.NAudio
11 | {
12 | public class GameBoyNAudioMixer : IWaveProvider, INotifyPropertyChanged
13 | {
14 | public event PropertyChangedEventHandler PropertyChanged;
15 |
16 | private readonly MixingWaveProvider32 _mixer = new MixingWaveProvider32();
17 | private bool _enabled = true;
18 | private WaveFileWriter _writer;
19 | private bool _isRecording;
20 |
21 | public GameBoyNAudioMixer()
22 | {
23 | Channels = new List
24 | {
25 | new NAudioChannelOutput(this, "Square + Sweep"),
26 | new NAudioChannelOutput(this, "Square"),
27 | new NAudioChannelOutput(this, "Wave"),
28 | new NAudioChannelOutput(this, "Noise"),
29 | }.AsReadOnly();
30 |
31 | foreach (var channel in Channels)
32 | _mixer.AddInputStream(channel);
33 | }
34 |
35 | public WaveFormat WaveFormat
36 | {
37 | get { return _mixer.WaveFormat; }
38 | }
39 |
40 | public IList Channels
41 | {
42 | get;
43 | }
44 |
45 | public bool IsRecording
46 | {
47 | get { return _isRecording; }
48 | private set
49 | {
50 | if (_isRecording != value)
51 | {
52 | _isRecording = value;
53 | OnPropertyChanged(nameof(IsRecording));
54 | }
55 | }
56 | }
57 |
58 | public bool Enabled
59 | {
60 | get { return _enabled; }
61 | set
62 | {
63 | if (_enabled != value)
64 | {
65 | _enabled = value;
66 | OnPropertyChanged(nameof(Enabled));
67 | }
68 | }
69 | }
70 |
71 | public int Read(byte[] buffer, int offset, int count)
72 | {
73 | _mixer.Read(buffer, offset, count);
74 | if (!Enabled)
75 | Array.Clear(buffer, offset, count);
76 |
77 | lock (this)
78 | {
79 | _writer?.Write(buffer, offset, count);
80 | }
81 | return count;
82 | }
83 |
84 | public void Connect(GameBoySpu spu)
85 | {
86 | for (var i = 0; i < spu.Channels.Count; i++)
87 | {
88 | var channel = spu.Channels[i];
89 | channel.ChannelOutput = Channels[i];
90 | channel.ChannelVolume = 0.05f;
91 | }
92 | }
93 |
94 | public void StartRecording(Stream outputStream)
95 | {
96 | if (outputStream == null)
97 | throw new ArgumentNullException(nameof(outputStream));
98 |
99 | lock (this)
100 | {
101 | if (IsRecording)
102 | throw new InvalidOperationException("Cannot start a recording when a recording is already happening.");
103 | _writer = new WaveFileWriter(outputStream, WaveFormat);
104 | IsRecording = true;
105 | }
106 | }
107 |
108 | public void StopRecording()
109 | {
110 | lock (this)
111 | {
112 | if (!IsRecording)
113 | throw new InvalidOperationException("Cannot stop a recording when a recording is not happening.");
114 | try
115 | {
116 | _writer.Flush();
117 | }
118 | finally
119 | {
120 | _writer.Dispose();
121 | _writer = null;
122 | IsRecording = false;
123 | }
124 | }
125 | }
126 |
127 | protected virtual void OnPropertyChanged(string propertyName = null)
128 | {
129 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/Emux/DeviceManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using Emux.GameBoy.Cartridge;
5 | using Emux.GameBoy.Cheating;
6 | using Emux.GameBoy.Graphics;
7 | using Emux.NAudio;
8 | using Emux.Gui;
9 | using NAudio.Wave;
10 |
11 | namespace Emux
12 | {
13 | public class DeviceManager
14 | {
15 | public event EventHandler DeviceLoaded;
16 | public event EventHandler DeviceUnloaded;
17 | public event EventHandler DeviceChanged;
18 |
19 | private GameBoy.GameBoy _currentDevice;
20 | private IExternalMemory _currentExternalMemory;
21 |
22 | public DeviceManager()
23 | {
24 | AudioMixer = new GameBoyNAudioMixer();
25 | var player = new DirectSoundOut();
26 | player.Init(AudioMixer);
27 | player.Play();
28 |
29 | GamesharkController = new GamesharkController();
30 | Properties.Settings.Default.PropertyChanged += SettingsOnPropertyChanged;
31 |
32 | Breakpoints = new Dictionary();
33 | }
34 |
35 | public GameBoyNAudioMixer AudioMixer
36 | {
37 | get;
38 | }
39 |
40 | public GameBoy.GameBoy CurrentDevice
41 | {
42 | get { return _currentDevice; }
43 | private set
44 | {
45 | if (_currentDevice != value)
46 | {
47 | _currentDevice = value;
48 | if (value != null)
49 | {
50 | AudioMixer.Connect(value.Spu);
51 | GamesharkController.Device = value;
52 | }
53 | OnDeviceChanged();
54 | }
55 | }
56 | }
57 |
58 | public GamesharkController GamesharkController
59 | {
60 | get;
61 | }
62 |
63 | public IDictionary Breakpoints
64 | {
65 | get;
66 | }
67 |
68 | public void UnloadDevice()
69 | {
70 | var device = _currentDevice;
71 | if (device != null)
72 | {
73 | device.Terminate();
74 | _currentExternalMemory.Dispose();
75 | _currentDevice = null;
76 | OnDeviceUnloaded(new DeviceEventArgs(device));
77 | }
78 | }
79 |
80 | public void LoadDevice(string romFilePath, string ramFilePath)
81 | {
82 | UnloadDevice();
83 |
84 | _currentExternalMemory = new BufferedExternalMemory(ramFilePath);
85 | var cartridge = new EmulatedCartridge(File.ReadAllBytes(romFilePath), _currentExternalMemory);
86 | _currentExternalMemory.SetBufferSize(cartridge.ExternalRamSize);
87 | CurrentDevice = new GameBoy.GameBoy(cartridge, new WinMmTimer(60), !Properties.Settings.Default.ForceOriginalGameBoy);
88 | ApplyColorPalettes();
89 | OnDeviceLoaded(new DeviceEventArgs(CurrentDevice));
90 | }
91 |
92 | protected virtual void OnDeviceChanged()
93 | {
94 | DeviceChanged?.Invoke(this, EventArgs.Empty);
95 | }
96 |
97 | protected virtual void OnDeviceLoaded(DeviceEventArgs e)
98 | {
99 | DeviceLoaded?.Invoke(this, e);
100 | }
101 |
102 | protected virtual void OnDeviceUnloaded(DeviceEventArgs e)
103 | {
104 | DeviceUnloaded?.Invoke(this, e);
105 | }
106 |
107 | private static Color ConvertColor(System.Windows.Media.Color color)
108 | {
109 | return new Color(color.R, color.G, color.B);
110 | }
111 |
112 | private void ApplyColorPalettes()
113 | {
114 | if (CurrentDevice != null)
115 | {
116 | CurrentDevice.Gpu.Color0 = ConvertColor(Properties.Settings.Default.GBColor0);
117 | CurrentDevice.Gpu.Color1 = ConvertColor(Properties.Settings.Default.GBColor1);
118 | CurrentDevice.Gpu.Color2 = ConvertColor(Properties.Settings.Default.GBColor2);
119 | CurrentDevice.Gpu.Color3 = ConvertColor(Properties.Settings.Default.GBColor3);
120 | }
121 | }
122 |
123 | private void SettingsOnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
124 | {
125 | if (e.PropertyName.Contains("GBColor"))
126 | ApplyColorPalettes();
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/Emux/Gui/OptionsDialog.xaml.cs:
--------------------------------------------------------------------------------
1 | using Emux.GameBoy.Graphics;
2 | using Emux.Properties;
3 | using System.ComponentModel;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Data;
7 | using System.Windows.Input;
8 |
9 | namespace Emux.Gui
10 | {
11 | ///
12 | /// Interaction logic for OptionsDialog.xaml
13 | ///
14 | public partial class OptionsDialog : Window
15 | {
16 | private Binding _buttonContentBinding;
17 |
18 | public OptionsDialog()
19 | {
20 | InitializeComponent();
21 |
22 | UpdateScaleDD();
23 | VideoScaleDD.SelectionChanged += VideoScale_SelectionChanged;
24 | }
25 |
26 | private void UpdateScaleDD()
27 | {
28 | var configWidth = Settings.Default.VideoWidth;
29 | var configHeight = Settings.Default.VideoHeight;
30 | var widthScale = configWidth / (float)GameBoyGpu.FrameWidth;
31 | var heightScale = configHeight / (float)GameBoyGpu.FrameHeight;
32 | if (widthScale == heightScale && widthScale % 1 == 0 && heightScale % 1 == 0) // Exact scale
33 | VideoScaleDD.SelectedIndex = (int)widthScale;
34 | else
35 | VideoScaleDD.SelectedIndex = 0;
36 | }
37 |
38 | private void VideoScale_SelectionChanged(object sender, SelectionChangedEventArgs e)
39 | {
40 | var newScale = VideoScaleDD.SelectedIndex;
41 | if (newScale == 0)
42 | return;
43 |
44 | Settings.Default.VideoWidth = GameBoyGpu.FrameWidth * newScale;
45 | Settings.Default.VideoHeight = GameBoyGpu.FrameHeight * newScale;
46 | }
47 |
48 | public bool CanClose
49 | {
50 | get;
51 | set;
52 | }
53 |
54 | private void OptionsDialogOnClosing(object sender, CancelEventArgs e)
55 | {
56 | if (!CanClose)
57 | {
58 | e.Cancel = true;
59 | Hide();
60 | }
61 | }
62 |
63 | private void PaletteButtonOnClick(object sender, RoutedEventArgs e)
64 | {
65 | string settingName = "GBColor" + ((Button) sender).Tag;
66 | var color = (System.Windows.Media.Color)Properties.Settings.Default[settingName];
67 |
68 | using (var dialog = new System.Windows.Forms.ColorDialog())
69 | {
70 | dialog.FullOpen = true;
71 | dialog.CustomColors = new[]
72 | {
73 | 0xD0F8E0, 0x70C088, 0x566834, 0x201808
74 | };
75 | dialog.Color = System.Drawing.Color.FromArgb(color.R, color.G, color.B);
76 | if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
77 | {
78 | Properties.Settings.Default[settingName] =
79 | System.Windows.Media.Color.FromRgb(dialog.Color.R, dialog.Color.G, dialog.Color.B);
80 | }
81 | }
82 |
83 | }
84 |
85 | private void ResetToDefaultsButtonOnClick(object sender, RoutedEventArgs e)
86 | {
87 | if (MessageBox.Show(
88 | "Are you sure you want to reset the settings to their default values?",
89 | "Emux",
90 | MessageBoxButton.YesNo,
91 | MessageBoxImage.Warning) == MessageBoxResult.Yes)
92 | {
93 | Properties.Settings.Default.Reset();
94 | }
95 | }
96 |
97 | private void KeyBindingButtonOnClick(object sender, RoutedEventArgs e)
98 | {
99 | var button = (Button) sender;
100 | _buttonContentBinding = button.GetBindingExpression(ContentProperty).ParentBinding;
101 | button.Content = "[ Press a key ]";
102 | }
103 |
104 | private void KeyBindingButtonOnKeyDown(object sender, KeyEventArgs e)
105 | {
106 | if (_buttonContentBinding != null)
107 | {
108 | var button = (Button) sender;
109 | Properties.Settings.Default["KeyBinding" + button.Tag] = e.Key;
110 | button.SetBinding(ContentProperty, _buttonContentBinding);
111 | _buttonContentBinding = null;
112 | }
113 | }
114 |
115 | private void KeyBindingButtonOnLostFocus(object sender, RoutedEventArgs e)
116 | {
117 | if (_buttonContentBinding != null)
118 | {
119 | var button = (Button) sender;
120 | button.SetBinding(ContentProperty, _buttonContentBinding);
121 | _buttonContentBinding = null;
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/ICartridge.cs:
--------------------------------------------------------------------------------
1 | namespace Emux.GameBoy.Cartridge
2 | {
3 | ///
4 | /// Provides members for accessing an inserted GameBoy cartridge.
5 | ///
6 | public interface ICartridge : IGameBoyComponent
7 | {
8 | ///
9 | /// Gets the compressed Nintendo Logo bitmap used by the BIOS.
10 | ///
11 | byte[] NintendoLogo { get; }
12 |
13 | ///
14 | /// Gets the title of the game.
15 | ///
16 | string GameTitle { get; }
17 |
18 | ///
19 | /// Gets the publisher code of the cartridge. This property is used by newer cartridges only.
20 | ///
21 | byte[] NewPublisherCode { get; }
22 |
23 | ///
24 | /// Gets a value indicating whether the cartridge is designed specifically for GameBoy Color devices.
25 | ///
26 | GameBoyColorFlag GameBoyColorFlag { get; }
27 |
28 | ///
29 | /// Gets a value indicating whether Super GameBoy features are enabled or not by this cartridge.
30 | ///
31 | bool SuperGameBoyMode { get; }
32 |
33 | ///
34 | /// Gets the type of the cartridge, including the present memory bank controller (MBC), if any.
35 | ///
36 | CartridgeType CartridgeType { get; }
37 |
38 | ///
39 | /// Gets the size in bytes of all ROM data present in the cartridge.
40 | ///
41 | int RomSize { get; }
42 |
43 | ///
44 | /// Gets the size in bytes of external RAM present in the cartridge, if any.
45 | ///
46 | int ExternalRamSize { get; }
47 |
48 | ///
49 | /// Gets a value indicating the cartridge was produced for the Japanese market or not.
50 | ///
51 | bool IsJapanese { get; }
52 |
53 | ///
54 | /// Gets the publisher code of the cartridge. This property is used by older cartridges only.
55 | ///
56 | byte OldPublisherCode { get; }
57 |
58 | ///
59 | /// Gets the checksum of the cartridge header.
60 | ///
61 | byte HeaderChecksum { get; }
62 |
63 | ///
64 | /// Gets the global checksum of the cartrige.
65 | ///
66 | byte[] GlobalChecksum { get; }
67 |
68 | ///
69 | /// Reads a single byte from the cartrige at a given address.
70 | ///
71 | /// The address to read from.
72 | /// The byte at the given location.
73 | byte ReadByte(ushort address);
74 |
75 | ///
76 | /// Reads a block of bytes from the cartridge starting at a given address.
77 | ///
78 | /// The start address.
79 | /// The buffer to write the bytes to.
80 | /// The destinatino offset of the buffer to write to.
81 | /// The amount of bytes to read.
82 | void ReadBytes(ushort address, byte[] buffer, int bufferOffset, int length);
83 |
84 | ///
85 | /// Writes a byte to the cartridge.
86 | ///
87 | /// The address to write to.
88 | /// The value to write.
89 | void WriteByte(ushort address, byte value);
90 | }
91 |
92 | ///
93 | /// Provides members for accessing a fully accessible cartridge.
94 | ///
95 | public interface IFullyAccessibleCartridge : ICartridge
96 | {
97 | IExternalMemory ExternalMemory
98 | {
99 | get;
100 | }
101 |
102 | ///
103 | /// Reads a single byte from the raw data of the cartridge.
104 | ///
105 | /// The absolute address of the raw data.
106 | /// The byte at the given absolute address.
107 | byte ReadFromAbsoluteAddress(int address);
108 |
109 | ///
110 | /// Reads a block of bytes from the raw data of the cartridge.
111 | ///
112 | /// The start address.
113 | /// The buffer to write the bytes to.
114 | /// The destinatino offset of the buffer to write to.
115 | /// The amount of bytes to read.
116 | void ReadFromAbsoluteAddress(int address, byte[] buffer, int bufferOffset, int length);
117 | }
118 | }
--------------------------------------------------------------------------------
/Emux.GameBoy/Timer/GameBoyTimer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Emux.GameBoy.Cpu;
3 |
4 | namespace Emux.GameBoy.Timer
5 | {
6 | public class GameBoyTimer : IGameBoyComponent
7 | {
8 | private readonly GameBoy _device;
9 | public const int DivFrequency = 16384;
10 | public const int DivCycleInterval = (int) (GameBoyCpu.OfficialClockFrequency / DivFrequency);
11 |
12 | private int _divClock;
13 | private int _timerClock;
14 | private TimerControlFlags _tac;
15 | private byte _tima;
16 | private byte _div;
17 |
18 | public byte Div
19 | {
20 | get { return _div; }
21 | set
22 | {
23 | _div = value;
24 | _timerClock = 0;
25 | _divClock = 0;
26 | }
27 | }
28 |
29 | public byte Tima
30 | {
31 | get { return _tima; }
32 | set { _tima = value; }
33 | }
34 |
35 | public byte Tma
36 | {
37 | get;
38 | set;
39 | }
40 |
41 | public TimerControlFlags Tac
42 | {
43 | get { return _tac; }
44 | set
45 | {
46 | if ((value & TimerControlFlags.EnableTimer) == 0)
47 | {
48 | //_timerClock = 0;
49 | }
50 |
51 | _tac = value;
52 | }
53 | }
54 |
55 | public GameBoyTimer(GameBoy device)
56 | {
57 | if (device == null)
58 | throw new ArgumentNullException(nameof(device));
59 | _device = device;
60 | }
61 |
62 | public void Initialize()
63 | {
64 | }
65 |
66 | public void Reset()
67 | {
68 | Div = 0x1E;
69 | Tima = 0;
70 | Tma = 0;
71 | Tac = 0;
72 | }
73 |
74 | public void Shutdown()
75 | {
76 | }
77 |
78 | public int GetTimaFrequency()
79 | {
80 | switch (Tac & TimerControlFlags.ClockMask)
81 | {
82 | case TimerControlFlags.Clock4096Hz:
83 | return 4096;
84 | case TimerControlFlags.Clock16384Hz:
85 | return 16384;
86 | case TimerControlFlags.Clock65536Hz:
87 | return 65536;
88 | case TimerControlFlags.Clock262144Hz:
89 | return 262144;
90 | }
91 | return 0;
92 | }
93 |
94 | private int GetTimaClockCycles()
95 | {
96 | return (int)(GameBoyCpu.OfficialClockFrequency / GetTimaFrequency());
97 | }
98 |
99 |
100 | public void Step(int cycles)
101 | {
102 | _divClock += cycles;
103 | while (_divClock > DivCycleInterval)
104 | {
105 | _divClock -= DivCycleInterval;
106 | _div = (byte) ((Div + 1) % 0xFF);
107 | }
108 |
109 | if ((_tac & TimerControlFlags.EnableTimer) == TimerControlFlags.EnableTimer)
110 | {
111 | _timerClock += cycles;
112 | int timaCycles = GetTimaClockCycles();
113 | while (_timerClock > timaCycles)
114 | {
115 | _timerClock -= timaCycles;
116 |
117 | int result = _tima + 1;
118 | _tima = (byte) (result & 0xFF);
119 | if (result > 0xFF)
120 | {
121 | _tima = Tma;
122 | _device.Cpu.Registers.IF |= InterruptFlags.Timer;
123 | }
124 | }
125 | }
126 | }
127 |
128 | public byte ReadRegister(ushort address)
129 | {
130 | switch (address)
131 | {
132 | case 0x04:
133 | return Div;
134 | case 0x05:
135 | return Tima;
136 | case 0x06:
137 | return Tma;
138 | case 0x07:
139 | return (byte) Tac;
140 | }
141 | throw new ArgumentOutOfRangeException(nameof(address));
142 | }
143 |
144 | public void WriteRegister(ushort address, byte value)
145 | {
146 | switch (address)
147 | {
148 | case 0x04:
149 | Div = 0;
150 | break;
151 | case 0x05:
152 | Tima = value;
153 | break;
154 | case 0x06:
155 | Tma = value;
156 | break;
157 | case 0x07:
158 | Tac = (TimerControlFlags) (value & 0b111);
159 | break;
160 | }
161 | }
162 |
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Audio/WaveSoundChannel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using Emux.GameBoy.Cpu;
4 |
5 | namespace Emux.GameBoy.Audio
6 | {
7 | public class WaveSoundChannel : ISoundChannel
8 | {
9 | private readonly byte[] _waveRam = new byte[0x10];
10 | private byte _nr0;
11 | private byte _nr1;
12 | private byte _nr2;
13 | private byte _nr3;
14 | private byte _nr4;
15 | private int _coordinate;
16 | private bool _top;
17 |
18 | public WaveSoundChannel(GameBoySpu spu)
19 | {
20 | Spu = spu ?? throw new ArgumentNullException(nameof(spu));
21 | ChannelVolume = 1;
22 | }
23 |
24 | public GameBoySpu Spu
25 | {
26 | get;
27 | }
28 |
29 | public virtual int ChannelNumber
30 | {
31 | get { return 3; }
32 | }
33 |
34 | public byte NR0
35 | {
36 | get { return _nr0; }
37 | set { _nr0 = value; }
38 | }
39 |
40 | public byte NR1
41 | {
42 | get { return _nr1; }
43 | set { _nr1 = value; }
44 | }
45 |
46 | public byte NR2
47 | {
48 | get { return _nr2; }
49 | set { _nr2 = value; }
50 | }
51 |
52 | public byte NR3
53 | {
54 | get { return _nr3; }
55 | set { _nr3 = value; }
56 | }
57 |
58 | public byte NR4
59 | {
60 | get { return _nr4; }
61 | set { _nr4 = value; }
62 | }
63 |
64 | public float ChannelVolume
65 | {
66 | get;
67 | set;
68 | }
69 |
70 | public IAudioChannelOutput ChannelOutput
71 | {
72 | get;
73 | set;
74 | }
75 |
76 | public bool Active
77 | {
78 | get;
79 | set;
80 | }
81 |
82 | public bool SoundEnabled
83 | {
84 | get { return (_nr0 & (1 << 7)) != 0; }
85 | }
86 |
87 | public float SoundLength
88 | {
89 | get { return (256f - _nr1) / 256f; }
90 | }
91 |
92 | public float OutputLevel
93 | {
94 | get
95 | {
96 | switch (_nr2 >> 5 & 0b11)
97 | {
98 | default:
99 | return 0f;
100 | case 1:
101 | return 1f;
102 | case 2:
103 | return 0.5f;
104 | case 3:
105 | return 0.25f;
106 | }
107 | }
108 | }
109 |
110 | public int Frequency
111 | {
112 | get
113 | {
114 | var value = _nr3 | (_nr4 & 0b111) << 8;
115 | return (int) (GameBoyCpu.OfficialClockFrequency / (64 * (2048 - value)));
116 | }
117 | }
118 |
119 | public byte ReadWavRam(ushort address)
120 | {
121 | return _waveRam[address];
122 | }
123 |
124 | public void WriteWavRam(ushort address, byte value)
125 | {
126 | _waveRam[address] = value;
127 | }
128 |
129 | public void ChannelStep(int cycles)
130 | {
131 | double cpuSpeedFactor = Spu.Device.SpeedFactor;
132 | if (!Active)
133 | return;
134 |
135 | int sampleRate = ChannelOutput.SampleRate;
136 | double timeDelta = (cycles / GameBoyCpu.OfficialClockFrequency) / cpuSpeedFactor;
137 | int sampleCount = (int) (timeDelta * sampleRate) * 2;
138 | using (var malloc = MemoryPool.Shared.Rent(sampleCount))
139 | {
140 | var buffer = malloc.Memory.Span;
141 |
142 | double interval = 1.0 / Frequency;
143 | int intervalSampleCount = (int)(interval * sampleRate);
144 |
145 | if (intervalSampleCount > 0)
146 | {
147 | for (int i = 0; i < sampleCount; i += 2)
148 | {
149 | _coordinate++;
150 | if (_coordinate >= intervalSampleCount)
151 | {
152 | _top = !_top;
153 | _coordinate = 0;
154 | }
155 |
156 | int waveRamCoordinate = (int)(_coordinate / (double)intervalSampleCount * _waveRam.Length);
157 |
158 | int waveDataSample = _top
159 | ? (_waveRam[waveRamCoordinate] & 0xF)
160 | : ((_waveRam[waveRamCoordinate] >> 4) & 0xF);
161 |
162 | float sample = ChannelVolume * OutputLevel * (waveDataSample - 7) / 15f;
163 |
164 | Spu.WriteToSoundBuffer(ChannelNumber, buffer, i, sample);
165 | }
166 | }
167 |
168 | ChannelOutput.BufferSoundSamples(buffer, 0, sampleCount);
169 | }
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/EmulatedCartridge.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 |
4 | namespace Emux.GameBoy.Cartridge
5 | {
6 | ///
7 | /// Represents an emulated cartridge initialized from a byte array.
8 | ///
9 | public class EmulatedCartridge : IFullyAccessibleCartridge
10 | {
11 | private readonly byte[] _romContents;
12 |
13 | public EmulatedCartridge(byte[] romContents, IExternalMemory externalMemory)
14 | {
15 | if (romContents == null)
16 | throw new ArgumentNullException(nameof(romContents));
17 | if (externalMemory == null)
18 | throw new ArgumentNullException(nameof(externalMemory));
19 | _romContents = romContents;
20 | ExternalMemory = externalMemory;
21 |
22 | if (CartridgeType.IsRom())
23 | BankController = new RomOnlyBankController(this);
24 | else if (CartridgeType.IsMbc1())
25 | BankController = new MemoryBankController1(this);
26 | else if (CartridgeType.IsMbc2())
27 | BankController = new MemoryBankController2(this);
28 | else if (CartridgeType.IsMbc3())
29 | BankController = new MemoryBankController3(this);
30 | else if (CartridgeType.IsMbc5())
31 | BankController = new MemoryBankController5(this);
32 | else
33 | throw new NotSupportedException("Unsupported cartridge type " + CartridgeType + ".");
34 | }
35 |
36 | public byte[] NintendoLogo
37 | {
38 | get
39 | {
40 | byte[] logo = new byte[0x133 - 0x104];
41 | Buffer.BlockCopy(_romContents, 0x104, logo, 0, logo.Length);
42 | return logo;
43 | }
44 | }
45 |
46 | public string GameTitle
47 | {
48 | get
49 | {
50 | byte[] nameBytes = new byte[16];
51 | Buffer.BlockCopy(_romContents, 0x134, nameBytes, 0, nameBytes.Length);
52 | return Encoding.ASCII.GetString(nameBytes);
53 | }
54 | }
55 |
56 | public byte[] NewPublisherCode
57 | {
58 | get { return new[] { _romContents[0x144], _romContents[0x145] }; }
59 | }
60 |
61 | public GameBoyColorFlag GameBoyColorFlag
62 | {
63 | get { return (GameBoyColorFlag) (_romContents[0x143] & 0xC0); }
64 | }
65 |
66 | public bool SuperGameBoyMode
67 | {
68 | get { return _romContents[0x146] == 0x3; }
69 | }
70 |
71 | public CartridgeType CartridgeType
72 | {
73 | get { return (CartridgeType) _romContents[0x147]; }
74 | }
75 |
76 | public int RomSize
77 | {
78 | get { return 0x8000 << _romContents[0x148]; }
79 | }
80 |
81 | public int ExternalRamSize
82 | {
83 | get {
84 | switch (_romContents[0x149])
85 | {
86 | case 1:
87 | return 0x800;
88 | case 2:
89 | return 0x2000;
90 | case 3:
91 | return 0x8000;
92 | }
93 | return 0;
94 | }
95 | }
96 |
97 | public bool IsJapanese
98 | {
99 | get { return _romContents[0x14B] == 0; }
100 | }
101 |
102 | public byte OldPublisherCode
103 | {
104 | get { return _romContents[0x14C]; }
105 | }
106 |
107 | public byte HeaderChecksum
108 | {
109 | get { return _romContents[0x14D]; }
110 | }
111 |
112 | public byte[] GlobalChecksum
113 | {
114 | get { return new[] { _romContents[0x14E], _romContents[0x14F] }; }
115 | }
116 |
117 | public IMemoryBankController BankController
118 | {
119 | get;
120 | }
121 |
122 | public IExternalMemory ExternalMemory
123 | {
124 | get;
125 | }
126 |
127 | public byte ReadByte(ushort address)
128 | {
129 | return BankController.ReadByte(address);
130 | }
131 |
132 | public void ReadBytes(ushort address, byte[] buffer, int bufferOffset, int length)
133 | {
134 | BankController.ReadBytes(address, buffer, bufferOffset, length);
135 | }
136 |
137 | public void WriteByte(ushort address, byte value)
138 | {
139 | BankController.WriteByte(address, value);
140 | }
141 |
142 | public byte ReadFromAbsoluteAddress(int address)
143 | {
144 | return _romContents[address];
145 | }
146 |
147 | public void ReadFromAbsoluteAddress(int address, byte[] buffer, int bufferOffset, int length)
148 | {
149 | Buffer.BlockCopy(_romContents, address, buffer, bufferOffset, length);
150 | }
151 |
152 | public void Initialize()
153 | {
154 | BankController.Initialize();
155 | }
156 |
157 | public void Reset()
158 | {
159 | BankController.Reset();
160 | }
161 |
162 | public void Shutdown()
163 | {
164 | BankController.Shutdown();
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Audio/NoiseChannel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using Emux.GameBoy.Cpu;
4 |
5 | namespace Emux.GameBoy.Audio
6 | {
7 | public class NoiseChannel : ISoundChannel
8 | {
9 | private readonly Random _random = new Random();
10 | private readonly VolumeEnvelope _volumeEnvelope;
11 | private readonly LfsRegister _lfsr;
12 |
13 | private int _clock = 0;
14 | private double _length = 0;
15 | private double _currentValue = 0;
16 |
17 | private byte _nr1;
18 | private byte _nr2;
19 | private byte _nr3;
20 | private byte _nr4;
21 |
22 | public NoiseChannel(GameBoySpu spu)
23 | {
24 | Spu = spu ?? throw new ArgumentNullException(nameof(spu));
25 | ChannelVolume = 1;
26 | _volumeEnvelope = new VolumeEnvelope(this);
27 | _lfsr = new LfsRegister(this);
28 | }
29 |
30 | public GameBoySpu Spu
31 | {
32 | get;
33 | }
34 |
35 | public virtual int ChannelNumber
36 | {
37 | get { return 4; }
38 | }
39 |
40 | public byte NR0
41 | {
42 | get { return 0; }
43 | set { }
44 | }
45 |
46 | public byte NR1
47 | {
48 | get { return _nr1; }
49 | set
50 | {
51 | _nr1 = value;
52 | _length = SoundLength;
53 | }
54 | }
55 |
56 | public byte NR2
57 | {
58 | get { return _nr2; }
59 | set
60 | {
61 | _nr2 = value;
62 | _volumeEnvelope.Reset();
63 | }
64 | }
65 |
66 | public byte NR3
67 | {
68 | get { return _nr3; }
69 | set
70 | {
71 | _nr3 = value;
72 | _clock = 0;
73 | _lfsr.Reset();
74 | }
75 | }
76 |
77 | public byte NR4
78 | {
79 | get { return _nr4; }
80 | set { _nr4 = value; }
81 | }
82 |
83 | public bool Active
84 | {
85 | get;
86 | set;
87 | }
88 |
89 | public float ChannelVolume
90 | {
91 | get;
92 | set;
93 | }
94 |
95 | public double SoundLength
96 | {
97 | get { return (64 - (_nr1 & 63)) / 256.0; }
98 | }
99 |
100 | public int ShiftClockFrequency
101 | {
102 | get { return NR3 >> 4; }
103 | set { NR3 = (byte) ((NR3 & 0b1111) | (value << 4)); }
104 | }
105 |
106 | public int DividingRatio
107 | {
108 | get { return NR3 & 0b111; }
109 | set { NR3 = (byte) ((NR3 & ~0b111) | value & 0b111); }
110 | }
111 |
112 | public float Frequency
113 | {
114 | get
115 | {
116 | double ratio = DividingRatio == 0 ? 0.5 : DividingRatio;
117 | return (float) (GameBoyCpu.OfficialClockFrequency / 8 / ratio / Math.Pow(2, ShiftClockFrequency + 1));
118 | }
119 | }
120 |
121 | public bool UseSoundLength
122 | {
123 | get { return (_nr4 & (1 << 6)) != 0; }
124 | }
125 |
126 | public IAudioChannelOutput ChannelOutput
127 | {
128 | get;
129 | set;
130 | }
131 |
132 | public void ChannelStep(int cycles)
133 | {
134 | double cpuSpeedFactor = Spu.Device.SpeedFactor;
135 | if (!Active)
136 | return;
137 |
138 | // Update volume.
139 | _volumeEnvelope.Update(cycles);
140 | float amplitude = ChannelVolume * _volumeEnvelope.Volume / 15.0f;
141 |
142 | // Get elapsed gameboy time.
143 | double timeDelta = (cycles / GameBoyCpu.OfficialClockFrequency) / cpuSpeedFactor;
144 |
145 | // Allocate buffer.
146 | int sampleRate = ChannelOutput.SampleRate;
147 | int sampleCount = (int) (timeDelta * sampleRate) * 2;
148 | using (var malloc = MemoryPool.Shared.Rent(sampleCount))
149 | {
150 | var buffer = malloc.Memory.Span;
151 |
152 | if (!UseSoundLength || _length >= 0)
153 | {
154 | double period = 1 / Frequency;
155 | int periodSampleCount = (int)(period * sampleRate) * 2;
156 |
157 | for (int i = 0; i < sampleCount; i += 2)
158 | {
159 | float sample = amplitude * (_lfsr.CurrentValue ? 1f : 0f);
160 | Spu.WriteToSoundBuffer(ChannelNumber, buffer, i, sample);
161 |
162 | _clock += 2;
163 | if (_clock >= periodSampleCount)
164 | {
165 | _lfsr.PerformShift();
166 | _clock -= periodSampleCount;
167 | }
168 | }
169 |
170 | if (UseSoundLength)
171 | _length -= timeDelta;
172 | }
173 |
174 | ChannelOutput.BufferSoundSamples(buffer, 0, sampleCount);
175 | }
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/Emux/Expressions/ExpressionLexer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Data;
4 | using System.IO;
5 | using System.Text;
6 | using System.Text.RegularExpressions;
7 |
8 | namespace Emux.Expressions
9 | {
10 | public class ExpressionLexer
11 | {
12 | private static readonly ISet Registers = new HashSet
13 | {
14 | "PC", "SP", "AF", "BC", "DE", "HL", "A", "B", "C", "D", "E", "F", "H", "L",
15 | "pc", "sp", "af", "bc", "de", "hl", "a", "b", "c", "d", "e", "f", "h", "l"
16 | };
17 |
18 | private const string HexCharacters = "0123456789ABCDEF";
19 |
20 | private readonly TextReader _reader;
21 | private Token _bufferedToken;
22 |
23 | public ExpressionLexer(TextReader reader)
24 | {
25 | _reader = reader ?? throw new ArgumentNullException(nameof(reader));
26 | }
27 |
28 | public bool HasNext()
29 | {
30 | SkipWhitespaces();
31 | return _reader.Peek() != -1;
32 | }
33 |
34 | public Token Peek()
35 | {
36 | if (_bufferedToken == null && HasNext())
37 | ReadNextToken();
38 |
39 | return _bufferedToken;
40 | }
41 |
42 | public Token Next()
43 | {
44 | if (_bufferedToken == null)
45 | ReadNextToken();
46 | var token = _bufferedToken;
47 | _bufferedToken = null;
48 | return token;
49 | }
50 |
51 | private void SkipWhitespaces()
52 | {
53 | while (true)
54 | {
55 | int c = _reader.Peek();
56 | if (c == -1 || !char.IsWhiteSpace((char) c))
57 | break;
58 | _reader.Read();
59 | }
60 | }
61 |
62 | private void ReadNextToken()
63 | {
64 | if (!HasNext())
65 | throw new EndOfStreamException();
66 |
67 | char c = (char) _reader.Peek();
68 |
69 | _bufferedToken = char.IsLetterOrDigit(c) ? ReadNextWord() : ReadNextSymbol();
70 | }
71 |
72 | private Token ReadNextWord()
73 | {
74 | string word = ReadWhile(char.IsLetterOrDigit);
75 | Terminal terminal;
76 |
77 | if (Registers.Contains(word))
78 | terminal = Terminal.Register;
79 | else if (Regex.IsMatch(word, @"(0x[\da-zA-Z]+)|([\da-zA-Z]+h)"))
80 | terminal = Terminal.Hexadecimal;
81 | else if (Regex.IsMatch(word, @"\d+"))
82 | terminal = Terminal.Decimal;
83 | else
84 | throw new SyntaxErrorException("Unrecognized word '" + word + "'.");
85 |
86 | return new Token(terminal, word);
87 | }
88 |
89 | private string ReadWhile(Predicate condition)
90 | {
91 | var builder = new StringBuilder();
92 |
93 | while (true)
94 | {
95 | int p = _reader.Peek();
96 | if (p == -1)
97 | break;
98 |
99 | char c = (char) p;
100 | if (!condition(c))
101 | break;
102 |
103 | _reader.Read();
104 | builder.Append(c);
105 | }
106 |
107 | return builder.ToString();
108 | }
109 |
110 |
111 | private Token ReadNextSymbol()
112 | {
113 | char c = (char) _reader.Read();
114 | switch (c)
115 | {
116 | case '(':
117 | return new Token(Terminal.LPar, "(");
118 |
119 | case ')':
120 | return new Token(Terminal.RPar, ")");
121 |
122 | case '!':
123 | return new Token(Terminal.Not, "!");
124 |
125 | case '+':
126 | return new Token(Terminal.Plus, "+");
127 |
128 | case '-':
129 | return new Token(Terminal.Minus, "-");
130 |
131 | case '=':
132 | return new Token(Terminal.Equals, "=");
133 |
134 | case '>':
135 | if (_reader.Peek() != '=')
136 | return new Token(Terminal.GreaterThan, "=");
137 | _reader.Read();
138 | return new Token(Terminal.GreaterThanOrEqual, ">=");
139 |
140 | case '<':
141 | if (_reader.Peek() != '=')
142 | return new Token(Terminal.Equals, "=");
143 | _reader.Read();
144 | return new Token(Terminal.LessThanOrEqual, ">=");
145 |
146 | case '&':
147 | if (_reader.Peek() != '&')
148 | return new Token(Terminal.BitwiseAnd, "&");
149 | _reader.Read();
150 | return new Token(Terminal.BooleanAnd, "&&");
151 |
152 | case '|':
153 | if (_reader.Peek() != '|')
154 | return new Token(Terminal.BitwiseOr, "|");
155 | _reader.Read();
156 | return new Token(Terminal.BooleanOr, "||");
157 |
158 | }
159 |
160 | throw new SyntaxErrorException("Unrecognized character '" + c + "'.");
161 | }
162 | }
163 | }
--------------------------------------------------------------------------------
/Emux/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Memory/DmaController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Emux.GameBoy.Graphics;
3 | using static Emux.GameBoy.Memory.GameBoyMemory;
4 |
5 | namespace Emux.GameBoy.Memory
6 | {
7 | public class DmaController : IGameBoyComponent
8 | {
9 | private const byte EnableMask = 0b10000000; // 0x80
10 | private const byte LengthMask = 0b01111111; // 0x7F
11 |
12 | private readonly GameBoy _device;
13 | private bool _isTransferring;
14 | private int _currentBlockIndex;
15 | private byte _sourceHigh;
16 | private byte _sourceLow;
17 | private byte _destinationHigh;
18 | private byte _destinationLow;
19 | private byte _dmaLengthMode;
20 | private readonly byte[] _hdmaBlockCopy = new byte[OAMDMABlockSize];
21 | private readonly byte[] _oamBlockCopy = new byte[OAMSize];
22 | private readonly byte[] _vramBlockCopy = new byte[((LengthMask & LengthMask) + 1) * 0x10]; // Largest possible size
23 |
24 | public DmaController(GameBoy device)
25 | {
26 | if (device == null)
27 | throw new ArgumentNullException(nameof(device));
28 | _device = device;
29 | }
30 |
31 | public ushort SourceAddress => (ushort)((_sourceHigh << 8) | _sourceLow & 0xF0);
32 |
33 | public ushort DestinationAddress => (ushort)(0x8000 | (((_destinationHigh << 8) | (_destinationLow & 0xF0)) & 0b0001111111110000));
34 |
35 | public bool DMAEnabled => (_dmaLengthMode & EnableMask) > 0;
36 |
37 | public int Length => ((_dmaLengthMode & LengthMask) + 1) * 0x10;
38 |
39 | public void Initialize()
40 | {
41 | _device.Gpu.HBlankStarted += GpuOnHBlankStarted;
42 | }
43 |
44 | public void Reset()
45 | {
46 | _isTransferring = false;
47 | _currentBlockIndex = 0;
48 | _sourceHigh = 0;
49 | _sourceLow = 0;
50 | _destinationHigh = 0;
51 | _destinationLow = 0;
52 | _dmaLengthMode = 0;
53 | }
54 |
55 | public void Shutdown()
56 | {
57 | _device.Gpu.HBlankStarted -= GpuOnHBlankStarted;
58 | }
59 |
60 | public byte ReadRegister(ushort address)
61 | {
62 | switch (address)
63 | {
64 | case 0xFF46:
65 | return 0;
66 | case 0xFF51:
67 | return _sourceHigh;
68 | case 0xFF52:
69 | return _sourceLow;
70 | case 0xFF53:
71 | return _destinationHigh;
72 | case 0xFF54:
73 | return _destinationLow;
74 | case 0xFF55:
75 | return _dmaLengthMode;
76 | }
77 |
78 | throw new ArgumentOutOfRangeException(nameof(address));
79 | }
80 |
81 | public void WriteRegister(ushort address, byte value)
82 | {
83 | switch (address)
84 | {
85 | case 0xFF46:
86 | PerformOamDmaTransfer(value);
87 | break;
88 | case 0xFF51:
89 | _sourceHigh = value;
90 | break;
91 | case 0xFF52:
92 | _sourceLow = value;
93 | break;
94 | case 0xFF53:
95 | _destinationHigh = value;
96 | break;
97 | case 0xFF54:
98 | _destinationLow = value;
99 | break;
100 | case 0xFF55:
101 | if (_isTransferring && (value & EnableMask) == 0)
102 | {
103 | StopVramDmaTransfer();
104 | }
105 | else
106 | {
107 | _dmaLengthMode = value;
108 | StartVramDmaTransfer();
109 | }
110 | break;
111 | default:
112 | throw new ArgumentOutOfRangeException(nameof(address));
113 | }
114 | }
115 |
116 | private void StopVramDmaTransfer()
117 | {
118 | _dmaLengthMode |= EnableMask;
119 | _currentBlockIndex = 0;
120 | _isTransferring = false;
121 | }
122 |
123 | private void StartVramDmaTransfer()
124 | {
125 | if (!DMAEnabled)
126 | {
127 | _device.Memory.ReadBlock(SourceAddress, _vramBlockCopy, 0, Length);
128 | _device.Gpu.WriteVRam((ushort)(DestinationAddress - VRAMStartAddress), _vramBlockCopy, 0, Length);
129 | }
130 | else
131 | {
132 | _currentBlockIndex = 0;
133 | _isTransferring = true;
134 | _dmaLengthMode &= LengthMask;
135 | }
136 | }
137 |
138 | private void PerformOamDmaTransfer(byte dma)
139 | {
140 | _device.Memory.ReadBlock((ushort)(dma * 0x100), _oamBlockCopy, 0, OAMSize);
141 | _device.Gpu.ImportOam(_oamBlockCopy);
142 | }
143 |
144 | private void GpuOnHBlankStarted(object sender, EventArgs eventArgs)
145 | {
146 | if (_isTransferring && _device.Gpu.LY < GameBoyGpu.FrameHeight)
147 | HDmaStep();
148 | }
149 |
150 | private void HDmaStep()
151 | {
152 | var currentOffset = _currentBlockIndex * OAMDMABlockSize;
153 |
154 | _device.Memory.ReadBlock((ushort)(SourceAddress + currentOffset), _hdmaBlockCopy, 0, OAMDMABlockSize);
155 | _device.Gpu.WriteVRam((ushort)(DestinationAddress - VRAMStartAddress + currentOffset), _hdmaBlockCopy, 0, OAMDMABlockSize);
156 |
157 | _currentBlockIndex++;
158 | var next = (_dmaLengthMode & LengthMask) - 1;
159 | _dmaLengthMode = (byte) ((_dmaLengthMode & EnableMask) | next);
160 |
161 | if (next <= 0)
162 | {
163 | _dmaLengthMode = 0xFF;
164 | _isTransferring = false;
165 | }
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/Emux.GameBoy/Cartridge/CartridgeType.cs:
--------------------------------------------------------------------------------
1 | namespace Emux.GameBoy.Cartridge
2 | {
3 | ///
4 | /// Provides a collection of valid (but not necessarily supported) cartridge types.
5 | ///
6 | public enum CartridgeType : byte
7 | {
8 | RomOnly = 0x00,
9 | Mbc1 = 0x01,
10 | Mbc1Ram = 0x02,
11 | Mbc1RamBattery = 0x03,
12 | Mbc2 = 0x05,
13 | Mbc2Battery = 0x06,
14 | RomRam = 0x8,
15 | RomRamBattery = 0x9,
16 | Mmm01 = 0xB,
17 | Mmm01Ram = 0xC,
18 | Mmm01RamBattery = 0xD,
19 | Mbc3TimerBattery = 0xF,
20 | Mbc3TimerRamBattery = 0x10,
21 | Mbc3 = 0x11,
22 | Mbc3Ram = 0x12,
23 | Mbc3RamBattery = 0x13,
24 | Mbc4 = 0x15,
25 | Mbc4Ram = 0x16,
26 | Mbc4RamBattery = 0x17,
27 | Mbc5 = 0x19,
28 | Mbc5Ram = 0x1A,
29 | Mbc5RamBattery = 0x1B,
30 | Mbc5Rumble = 0x1C,
31 | Mbc5RumbleRam = 0x1D,
32 | Mbc5RumbleRamBattery = 0x1E,
33 | PocketCamera = 0xFC,
34 | BandaiTama5 = 0xFD,
35 | HuC3 = 0xFE,
36 | HuC1RamBattery = 0xFF
37 | }
38 |
39 | public static class CartridgeTypeExtensions
40 | {
41 | public static bool IsRom(this CartridgeType type)
42 | {
43 | switch (type)
44 | {
45 | case CartridgeType.RomOnly:
46 | case CartridgeType.RomRam:
47 | case CartridgeType.RomRamBattery:
48 | return true;
49 | }
50 | return false;
51 | }
52 |
53 | public static bool IsMbc1(this CartridgeType type)
54 | {
55 | switch (type)
56 | {
57 | case CartridgeType.Mbc1:
58 | case CartridgeType.Mbc1Ram:
59 | case CartridgeType.Mbc1RamBattery:
60 | return true;
61 | }
62 | return false;
63 | }
64 |
65 | public static bool IsMbc2(this CartridgeType type)
66 | {
67 | switch (type)
68 | {
69 | case CartridgeType.Mbc2:
70 | case CartridgeType.Mbc2Battery:
71 | return true;
72 | }
73 | return false;
74 | }
75 |
76 | public static bool IsMbc3(this CartridgeType type)
77 | {
78 | switch (type)
79 | {
80 | case CartridgeType.Mbc3:
81 | case CartridgeType.Mbc3Ram:
82 | case CartridgeType.Mbc3RamBattery:
83 | case CartridgeType.Mbc3TimerBattery:
84 | case CartridgeType.Mbc3TimerRamBattery:
85 | return true;
86 | }
87 | return false;
88 | }
89 |
90 | public static bool IsMbc4(this CartridgeType type)
91 | {
92 | switch (type)
93 | {
94 | case CartridgeType.Mbc4:
95 | case CartridgeType.Mbc4Ram:
96 | case CartridgeType.Mbc4RamBattery:
97 | return true;
98 | }
99 | return false;
100 | }
101 |
102 | public static bool IsMbc5(this CartridgeType type)
103 | {
104 | switch (type)
105 | {
106 | case CartridgeType.Mbc5:
107 | case CartridgeType.Mbc5Ram:
108 | case CartridgeType.Mbc5RamBattery:
109 | case CartridgeType.Mbc5Rumble:
110 | case CartridgeType.Mbc5RumbleRam:
111 | case CartridgeType.Mbc5RumbleRamBattery:
112 | return true;
113 | }
114 | return false;
115 | }
116 |
117 | public static bool HasRam(this CartridgeType type)
118 | {
119 | switch (type)
120 | {
121 | case CartridgeType.Mbc1Ram:
122 | case CartridgeType.Mbc3Ram:
123 | case CartridgeType.Mbc3RamBattery:
124 | case CartridgeType.Mbc3TimerRamBattery:
125 | case CartridgeType.Mbc4Ram:
126 | case CartridgeType.Mbc4RamBattery:
127 | case CartridgeType.Mbc5Ram:
128 | case CartridgeType.Mbc5RamBattery:
129 | case CartridgeType.Mbc5RumbleRam:
130 | case CartridgeType.Mbc5RumbleRamBattery:
131 | case CartridgeType.HuC1RamBattery:
132 | case CartridgeType.RomRam:
133 | case CartridgeType.Mmm01Ram:
134 | case CartridgeType.Mmm01RamBattery:
135 | return true;
136 | }
137 | return false;
138 | }
139 |
140 | public static bool HasBattery(this CartridgeType type)
141 | {
142 | switch (type)
143 | {
144 | case CartridgeType.RomRamBattery:
145 | case CartridgeType.Mbc1RamBattery:
146 | case CartridgeType.Mbc2Battery:
147 | case CartridgeType.Mbc3RamBattery:
148 | case CartridgeType.Mbc3TimerBattery:
149 | case CartridgeType.Mbc3TimerRamBattery:
150 | case CartridgeType.Mbc4RamBattery:
151 | case CartridgeType.Mbc5RamBattery:
152 | case CartridgeType.Mmm01RamBattery:
153 | case CartridgeType.HuC1RamBattery:
154 | return true;
155 | }
156 | return false;
157 | }
158 |
159 | public static bool HasTimer(this CartridgeType type)
160 | {
161 | switch (type)
162 | {
163 | case CartridgeType.Mbc3TimerBattery:
164 | case CartridgeType.Mbc3TimerRamBattery:
165 | return true;
166 | }
167 | return false;
168 | }
169 |
170 | public static bool HasRumble(this CartridgeType type)
171 | {
172 | switch (type)
173 | {
174 | case CartridgeType.Mbc5Rumble:
175 | case CartridgeType.Mbc5RumbleRam:
176 | case CartridgeType.Mbc5RumbleRamBattery:
177 | return true;
178 | }
179 | return false;
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------