├── NesSharp.Core
├── RgbColor.cs
├── MirrorMode.cs
├── NesSharp.Core.csproj
├── Sound
│ └── ISoundDriver.cs
├── ByteExtensions.cs
├── Mappers
│ ├── MapperBase.cs
│ ├── Mapper003.cs
│ ├── Mapper000.cs
│ ├── Mapper066.cs
│ ├── Mapper002.cs
│ ├── Mapper001.cs
│ └── Mapper004.cs
├── Bus.cs
├── Cartridge.cs
├── Apu.cs
└── Cpu.cs
├── NesSharp.WinForms
├── Debugging
│ ├── IDebugOutput.cs
│ ├── PaletteControl.cs
│ ├── PaletteControl.resx
│ └── PaletteControl.Designer.cs
├── Program.cs
├── NesSharp.WinForms.csproj
├── Sound
│ ├── QueuedBufferProvider.cs
│ └── NAudioSoundDriver.cs
├── DebugWindow.cs
├── DebugWindow.Designer.cs
├── DebugWindow.resx
├── EmulatorWindow.resx
├── EmulatorWindow.Designer.cs
└── EmulatorWindow.cs
├── README.md
├── NesSharp.Console
├── NesSharp.Console.csproj
└── Program.cs
├── NesSharp.sln.DotSettings
├── LICENSE.md
├── NesSharp.sln
└── .gitignore
/NesSharp.Core/RgbColor.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core;
2 |
3 | public readonly record struct RgbColor(byte R, byte G, byte B);
--------------------------------------------------------------------------------
/NesSharp.Core/MirrorMode.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core;
2 |
3 | public enum MirrorMode
4 | {
5 | Hardware,
6 | Horizontal,
7 | Vertical,
8 | OneScreenLow,
9 | OneScreenHigh
10 | }
--------------------------------------------------------------------------------
/NesSharp.WinForms/Debugging/IDebugOutput.cs:
--------------------------------------------------------------------------------
1 | using NesSharp.Core;
2 |
3 | namespace NesSharp.WinForms.Debugging
4 | {
5 | public interface IDebugOutput
6 | {
7 | void SetPpu(Ppu ppu);
8 | void DebugUpdate();
9 | }
10 | }
--------------------------------------------------------------------------------
/NesSharp.Core/NesSharp.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/NesSharp.Core/Sound/ISoundDriver.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core.Sound;
2 |
3 | public interface ISoundDriver : IDisposable
4 | {
5 | void InitializeAudio(uint sampleRate, uint channels, uint blocks, uint blockSamples);
6 | void SetSoundOutMethod(SoundOut soundOut);
7 | void DestroyAudio();
8 | }
9 |
10 | public delegate float SoundOut(uint channel, float globalTime, float timeStep);
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NES#
2 |
3 | A NES (Nintendo Entertainment System) console emulator written in C#.
4 |
5 |
6 | Based on the YouTube video series and assorted code by OneLoneCoder. Please have a look at:
7 | * the [OneLoneCoder NES Playlist](https://www.youtube.com/playlist?list=PLrOv9FMX8xJHqMvSGB_9G9nZZ_4IgteYf).
8 | * the [OneLoneCoder NES GitHub repository](https://github.com/OneLoneCoder/olcNES)
9 |
--------------------------------------------------------------------------------
/NesSharp.Console/NesSharp.Console.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/NesSharp.Console/Program.cs:
--------------------------------------------------------------------------------
1 | using NesSharp.Core;
2 |
3 | namespace NesSharp.Console;
4 |
5 | internal class Program
6 | {
7 | static async Task Main(string[] args)
8 | {
9 | var bus = new Bus();
10 |
11 | var cartridge = await Cartridge.FromFile("E:\\temp\\NES-ROMS\\nestest.nes");
12 | bus.InsertCartridge(cartridge);
13 |
14 | bus.Reset();
15 |
16 | while (true)
17 | {
18 | bus.Clock();
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/NesSharp.Core/ByteExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core;
2 |
3 | public static class ByteExtensions
4 | {
5 | public static byte FlipByte(this byte b)
6 | {
7 | // Flips a byte so that 0b00000001 becomes 0b10000000
8 | // and 0b10000000 becomes 0b00000001
9 | var result = 0;
10 |
11 | for (var i = 0; i < 8; i++)
12 | {
13 | result <<= 1;
14 | result |= b & 1;
15 | b >>= 1;
16 | }
17 |
18 | return (byte)result;
19 | }
20 | }
--------------------------------------------------------------------------------
/NesSharp.WinForms/Program.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.WinForms
2 | {
3 | internal static class Program
4 | {
5 | ///
6 | /// The main entry point for the application.
7 | ///
8 | [STAThread]
9 | static void Main()
10 | {
11 | // To customize application configuration such as set high DPI settings or default font,
12 | // see https://aka.ms/applicationconfiguration.
13 | ApplicationConfiguration.Initialize();
14 | Application.Run(new EmulatorWindow());
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/NesSharp.WinForms/NesSharp.WinForms.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net7.0-windows
6 | enable
7 | true
8 | enable
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/NesSharp.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | IRQ
3 | NMI
4 | OAM
5 | PC
6 | RAM
7 | SP
--------------------------------------------------------------------------------
/NesSharp.Core/Mappers/MapperBase.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core.Mappers;
2 |
3 | public abstract class MapperBase
4 | {
5 | protected readonly int PrgBanks;
6 | protected readonly int ChrBanks;
7 |
8 | protected MapperBase(int prgBanks, int chrBanks)
9 | {
10 | PrgBanks = prgBanks;
11 | ChrBanks = chrBanks;
12 | }
13 |
14 | public virtual bool CpuMapRead(ushort address, out uint mappedAddress, ref byte data)
15 | {
16 | mappedAddress = 0;
17 | return false;
18 | }
19 |
20 | public virtual bool CpuMapWrite(ushort address, out uint mappedAddress, byte data = 0)
21 | {
22 | mappedAddress = 0;
23 | return false;
24 | }
25 |
26 | public virtual bool PpuMapRead(ushort address, out uint mappedAddress)
27 | {
28 | mappedAddress = 0;
29 | return false;
30 | }
31 |
32 | public virtual bool PpuMapWrite(ushort address, out uint mappedAddress)
33 | {
34 | mappedAddress = 0;
35 | return false;
36 | }
37 |
38 | public virtual void Reset()
39 | {
40 |
41 | }
42 |
43 | public virtual MirrorMode Mirror()
44 | {
45 | return MirrorMode.Hardware;
46 | }
47 |
48 | public virtual bool IRQState()
49 | {
50 | return false;
51 | }
52 |
53 | public virtual void IRQClear()
54 | {
55 |
56 | }
57 |
58 | public virtual void ScanLine()
59 | {
60 |
61 | }
62 | }
--------------------------------------------------------------------------------
/NesSharp.WinForms/Debugging/PaletteControl.cs:
--------------------------------------------------------------------------------
1 | using NesSharp.Core;
2 |
3 | namespace NesSharp.WinForms.Debugging
4 | {
5 | public partial class PaletteControl : UserControl, IDebugOutput
6 | {
7 | private Ppu? _ppu;
8 |
9 | public PaletteControl()
10 | {
11 | InitializeComponent();
12 | }
13 |
14 | public void SetPpu(Ppu ppu)
15 | {
16 | _ppu = ppu;
17 | GetPalettes();
18 | }
19 |
20 | public void DebugUpdate()
21 | {
22 | GetPalettes();
23 | }
24 |
25 | private void GetPalettes()
26 | {
27 | for (var p = 0; p < 8; p++)
28 | {
29 | for (var c = 0; c < 4; c++)
30 | {
31 | var color = _ppu!.GetColorFromPalette((byte)p, (byte)c);
32 | var control = Controls.Find($"palette{p}{c}", true).FirstOrDefault() as PictureBox;
33 |
34 | if (control is null)
35 | continue;
36 |
37 | if (control is { InvokeRequired: true, IsDisposed: false })
38 | {
39 | control.Invoke(() => control.BackColor = Color.FromArgb(color.R, color.G, color.B));
40 | }
41 | else if (!control.IsDisposed)
42 | {
43 | control.BackColor = Color.FromArgb(color.R, color.G, color.B);
44 | }
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # License (OLC-3)
2 |
3 | Copyright 2018, 2019, 2020, 2021 OneLoneCoder.com
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions or derivations of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 |
9 | 2. Redistributions or derivative works in binary form must reproduce the above copyright notice. This list of conditions and the following disclaimer must be reproduced in the documentation and/or other materials provided with the distribution.
10 |
11 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/NesSharp.Core/Mappers/Mapper003.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core.Mappers;
2 |
3 | public sealed class Mapper003 : MapperBase
4 | {
5 | private byte _chrBankSelect;
6 |
7 | public Mapper003(int prgBanks, int chrBanks) : base(prgBanks, chrBanks)
8 | {
9 | }
10 |
11 | public override bool CpuMapRead(ushort address, out uint mappedAddress, ref byte data)
12 | {
13 | if (address >= 0x8000)
14 | {
15 | mappedAddress = (uint)(address & (PrgBanks > 1 ? 0x7FFF : 0x3FFF));
16 | return true;
17 | }
18 |
19 | mappedAddress = 0;
20 | return false;
21 | }
22 |
23 | public override bool CpuMapWrite(ushort address, out uint mappedAddress, byte data = 0)
24 | {
25 | mappedAddress = 0;
26 |
27 | if (address >= 0x8000)
28 | {
29 | _chrBankSelect = (byte)(data & 0x03);
30 | mappedAddress = address;
31 | }
32 |
33 | return false;
34 | }
35 |
36 | public override bool PpuMapRead(ushort address, out uint mappedAddress)
37 | {
38 | if (address <= 0x1FFF)
39 | {
40 | mappedAddress = (uint)(_chrBankSelect * 0x2000 + address);
41 | return true;
42 | }
43 |
44 | mappedAddress = 0;
45 | return false;
46 | }
47 |
48 | public override bool PpuMapWrite(ushort address, out uint mappedAddress)
49 | {
50 | mappedAddress = 0;
51 | return false;
52 | }
53 |
54 | public override void Reset()
55 | {
56 | _chrBankSelect = 0;
57 | }
58 | }
--------------------------------------------------------------------------------
/NesSharp.Core/Mappers/Mapper000.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core.Mappers;
2 |
3 | public sealed class Mapper000 : MapperBase
4 | {
5 | public Mapper000(int prgBanks, int chrBanks) : base(prgBanks, chrBanks)
6 | {
7 | }
8 |
9 | public override bool CpuMapRead(ushort address, out uint mappedAddress, ref byte data)
10 | {
11 | if (address >= 0x8000)
12 | {
13 | mappedAddress = (uint)(address & (PrgBanks > 1 ? 0x7FFF : 0x3FFF));
14 | return true;
15 | }
16 |
17 | mappedAddress = 0;
18 | return false;
19 | }
20 |
21 | public override bool CpuMapWrite(ushort address, out uint mappedAddress, byte data = 0)
22 | {
23 | if (address >= 0x8000)
24 | {
25 | mappedAddress = (uint)(address & (PrgBanks > 1 ? 0x7FFF : 0x3FFF));
26 | return true;
27 | }
28 |
29 | mappedAddress = 0;
30 | return false;
31 | }
32 |
33 | public override bool PpuMapRead(ushort address, out uint mappedAddress)
34 | {
35 | if (address <= 0x1FFF)
36 | {
37 | mappedAddress = address;
38 | return true;
39 | }
40 |
41 | mappedAddress = 0;
42 | return false;
43 | }
44 |
45 | public override bool PpuMapWrite(ushort address, out uint mappedAddress)
46 | {
47 | if (address <= 0x1FFF)
48 | {
49 | if (ChrBanks == 0)
50 | {
51 | // Treat as RAM
52 | mappedAddress = address;
53 | return true;
54 | }
55 | }
56 |
57 | mappedAddress = 0;
58 | return false;
59 | }
60 | }
--------------------------------------------------------------------------------
/NesSharp.Core/Mappers/Mapper066.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core.Mappers;
2 |
3 | public sealed class Mapper066 : MapperBase
4 | {
5 | private byte _prgBankSelect;
6 | private byte _chrBankSelect;
7 |
8 | public Mapper066(int prgBanks, int chrBanks) : base(prgBanks, chrBanks)
9 | {
10 | }
11 |
12 | public override bool CpuMapRead(ushort address, out uint mappedAddress, ref byte data)
13 | {
14 | if (address >= 0x8000)
15 | {
16 | mappedAddress = (uint)(_prgBankSelect * 0x8000 + (address & 0x7FFF));
17 | return true;
18 | }
19 |
20 | mappedAddress = 0;
21 | return false;
22 | }
23 |
24 | public override bool CpuMapWrite(ushort address, out uint mappedAddress, byte data = 0)
25 | {
26 | mappedAddress = 0;
27 |
28 | if (address >= 0x8000)
29 | {
30 | _chrBankSelect = (byte)(data & 0x03);
31 | _prgBankSelect = (byte)((data & 0x30) >>> 4);
32 | }
33 |
34 | return false;
35 | }
36 |
37 | public override bool PpuMapRead(ushort address, out uint mappedAddress)
38 | {
39 | if (address <= 0x1FFF)
40 | {
41 | mappedAddress = (uint)(_chrBankSelect * 0x2000 + address);
42 | return true;
43 | }
44 |
45 | mappedAddress = 0;
46 | return false;
47 | }
48 |
49 | public override bool PpuMapWrite(ushort address, out uint mappedAddress)
50 | {
51 | mappedAddress = 0;
52 | return false;
53 | }
54 |
55 | public override void Reset()
56 | {
57 | _prgBankSelect = 0;
58 | _chrBankSelect = 0;
59 | }
60 | }
--------------------------------------------------------------------------------
/NesSharp.Core/Mappers/Mapper002.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core.Mappers;
2 |
3 | public sealed class Mapper002 : MapperBase
4 | {
5 | private byte _prgBankSelectLo;
6 | private byte _prgBankSelectHi;
7 |
8 | public Mapper002(int prgBanks, int chrBanks) : base(prgBanks, chrBanks)
9 | {
10 | }
11 |
12 | public override bool CpuMapRead(ushort address, out uint mappedAddress, ref byte data)
13 | {
14 | if (address is >= 0x8000 and <= 0xBFFF)
15 | {
16 | mappedAddress = (uint)(_prgBankSelectLo * 0x4000 + (address & 0x3FFF));
17 | return true;
18 | }
19 |
20 | if (address >= 0xC000)
21 | {
22 | mappedAddress = (uint)(_prgBankSelectHi * 0x4000 + (address & 0x3FFF));
23 | return true;
24 | }
25 |
26 | mappedAddress = 0;
27 | return false;
28 | }
29 |
30 | public override bool CpuMapWrite(ushort address, out uint mappedAddress, byte data = 0)
31 | {
32 | if (address >= 0x8000)
33 | {
34 | _prgBankSelectLo = (byte)(data & 0x0F);
35 | }
36 |
37 | mappedAddress = 0;
38 | return false;
39 | }
40 |
41 | public override bool PpuMapRead(ushort address, out uint mappedAddress)
42 | {
43 | if (address <= 0x1FFF)
44 | {
45 | mappedAddress = address;
46 | return true;
47 | }
48 |
49 | mappedAddress = 0;
50 | return false;
51 | }
52 |
53 | public override bool PpuMapWrite(ushort address, out uint mappedAddress)
54 | {
55 | if (address <= 0x1FFF)
56 | {
57 | if (ChrBanks == 0)
58 | {
59 | // Treat as RAM
60 | mappedAddress = address;
61 | return true;
62 | }
63 | }
64 |
65 | mappedAddress = 0;
66 | return false;
67 | }
68 |
69 | public override void Reset()
70 | {
71 | _prgBankSelectLo = 0;
72 | _prgBankSelectHi = (byte)(PrgBanks - 1);
73 | }
74 | }
--------------------------------------------------------------------------------
/NesSharp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NesSharp.Core", "NesSharp.Core\NesSharp.Core.csproj", "{D3BE8596-C95E-4301-B685-5B3B7E37432C}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NesSharp.Console", "NesSharp.Console\NesSharp.Console.csproj", "{91C01285-E25A-4655-98B3-30603DDC38FE}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NesSharp.WinForms", "NesSharp.WinForms\NesSharp.WinForms.csproj", "{F857BD0F-EF1A-4663-BBFE-641DB06A91F2}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution items", "{DF6D1300-24A2-405B-A2EA-6D1B29010722}"
13 | ProjectSection(SolutionItems) = preProject
14 | LICENSE.md = LICENSE.md
15 | README.md = README.md
16 | EndProjectSection
17 | EndProject
18 | Global
19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 | Debug|Any CPU = Debug|Any CPU
21 | Release|Any CPU = Release|Any CPU
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {D3BE8596-C95E-4301-B685-5B3B7E37432C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {D3BE8596-C95E-4301-B685-5B3B7E37432C}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {D3BE8596-C95E-4301-B685-5B3B7E37432C}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {D3BE8596-C95E-4301-B685-5B3B7E37432C}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {91C01285-E25A-4655-98B3-30603DDC38FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {91C01285-E25A-4655-98B3-30603DDC38FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {91C01285-E25A-4655-98B3-30603DDC38FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {91C01285-E25A-4655-98B3-30603DDC38FE}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {F857BD0F-EF1A-4663-BBFE-641DB06A91F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {F857BD0F-EF1A-4663-BBFE-641DB06A91F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {F857BD0F-EF1A-4663-BBFE-641DB06A91F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {F857BD0F-EF1A-4663-BBFE-641DB06A91F2}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {CBDCF78E-F5FC-4751-A34F-EA13A3B23CE2}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/NesSharp.WinForms/Sound/QueuedBufferProvider.cs:
--------------------------------------------------------------------------------
1 | using NAudio.Wave;
2 |
3 | namespace NesSharp.WinForms.Sound;
4 |
5 | public class QueuedBufferProvider : IWaveProvider
6 | {
7 | private readonly Queue _queue = new();
8 | private byte[]? _currentReadBuffer = null;
9 | private int _currentReadPos = 0;
10 |
11 | private int _queuePos = 0;
12 |
13 | public QueuedBufferProvider(WaveFormat waveFormat, int bufferLength, int bufferCount)
14 | {
15 | WaveFormat = waveFormat;
16 | BufferLength = bufferLength;
17 | BufferCount = bufferCount;
18 |
19 | for (var i = 0; i < bufferCount; i++)
20 | {
21 | _queue.Enqueue(new byte[bufferLength]);
22 | }
23 | }
24 |
25 | public WaveFormat WaveFormat { get; }
26 |
27 | /// Buffer length in bytes
28 | public int BufferLength { get; }
29 |
30 | /// Number of buffers
31 | public int BufferCount { get; }
32 |
33 | public void AddSamples(byte[] buffer, int offset, int count)
34 | {
35 | if (count > BufferLength)
36 | {
37 | throw new ArgumentException(@"count must be less than or equal to BufferLength", nameof(count));
38 | }
39 |
40 | if (_queuePos >= BufferCount)
41 | {
42 | return; // ignore samples if queue is full
43 | }
44 |
45 | Array.Copy(buffer, offset, _queue.ElementAt(_queuePos), 0, count);
46 | _queuePos++;
47 | }
48 |
49 | public int Read(byte[] buffer, int offset, int count)
50 | {
51 | if (_queuePos == 0)
52 | {
53 | return 0;
54 | }
55 |
56 | var bytesRead = 0;
57 | if (_currentReadBuffer != null)
58 | {
59 | var bytesToCopy = Math.Min(count, _currentReadBuffer.Length - _currentReadPos);
60 | Array.Copy(_currentReadBuffer, _currentReadPos, buffer, offset, bytesToCopy);
61 |
62 | bytesRead += bytesToCopy;
63 | _currentReadPos += bytesToCopy;
64 |
65 | ClearCurrentReadBufferWhenExhausted();
66 | }
67 |
68 | while (_currentReadBuffer == null && _queue.Count != 0 && bytesRead < count && _queuePos > 0)
69 | {
70 | _currentReadBuffer = _queue.Dequeue();
71 | _queuePos--;
72 |
73 | _currentReadPos = 0;
74 |
75 | var bytesToCopy = Math.Min(count - bytesRead, _currentReadBuffer.Length);
76 | Array.Copy(_currentReadBuffer, _currentReadPos, buffer, offset + bytesRead, bytesToCopy);
77 |
78 | bytesRead += bytesToCopy;
79 | _currentReadPos += bytesToCopy;
80 |
81 | ClearCurrentReadBufferWhenExhausted();
82 | }
83 |
84 | void ClearCurrentReadBufferWhenExhausted()
85 | {
86 | if (_currentReadBuffer != null && _currentReadPos >= _currentReadBuffer.Length)
87 | {
88 | Array.Clear(_currentReadBuffer);
89 | _queue.Enqueue(_currentReadBuffer);
90 |
91 | _currentReadBuffer = null;
92 | _currentReadPos = 0;
93 |
94 | BufferExhausted?.Invoke(this, EventArgs.Empty);
95 | }
96 | }
97 |
98 | return bytesRead;
99 | }
100 |
101 | public event EventHandler? BufferExhausted;
102 | }
--------------------------------------------------------------------------------
/NesSharp.WinForms/DebugWindow.cs:
--------------------------------------------------------------------------------
1 | using NesSharp.Core;
2 | using NesSharp.WinForms.Debugging;
3 | using System.Drawing.Imaging;
4 |
5 | namespace NesSharp.WinForms
6 | {
7 | public partial class DebugWindow : Form, IDebugOutput
8 | {
9 | private Ppu? _ppu;
10 |
11 | private readonly RgbColor[][] _patternTables = new RgbColor[2][];
12 | private readonly Bitmap[][] _buffer = new Bitmap[2][];
13 | private int _bufferIndex;
14 | private static readonly Rectangle PatternTableRect = new(0, 0, 128, 128);
15 |
16 | public DebugWindow()
17 | {
18 | InitializeComponent();
19 |
20 | _patternTables[0] = new RgbColor[128 * 128];
21 | _patternTables[1] = new RgbColor[128 * 128];
22 |
23 | _buffer[0] = new Bitmap[2];
24 | _buffer[1] = new Bitmap[2];
25 |
26 | _buffer[0][0] = new Bitmap(128, 128, PixelFormat.Format32bppArgb);
27 | _buffer[0][1] = new Bitmap(128, 128, PixelFormat.Format32bppArgb);
28 | _buffer[1][0] = new Bitmap(128, 128, PixelFormat.Format32bppArgb);
29 | _buffer[1][1] = new Bitmap(128, 128, PixelFormat.Format32bppArgb);
30 | }
31 |
32 | public void SetPpu(Ppu ppu)
33 | {
34 | _ppu = ppu;
35 |
36 | CreatePatternTableImage(0, patternTable0View);
37 | CreatePatternTableImage(1, patternTable1View);
38 | IncreaseBufferIndex();
39 |
40 | paletteControl1.SetPpu(ppu);
41 | }
42 |
43 | public void DebugUpdate()
44 | {
45 | CreatePatternTableImage(0, patternTable0View);
46 | CreatePatternTableImage(1, patternTable1View);
47 | IncreaseBufferIndex();
48 |
49 | paletteControl1.DebugUpdate();
50 | }
51 |
52 | private void IncreaseBufferIndex()
53 | {
54 | _bufferIndex = (_bufferIndex + 1) % 2;
55 | }
56 |
57 | private void CreatePatternTableImage(byte index, PictureBox target)
58 | {
59 | BitmapData? bitmapData = null;
60 | var bitmap = _buffer[index][_bufferIndex];
61 |
62 | try
63 | {
64 | bitmapData = bitmap.LockBits(PatternTableRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
65 | unsafe
66 | {
67 | var dstPointer = (byte*)bitmapData.Scan0.ToPointer();
68 | var src = _ppu!.GetPatternTable(_patternTables[index], index);
69 |
70 | for (var y = 0; y < 128; y++)
71 | {
72 | for (var x = 0; x < 128; x++)
73 | {
74 | var pixel = src[y * 128 + x];
75 | var offset = (y * bitmapData.Stride) + (x * 4);
76 |
77 | dstPointer[offset + 0] = pixel.B;
78 | dstPointer[offset + 1] = pixel.G;
79 | dstPointer[offset + 2] = pixel.R;
80 | dstPointer[offset + 3] = 255;
81 | }
82 | }
83 | }
84 | }
85 | catch
86 | {
87 | // unused
88 | }
89 | finally
90 | {
91 | if (bitmapData is not null)
92 | {
93 | bitmap.UnlockBits(bitmapData);
94 | }
95 |
96 | try
97 | {
98 | if (target is { InvokeRequired: true, IsDisposed: false })
99 | {
100 | target.Invoke(() => target.Image = bitmap);
101 | }
102 | else if (!target.IsDisposed)
103 | {
104 | target.Image = bitmap;
105 | }
106 | }
107 | catch
108 | {
109 | // unused
110 | }
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/NesSharp.WinForms/Sound/NAudioSoundDriver.cs:
--------------------------------------------------------------------------------
1 | using NAudio.Wave;
2 | using NesSharp.Core.Sound;
3 |
4 | namespace NesSharp.WinForms.Sound;
5 |
6 | public class NAudioSoundDriver : ISoundDriver
7 | {
8 | private uint _sampleRate;
9 | private uint _channels;
10 | private uint _blocks;
11 | private uint _blockSamples;
12 | private uint _blockFree;
13 | private uint _blockPos;
14 |
15 | private byte[] _blockMemory = Array.Empty();
16 |
17 | private float _globalTime;
18 |
19 | private SoundOut? _soundOut;
20 |
21 | private WaveOut? _soundOutput;
22 | private QueuedBufferProvider? _waveProvider;
23 | private readonly CancellationTokenSource _soundCancellationTokenSource = new();
24 | private Thread? _soundThread;
25 |
26 | private readonly object _lock = new();
27 |
28 | public void InitializeAudio(uint sampleRate, uint channels, uint blocks, uint blockSamples)
29 | {
30 | _sampleRate = sampleRate;
31 | _channels = channels;
32 | _blocks = blocks;
33 | _blockSamples = blockSamples;
34 | _blockFree = _blocks;
35 |
36 | var callback = WaveCallbackInfo.FunctionCallback();
37 | _soundOutput = new WaveOut(callback);
38 |
39 | _waveProvider = new QueuedBufferProvider(new WaveFormat((int)_sampleRate, sizeof(short) * 8, (int)_channels), (int)(_blockSamples * sizeof(short)), (int)_blocks);
40 | _waveProvider.BufferExhausted += WhenBufferExhausted;
41 |
42 | _soundOutput.Init(_waveProvider);
43 |
44 | _blockMemory = new byte[_blocks * _blockSamples * sizeof(short)];
45 |
46 | _soundThread = new Thread(SoundThread);
47 | _soundThread.Start();
48 |
49 | lock (_lock)
50 | {
51 | Monitor.Pulse(_lock);
52 | }
53 | }
54 |
55 | public void SetSoundOutMethod(SoundOut soundOut)
56 | {
57 | _soundOut = soundOut;
58 | }
59 |
60 | public void DestroyAudio()
61 | {
62 | _soundCancellationTokenSource.Cancel();
63 |
64 | _soundOutput?.Dispose();
65 | }
66 |
67 | private void SoundThread()
68 | {
69 | var cancellationToken = _soundCancellationTokenSource.Token;
70 |
71 | _globalTime = 0.0f;
72 | var timeStep = 1.0f / _sampleRate;
73 | var maxSample = (float)short.MaxValue;
74 |
75 | while (!cancellationToken.IsCancellationRequested)
76 | {
77 | // Wait for block to become available
78 | if (_blockFree == 0)
79 | {
80 | lock (_lock)
81 | {
82 | while (_blockFree == 0 && !cancellationToken.IsCancellationRequested)
83 | {
84 | Monitor.Wait(_lock, 10);
85 | }
86 | }
87 | }
88 |
89 | if (cancellationToken.IsCancellationRequested)
90 | {
91 | return;
92 | }
93 |
94 | _blockFree--;
95 |
96 | var currentBlock = _blockPos * _blockSamples;
97 |
98 | for (uint i = 0; i < _blockSamples; i += _channels)
99 | {
100 | for (uint c = 0; c < _channels; c++)
101 | {
102 | var newSample = (short)(Clip(GetMixerOutput(c, _globalTime + timeStep * i, timeStep, cancellationToken), 1.0f) * maxSample);
103 | _blockMemory[currentBlock + ((i + c) * sizeof(short)) + 0] = (byte)(newSample & 0xFF);
104 | _blockMemory[currentBlock + ((i + c) * sizeof(short)) + 1] = (byte)(newSample >>> 8);
105 | }
106 | }
107 |
108 | _globalTime += (timeStep * _blockSamples);
109 |
110 | // Send the block to the sound card
111 | _waveProvider!.AddSamples(_blockMemory, (int)currentBlock, (int)_blockSamples * sizeof(short));
112 |
113 | _blockPos++;
114 | _blockPos %= _blocks;
115 | _soundOutput!.Play();
116 | }
117 | }
118 |
119 | private void WhenBufferExhausted(object? sender, EventArgs e)
120 | {
121 | _blockFree++;
122 | lock (_lock)
123 | {
124 | Monitor.Pulse(_lock);
125 | }
126 | }
127 |
128 | private float GetMixerOutput(uint channel, float globalTime, float timeStep, CancellationToken cancellationToken = default)
129 | {
130 | if (!cancellationToken.IsCancellationRequested)
131 | {
132 | return _soundOut?.Invoke(channel, globalTime, timeStep) ?? 0.0f;
133 | }
134 |
135 | return 0.0f;
136 | }
137 |
138 | private static float Clip(float sample, float max)
139 | {
140 | return sample >= 0.0 ? Math.Min(sample, max) : Math.Max(sample, -max);
141 | }
142 |
143 | public void Dispose()
144 | {
145 | DestroyAudio();
146 |
147 | _soundCancellationTokenSource.Dispose();
148 | }
149 | }
--------------------------------------------------------------------------------
/NesSharp.WinForms/DebugWindow.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.WinForms
2 | {
3 | partial class DebugWindow
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 |
21 | if (disposing)
22 | {
23 | _buffer[0][0].Dispose();
24 | _buffer[0][1].Dispose();
25 | _buffer[1][0].Dispose();
26 | _buffer[1][1].Dispose();
27 | }
28 |
29 | base.Dispose(disposing);
30 | }
31 |
32 | #region Windows Form Designer generated code
33 |
34 | ///
35 | /// Required method for Designer support - do not modify
36 | /// the contents of this method with the code editor.
37 | ///
38 | private void InitializeComponent()
39 | {
40 | patternTable0View = new PictureBox();
41 | patternTable1View = new PictureBox();
42 | label1 = new Label();
43 | label2 = new Label();
44 | paletteControl1 = new Debugging.PaletteControl();
45 | ((System.ComponentModel.ISupportInitialize)patternTable0View).BeginInit();
46 | ((System.ComponentModel.ISupportInitialize)patternTable1View).BeginInit();
47 | SuspendLayout();
48 | //
49 | // patternTable0View
50 | //
51 | patternTable0View.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
52 | patternTable0View.Location = new Point(12, 85);
53 | patternTable0View.Name = "patternTable0View";
54 | patternTable0View.Size = new Size(256, 256);
55 | patternTable0View.SizeMode = PictureBoxSizeMode.Zoom;
56 | patternTable0View.TabIndex = 0;
57 | patternTable0View.TabStop = false;
58 | //
59 | // patternTable1View
60 | //
61 | patternTable1View.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
62 | patternTable1View.Location = new Point(274, 85);
63 | patternTable1View.Name = "patternTable1View";
64 | patternTable1View.Size = new Size(256, 256);
65 | patternTable1View.SizeMode = PictureBoxSizeMode.Zoom;
66 | patternTable1View.TabIndex = 1;
67 | patternTable1View.TabStop = false;
68 | //
69 | // label1
70 | //
71 | label1.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
72 | label1.AutoSize = true;
73 | label1.Location = new Point(12, 67);
74 | label1.Name = "label1";
75 | label1.Size = new Size(83, 15);
76 | label1.TabIndex = 2;
77 | label1.Text = "Pattern table 0";
78 | //
79 | // label2
80 | //
81 | label2.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
82 | label2.AutoSize = true;
83 | label2.Location = new Point(274, 67);
84 | label2.Name = "label2";
85 | label2.Size = new Size(83, 15);
86 | label2.TabIndex = 3;
87 | label2.Text = "Pattern table 1";
88 | //
89 | // paletteControl1
90 | //
91 | paletteControl1.Location = new Point(10, 12);
92 | paletteControl1.MaximumSize = new Size(1011, 52);
93 | paletteControl1.MinimumSize = new Size(1011, 52);
94 | paletteControl1.Name = "paletteControl1";
95 | paletteControl1.Size = new Size(1011, 52);
96 | paletteControl1.TabIndex = 4;
97 | //
98 | // DebugWindow
99 | //
100 | AutoScaleDimensions = new SizeF(7F, 15F);
101 | AutoScaleMode = AutoScaleMode.Font;
102 | ClientSize = new Size(1030, 353);
103 | Controls.Add(paletteControl1);
104 | Controls.Add(label2);
105 | Controls.Add(label1);
106 | Controls.Add(patternTable1View);
107 | Controls.Add(patternTable0View);
108 | FormBorderStyle = FormBorderStyle.FixedSingle;
109 | MaximizeBox = false;
110 | Name = "DebugWindow";
111 | Text = "NES# - Debug";
112 | ((System.ComponentModel.ISupportInitialize)patternTable0View).EndInit();
113 | ((System.ComponentModel.ISupportInitialize)patternTable1View).EndInit();
114 | ResumeLayout(false);
115 | PerformLayout();
116 | }
117 |
118 | #endregion
119 |
120 | private PictureBox patternTable0View;
121 | private PictureBox patternTable1View;
122 | private Label label1;
123 | private Label label2;
124 | private Debugging.PaletteControl paletteControl1;
125 | }
126 | }
--------------------------------------------------------------------------------
/NesSharp.WinForms/DebugWindow.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 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/NesSharp.WinForms/Debugging/PaletteControl.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 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/NesSharp.WinForms/EmulatorWindow.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 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | 17, 17
122 |
123 |
--------------------------------------------------------------------------------
/NesSharp.WinForms/EmulatorWindow.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.WinForms
2 | {
3 | partial class EmulatorWindow
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | if (disposing)
21 | {
22 | DisposeBuffers();
23 | DisposeSoundOutput();
24 | }
25 | base.Dispose(disposing);
26 | }
27 |
28 | #region Windows Form Designer generated code
29 |
30 | ///
31 | /// Required method for Designer support - do not modify
32 | /// the contents of this method with the code editor.
33 | ///
34 | private void InitializeComponent()
35 | {
36 | emulatorOutput = new PictureBox();
37 | menuStrip1 = new MenuStrip();
38 | fileToolStripMenuItem = new ToolStripMenuItem();
39 | loadROMToolStripMenuItem = new ToolStripMenuItem();
40 | toolStripMenuItem1 = new ToolStripSeparator();
41 | exitToolStripMenuItem = new ToolStripMenuItem();
42 | debugToolStripMenuItem = new ToolStripMenuItem();
43 | showDebugWindowToolStripMenuItem = new ToolStripMenuItem();
44 | ((System.ComponentModel.ISupportInitialize)emulatorOutput).BeginInit();
45 | menuStrip1.SuspendLayout();
46 | SuspendLayout();
47 | //
48 | // emulatorOutput
49 | //
50 | emulatorOutput.Dock = DockStyle.Fill;
51 | emulatorOutput.Location = new Point(0, 24);
52 | emulatorOutput.Name = "emulatorOutput";
53 | emulatorOutput.Size = new Size(752, 657);
54 | emulatorOutput.SizeMode = PictureBoxSizeMode.Zoom;
55 | emulatorOutput.TabIndex = 0;
56 | emulatorOutput.TabStop = false;
57 | //
58 | // menuStrip1
59 | //
60 | menuStrip1.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem, debugToolStripMenuItem });
61 | menuStrip1.Location = new Point(0, 0);
62 | menuStrip1.Name = "menuStrip1";
63 | menuStrip1.Size = new Size(752, 24);
64 | menuStrip1.TabIndex = 1;
65 | menuStrip1.Text = "menuStrip1";
66 | //
67 | // fileToolStripMenuItem
68 | //
69 | fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { loadROMToolStripMenuItem, toolStripMenuItem1, exitToolStripMenuItem });
70 | fileToolStripMenuItem.Name = "fileToolStripMenuItem";
71 | fileToolStripMenuItem.Size = new Size(37, 20);
72 | fileToolStripMenuItem.Text = "&File";
73 | //
74 | // loadROMToolStripMenuItem
75 | //
76 | loadROMToolStripMenuItem.Name = "loadROMToolStripMenuItem";
77 | loadROMToolStripMenuItem.ShortcutKeys = Keys.Control | Keys.O;
78 | loadROMToolStripMenuItem.Size = new Size(182, 22);
79 | loadROMToolStripMenuItem.Text = "&Load ROM...";
80 | loadROMToolStripMenuItem.Click += OnLoadRomClicked;
81 | //
82 | // toolStripMenuItem1
83 | //
84 | toolStripMenuItem1.Name = "toolStripMenuItem1";
85 | toolStripMenuItem1.Size = new Size(179, 6);
86 | //
87 | // exitToolStripMenuItem
88 | //
89 | exitToolStripMenuItem.Name = "exitToolStripMenuItem";
90 | exitToolStripMenuItem.ShortcutKeys = Keys.Alt | Keys.F4;
91 | exitToolStripMenuItem.Size = new Size(182, 22);
92 | exitToolStripMenuItem.Text = "&Exit";
93 | exitToolStripMenuItem.Click += exitToolStripMenuItem_Click;
94 | //
95 | // debugToolStripMenuItem
96 | //
97 | debugToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { showDebugWindowToolStripMenuItem });
98 | debugToolStripMenuItem.Name = "debugToolStripMenuItem";
99 | debugToolStripMenuItem.Size = new Size(54, 20);
100 | debugToolStripMenuItem.Text = "&Debug";
101 | //
102 | // showDebugWindowToolStripMenuItem
103 | //
104 | showDebugWindowToolStripMenuItem.Name = "showDebugWindowToolStripMenuItem";
105 | showDebugWindowToolStripMenuItem.Size = new Size(197, 22);
106 | showDebugWindowToolStripMenuItem.Text = "&Show Debug Window...";
107 | showDebugWindowToolStripMenuItem.Click += showDebugWindowToolStripMenuItem_Click;
108 | //
109 | // EmulatorWindow
110 | //
111 | AutoScaleDimensions = new SizeF(7F, 15F);
112 | AutoScaleMode = AutoScaleMode.Font;
113 | ClientSize = new Size(752, 681);
114 | Controls.Add(emulatorOutput);
115 | Controls.Add(menuStrip1);
116 | MainMenuStrip = menuStrip1;
117 | Name = "EmulatorWindow";
118 | Text = "NES#";
119 | FormClosing += EmulatorWindow_Closing;
120 | Load += EmulatorWindow_Load;
121 | KeyDown += OnKeyDown;
122 | KeyUp += OnKeyUp;
123 | ((System.ComponentModel.ISupportInitialize)emulatorOutput).EndInit();
124 | menuStrip1.ResumeLayout(false);
125 | menuStrip1.PerformLayout();
126 | ResumeLayout(false);
127 | PerformLayout();
128 | }
129 |
130 | #endregion
131 |
132 | private PictureBox emulatorOutput;
133 | private MenuStrip menuStrip1;
134 | private ToolStripMenuItem fileToolStripMenuItem;
135 | private ToolStripMenuItem exitToolStripMenuItem;
136 | private ToolStripMenuItem debugToolStripMenuItem;
137 | private ToolStripMenuItem showDebugWindowToolStripMenuItem;
138 | private ToolStripMenuItem loadROMToolStripMenuItem;
139 | private ToolStripSeparator toolStripMenuItem1;
140 | }
141 | }
--------------------------------------------------------------------------------
/NesSharp.Core/Bus.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core;
2 |
3 | public sealed class Bus
4 | {
5 | private readonly Cpu _cpu = new();
6 | private readonly Ppu _ppu = new();
7 | private readonly Apu _apu = new();
8 | private Cartridge? _cartridge;
9 |
10 | private readonly byte[] _cpuRam = new byte[2 * 1024];
11 |
12 | private byte _dmaPage;
13 | private byte _dmaAddress;
14 | private byte _dmaData;
15 | private bool _dmaTransfer;
16 | private bool _dmaDummy = true;
17 |
18 | private readonly byte[] _controller = new byte[2];
19 | private readonly byte[] _controllerState = new byte[2];
20 |
21 | private uint _systemClockCounter;
22 |
23 | private double _audioTimePerSystemSample;
24 | private double _audioTimePerNESClock;
25 | private double _audioTime;
26 |
27 | public Bus()
28 | {
29 | _cpu.ConnectBus(this);
30 | }
31 |
32 | public void Initialize()
33 | {
34 | Array.Clear(_cpuRam, 0, 2 * 1024);
35 | }
36 |
37 | public byte CpuRead(ushort address, bool readOnly = false)
38 | {
39 | if (_cartridge != null && _cartridge.CpuRead(address, out var data))
40 | {
41 | return data;
42 | }
43 |
44 | switch (address)
45 | {
46 | case <= 0x1FFF:
47 | return _cpuRam[address & 0x07FF];
48 | case <= 0x3FFF:
49 | return _ppu.CpuRead((ushort)(address & 0x0007), readOnly);
50 | case >= 0x4016 and <= 0x4017:
51 | data = (_controllerState[address & 0x0001] & 0x80) > 0 ? (byte)1 : (byte)0;
52 | _controllerState[address & 0x0001] <<= 1;
53 | return data;
54 | //case >= 0x8000:
55 | // return _cartridge!.CpuRead(address, readOnly);
56 | default:
57 | return 0;
58 | }
59 | }
60 |
61 | public void CpuWrite(ushort address, byte data)
62 | {
63 | if (_cartridge != null && _cartridge.CpuWrite(address, data))
64 | {
65 | return;
66 | }
67 |
68 | if (address <= 0x1FFF)
69 | {
70 | _cpuRam[address & 0x07FF] = data;
71 | }
72 | else if (address <= 0x3FFF)
73 | {
74 | _ppu.CpuWrite((ushort)(address & 0x0007), data);
75 | }
76 | else if (address is >= 0x4000 and <= 0x4013 or 0x4015 or 0x4017)
77 | {
78 | _apu.CpuWrite(address, data);
79 | }
80 | else if (address == 0x4014)
81 | {
82 | _dmaPage = data;
83 | _dmaAddress = 0x00;
84 | _dmaTransfer = true;
85 | }
86 | else if (address is >= 0x4016 and <= 0x4017)
87 | {
88 | _controllerState[address & 0x0001] = _controller[address & 0x0001];
89 | }
90 | //else if (address >= 0x8000)
91 | //{
92 | // _cpu.CpuWrite(address, data);
93 | //}
94 | }
95 |
96 | public void InsertCartridge(Cartridge cartridge)
97 | {
98 | _cartridge = cartridge;
99 | _ppu.ConnectCartridge(cartridge);
100 | }
101 |
102 | public void Reset()
103 | {
104 | _cartridge?.Reset();
105 | _cpu.Reset();
106 | _ppu.Reset();
107 | _systemClockCounter = 0;
108 | IsRunning = true;
109 | }
110 |
111 | public void Stop()
112 | {
113 | IsRunning = false;
114 |
115 | _cpu.Stop();
116 | _ppu.Stop();
117 | }
118 |
119 | public bool IsRunning { get; private set; }
120 |
121 | public bool Clock()
122 | {
123 | _ppu.Clock();
124 | _apu.Clock();
125 |
126 | if (_systemClockCounter % 3 == 0)
127 | {
128 | if (_dmaTransfer)
129 | {
130 | if (_dmaDummy)
131 | {
132 | if (_systemClockCounter % 2 == 1)
133 | {
134 | _dmaDummy = false;
135 | }
136 | }
137 | else
138 | {
139 | if (_systemClockCounter % 2 == 0)
140 | {
141 | _dmaData = CpuRead((ushort)((_dmaPage << 8) | _dmaAddress));
142 | }
143 | else
144 | {
145 | _ppu.OAMData[_dmaAddress] = _dmaData;
146 | _dmaAddress++;
147 | if (_dmaAddress == 0x00)
148 | {
149 | _dmaTransfer = false;
150 | _dmaDummy = true;
151 | }
152 | }
153 | }
154 | }
155 | else
156 | {
157 | _cpu.Clock();
158 | }
159 | }
160 |
161 | // Synchronizing with audio
162 | var audioSampleReady = false;
163 | _audioTime += _audioTimePerNESClock;
164 | if (_audioTime >= _audioTimePerSystemSample)
165 | {
166 | _audioTime -= _audioTimePerSystemSample;
167 | AudioSample = _apu.GetOutputSample();
168 | audioSampleReady = true;
169 | }
170 |
171 | if (_ppu.IsNMISet)
172 | {
173 | _ppu.IsNMISet = false;
174 | _cpu.NMI();
175 | }
176 |
177 | // Check if cartridge is requesting IRQ
178 | if (_cartridge!.Mapper!.IRQState())
179 | {
180 | _cartridge.Mapper.IRQClear();
181 | _cpu.IRQ();
182 | }
183 |
184 | _systemClockCounter++;
185 |
186 | return audioSampleReady;
187 | }
188 |
189 | public void SetControllerState(int player, bool up, bool down, bool left, bool right, bool start, bool select, bool btnA, bool btnB)
190 | {
191 | byte data = 0;
192 | data |= (byte)(btnA ? 0x80 : 0);
193 | data |= (byte)(btnB ? 0x40 : 0);
194 | data |= (byte)(select ? 0x20 : 0);
195 | data |= (byte)(start ? 0x10 : 0);
196 | data |= (byte)(up ? 0x08 : 0);
197 | data |= (byte)(down ? 0x04 : 0);
198 | data |= (byte)(left ? 0x02 : 0);
199 | data |= (byte)(right ? 0x01 : 0);
200 |
201 | _controller[player] = data;
202 | }
203 |
204 | public void SetSampleFrequency(uint sampleRate)
205 | {
206 | _audioTimePerSystemSample = 1.0 / sampleRate;
207 | _audioTimePerNESClock = 1.0 / 5369318.0; // PPU Clock Frequency
208 | }
209 |
210 | public Ppu Ppu => _ppu;
211 | public double AudioSample { get; private set; }
212 | }
--------------------------------------------------------------------------------
/NesSharp.Core/Cartridge.cs:
--------------------------------------------------------------------------------
1 | using NesSharp.Core.Mappers;
2 |
3 | namespace NesSharp.Core;
4 |
5 | public class Cartridge
6 | {
7 | private readonly List _prgMemory = new();
8 | private readonly List _chrMemory = new();
9 |
10 | private int _mapperId;
11 | private MirrorMode _mirrorMode = MirrorMode.Horizontal;
12 | private int _prgBanks;
13 | private int _chrBanks;
14 |
15 | private MapperBase? _mapper;
16 |
17 | public static async Task FromFile(string fileName)
18 | {
19 | var cartridge = new Cartridge();
20 |
21 | await using var fs = File.OpenRead(fileName);
22 | using var br = new BinaryReader(fs);
23 |
24 | var header = new NesHeader
25 | {
26 | Name = br.ReadChars(4),
27 | PrgRomChunks = br.ReadByte(),
28 | ChrRomChunks = br.ReadByte(),
29 | Mapper1 = br.ReadByte(),
30 | Mapper2 = br.ReadByte(),
31 | PrgRamSize = br.ReadByte(),
32 | TvSystem1 = br.ReadByte(),
33 | TvSystem2 = br.ReadByte(),
34 | Unused = br.ReadBytes(5)
35 | };
36 |
37 | Nes2Header? header2 = null;
38 |
39 | // Discover File Format
40 | var fileType = 1;
41 | if ((header.Mapper1 & 0x01) != 0)
42 | {
43 | fileType = 2;
44 | br.BaseStream.Seek(0, SeekOrigin.Begin);
45 | header2 = new Nes2Header
46 | {
47 | Name = br.ReadChars(4),
48 | PrgRomSizeLsb = br.ReadByte(),
49 | ChrRomSizeLsb = br.ReadByte(),
50 | Mapper1 = br.ReadByte(),
51 | Mapper2 = br.ReadByte(),
52 | Mapper3 = br.ReadByte(),
53 | PrgCharRomSizeMsb = br.ReadByte(),
54 | PrgRamSize = br.ReadByte(),
55 | ChrRamSize = br.ReadByte(),
56 | CpuPpuTiming = br.ReadByte(),
57 | TvSystem = br.ReadByte(),
58 | MiscRoms = br.ReadByte(),
59 | ExpansionDevice = br.ReadByte()
60 | };
61 | }
62 |
63 | if ((header.Mapper1 & 0x04) != 0)
64 | {
65 | // skip trainer data
66 | br.BaseStream.Seek(512, SeekOrigin.Current);
67 | }
68 |
69 | // Determine Mapper ID
70 | cartridge._mapperId = ((header.Mapper2 >>> 4) << 4) | (header.Mapper1 >>> 4);
71 | cartridge._mirrorMode = (header.Mapper1 & 0x01) != 0 ? MirrorMode.Vertical : MirrorMode.Horizontal;
72 |
73 | //if (fileType == 0)
74 | //{
75 |
76 | //}
77 |
78 | if (fileType == 1)
79 | {
80 | cartridge._prgBanks = header.PrgRomChunks;
81 | cartridge._prgMemory.AddRange(br.ReadBytes(cartridge._prgBanks * 16 * 1024));
82 |
83 | cartridge._chrBanks = header.ChrRomChunks;
84 |
85 | // Create RAM if CHR ROM is not present
86 | cartridge._chrMemory.AddRange(cartridge._chrBanks == 0 ? new byte[8 * 1024] : br.ReadBytes(cartridge._chrBanks * 8 * 1024));
87 | }
88 |
89 | if (fileType == 2 && header2.HasValue)
90 | {
91 | // Determine the amount of PRG ROM banks based on the NES 2.0 header2
92 | var prgRomSize = ((header.PrgRamSize & 0x07) << 8) | header.PrgRomChunks;
93 | cartridge._prgBanks = prgRomSize;
94 | cartridge._prgMemory.AddRange(br.ReadBytes(cartridge._prgBanks * 16 * 1024));
95 |
96 | var chrRomSize = ((header.PrgRamSize & 0x38) << 8) | header.ChrRomChunks;
97 | cartridge._chrBanks = chrRomSize;
98 |
99 | // Create RAM if CHR ROM is not present
100 | cartridge._chrMemory.AddRange(cartridge._chrBanks == 0 ? new byte[8 * 1024] : br.ReadBytes(cartridge._chrBanks * 8 * 1024));
101 | }
102 |
103 | switch (cartridge._mapperId)
104 | {
105 | case 0:
106 | cartridge._mapper = new Mapper000(cartridge._prgBanks, cartridge._chrBanks);
107 | break;
108 |
109 | case 1:
110 | cartridge._mapper = new Mapper001(cartridge._prgBanks, cartridge._chrBanks);
111 | break;
112 |
113 | case 2:
114 | cartridge._mapper = new Mapper002(cartridge._prgBanks, cartridge._chrBanks);
115 | break;
116 |
117 | case 3:
118 | cartridge._mapper = new Mapper003(cartridge._prgBanks, cartridge._chrBanks);
119 | break;
120 |
121 | case 4:
122 | cartridge._mapper = new Mapper004(cartridge._prgBanks, cartridge._chrBanks);
123 | break;
124 |
125 | case 66:
126 | cartridge._mapper = new Mapper066(cartridge._prgBanks, cartridge._chrBanks);
127 | break;
128 | }
129 |
130 | return cartridge;
131 | }
132 |
133 | public bool CpuRead(ushort address, out byte data)
134 | {
135 | data = 0;
136 |
137 | if (_mapper is not null && _mapper.CpuMapRead(address, out var mappedAddress, ref data))
138 | {
139 | if (mappedAddress == 0xFFFFFFFF)
140 | {
141 | // Mapper has handled the read, no need to continue
142 | return true;
143 | }
144 |
145 | data = _prgMemory[(int)mappedAddress];
146 | return true;
147 | }
148 |
149 | return false;
150 | }
151 |
152 | public bool CpuWrite(ushort address, byte data)
153 | {
154 | if (_mapper is not null && _mapper.CpuMapWrite(address, out var mappedAddress, data))
155 | {
156 | if (mappedAddress == 0xFFFFFFFF)
157 | {
158 | // Mapper has handled the write, no need to continue
159 | return true;
160 | }
161 |
162 | _prgMemory[(int)mappedAddress] = data;
163 | return true;
164 | }
165 |
166 | return false;
167 | }
168 |
169 | public bool PpuRead(ushort address, out byte data)
170 | {
171 | if (_mapper is not null && _mapper.PpuMapRead(address, out var mappedAddress))
172 | {
173 | data = _chrMemory[(int)mappedAddress];
174 | return true;
175 | }
176 |
177 | data = 0;
178 | return false;
179 | }
180 |
181 | public bool PpuWrite(ushort address, byte data)
182 | {
183 | if (_mapper is not null && _mapper.PpuMapWrite(address, out var mappedAddress))
184 | {
185 | _chrMemory[(int)mappedAddress] = data;
186 | return true;
187 | }
188 |
189 | return false;
190 | }
191 |
192 | public void Reset()
193 | {
194 | _mapper?.Reset();
195 | }
196 |
197 | public MirrorMode MirrorMode
198 | {
199 | get
200 | {
201 | var m = _mapper?.Mirror() ?? _mirrorMode;
202 | return m == MirrorMode.Hardware ? _mirrorMode : m;
203 | }
204 | }
205 |
206 | public MapperBase? Mapper => _mapper;
207 |
208 | private readonly record struct NesHeader(char[] Name, byte PrgRomChunks, byte ChrRomChunks, byte Mapper1, byte Mapper2, byte PrgRamSize, byte TvSystem1, byte TvSystem2, byte[] Unused);
209 | private readonly record struct Nes2Header(char[] Name, byte PrgRomSizeLsb, byte ChrRomSizeLsb, byte Mapper1, byte Mapper2, byte Mapper3, byte PrgCharRomSizeMsb, byte PrgRamSize, byte ChrRamSize, byte CpuPpuTiming, byte TvSystem, byte MiscRoms, byte ExpansionDevice);
210 | }
--------------------------------------------------------------------------------
/NesSharp.Core/Mappers/Mapper001.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core.Mappers;
2 |
3 | public sealed class Mapper001 : MapperBase
4 | {
5 | private byte _chrBankSelect4Lo;
6 | private byte _chrBankSelect4Hi;
7 | private byte _chrBankSelect8;
8 |
9 | private byte _prgBankSelect16Lo;
10 | private byte _prgBankSelect16Hi;
11 | private byte _prgBankSelect32;
12 |
13 | private byte _loadRegister;
14 | private byte _loadRegisterCount;
15 | private byte _controlRegister;
16 |
17 | private MirrorMode _mirrorMode = MirrorMode.Horizontal;
18 | private readonly byte[] _staticRAM = new byte[32 * 1024];
19 |
20 | // TODO: Allow saving of SRAM for cartridges using this mapper
21 |
22 | public Mapper001(int prgBanks, int chrBanks) : base(prgBanks, chrBanks)
23 | {
24 | }
25 |
26 | public override bool CpuMapRead(ushort address, out uint mappedAddress, ref byte data)
27 | {
28 | if (address is >= 0x6000 and <= 0x7FFF)
29 | {
30 | // Read from static ram on cartridge
31 | mappedAddress = 0xFFFFFFFF;
32 |
33 | // Read data from RAM
34 | data = _staticRAM[address & 0x1FFF];
35 |
36 | // Signal mapper has handled request
37 | return true;
38 | }
39 |
40 | if (address >= 0x8000)
41 | {
42 | if ((_controlRegister & 0b01000) != 0)
43 | {
44 | // 16K Mode
45 | if (address is >= 0x8000 and <= 0xBFFF)
46 | {
47 | mappedAddress = (uint)(_prgBankSelect16Lo * 0x4000 + (address & 0x3FFF));
48 | return true;
49 | }
50 |
51 | mappedAddress = (uint)(_prgBankSelect16Hi * 0x4000 + (address & 0x3FFF));
52 | return true;
53 | }
54 |
55 | // 32K Mode
56 | mappedAddress = (uint)(_prgBankSelect32 * 0x8000 + (address & 0x7FFF));
57 | return true;
58 | }
59 |
60 | mappedAddress = 0;
61 | return false;
62 | }
63 |
64 | public override bool CpuMapWrite(ushort address, out uint mappedAddress, byte data = 0)
65 | {
66 | if (address is >= 0x6000 and <= 0x7FFF)
67 | {
68 | // Write is to static ram on cartridge
69 | mappedAddress = 0xFFFFFFFF;
70 |
71 | // Write data to RAM
72 | _staticRAM[address & 0x1FFF] = data;
73 |
74 | // Signal mapper has handled request
75 | return true;
76 | }
77 |
78 | if (address >= 0x8000)
79 | {
80 | if ((data & 0x80) != 0)
81 | {
82 | // MSB is set, so reset serial loading
83 | _loadRegister = 0x00;
84 | _loadRegisterCount = 0;
85 | _controlRegister |= 0x0C;
86 | }
87 | else
88 | {
89 | // Load data in serially into load register
90 | // It arrives LSB first, so implant this at
91 | // bit 5. After 5 writes, the register is ready
92 | _loadRegister >>>= 1;
93 | _loadRegister |= (byte)((data & 0x01) << 4);
94 | _loadRegisterCount++;
95 |
96 | if (_loadRegisterCount == 5)
97 | {
98 | // Get Mapper Target Register, by examining
99 | // bits 13 & 14 of the address
100 | var targetRegister = (byte)((address >>> 13) & 0x03);
101 |
102 | if (targetRegister == 0) // 0x8000 - 0x9FFF
103 | {
104 | // Set Control Register
105 | _controlRegister = (byte)(_loadRegister & 0x1F);
106 |
107 | switch (_controlRegister & 0x03)
108 | {
109 | case 0: _mirrorMode = MirrorMode.OneScreenLow; break;
110 | case 1: _mirrorMode = MirrorMode.OneScreenHigh; break;
111 | case 2: _mirrorMode = MirrorMode.Vertical; break;
112 | case 3: _mirrorMode = MirrorMode.Horizontal; break;
113 | }
114 | }
115 | else if (targetRegister == 1) // 0xA000 - 0xBFFF
116 | {
117 | // Set CHR Bank Lo
118 | if ((_controlRegister & 0b10000) != 0)
119 | {
120 | // 4K CHR Bank at PPU 0x0000
121 | _chrBankSelect4Lo = (byte)(_loadRegister & 0x1F);
122 | }
123 | else
124 | {
125 | // 8K CHR Bank at PPU 0x0000
126 | _chrBankSelect8 = (byte)(_loadRegister & 0x1E);
127 | }
128 | }
129 | else if (targetRegister == 2) // 0xC000 - 0xDFFF
130 | {
131 | // Set CHR Bank Hi
132 | if ((_controlRegister & 0b10000) != 0)
133 | {
134 | // 4K CHR Bank at PPU 0x1000
135 | _chrBankSelect4Hi = (byte)(_loadRegister & 0x1F);
136 | }
137 | }
138 | else if (targetRegister == 3) // 0xE000 - 0xFFFF
139 | {
140 | // Configure PRG Banks
141 | var prgMode = (byte)((_controlRegister >>> 2) & 0x03);
142 |
143 | if (prgMode is 0 or 1)
144 | {
145 | // Set 32K PRG Bank at CPU 0x8000
146 | _prgBankSelect32 = (byte)((_loadRegister & 0x0E) >>> 1);
147 | }
148 | else if (prgMode == 2)
149 | {
150 | // Fix 16KB PRG Bank at CPU 0x8000 to First Bank
151 | _prgBankSelect16Lo = 0;
152 | // Set 16KB PRG Bank at CPU 0xC000
153 | _prgBankSelect16Hi = (byte)(_loadRegister & 0x0F);
154 | }
155 | else if (prgMode == 3)
156 | {
157 | // Set 16KB PRG Bank at CPU 0x8000
158 | _prgBankSelect16Lo = (byte)(_loadRegister & 0x0F);
159 | // Fix 16KB PRG Bank at CPU 0xC000 to Last Bank
160 | _prgBankSelect16Hi = (byte)(PrgBanks - 1);
161 | }
162 | }
163 |
164 | // 5 bits were written, and decoded, so
165 | // reset load register
166 | _loadRegister = 0x00;
167 | _loadRegisterCount = 0;
168 | }
169 | }
170 | }
171 |
172 | // Mapper has handled write, but do not update ROMs
173 | mappedAddress = 0;
174 | return false;
175 | }
176 |
177 | public override bool PpuMapRead(ushort address, out uint mappedAddress)
178 | {
179 | if (address < 0x2000)
180 | {
181 | if (ChrBanks == 0)
182 | {
183 | mappedAddress = address;
184 | return true;
185 | }
186 | else
187 | {
188 | if ((_controlRegister & 0b10000) != 0)
189 | {
190 | // 4K CHR Bank Mode
191 | if (address <= 0x0FFF)
192 | {
193 | mappedAddress = (uint)(_chrBankSelect4Lo * 0x1000 + (address & 0x0FFF));
194 | return true;
195 | }
196 |
197 | mappedAddress = (uint)(_chrBankSelect4Hi * 0x1000 + (address & 0x0FFF));
198 | return true;
199 | }
200 |
201 |
202 | // 8K CHR Bank Mode
203 | mappedAddress = (uint)(_chrBankSelect8 * 0x2000 + (address & 0x1FFF));
204 | return true;
205 | }
206 | }
207 |
208 | mappedAddress = 0;
209 | return false;
210 | }
211 |
212 | public override bool PpuMapWrite(ushort address, out uint mappedAddress)
213 | {
214 | mappedAddress = 0;
215 |
216 | if (address < 0x2000)
217 | {
218 | if (ChrBanks == 0)
219 | {
220 | mappedAddress = address;
221 | return true;
222 | }
223 |
224 | return true;
225 | }
226 |
227 | return false;
228 | }
229 |
230 | public override void Reset()
231 | {
232 | _controlRegister = 0x1C;
233 | _loadRegister = 0x00;
234 | _loadRegisterCount = 0x00;
235 |
236 | _chrBankSelect4Lo = 0;
237 | _chrBankSelect4Hi = 0;
238 | _chrBankSelect8 = 0;
239 |
240 | _prgBankSelect32 = 0;
241 | _prgBankSelect16Lo = 0;
242 | _prgBankSelect16Hi = (byte)(PrgBanks - 1);
243 | }
244 |
245 | public override MirrorMode Mirror()
246 | {
247 | return _mirrorMode;
248 | }
249 | }
--------------------------------------------------------------------------------
/NesSharp.Core/Mappers/Mapper004.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core.Mappers;
2 |
3 | public sealed class Mapper004 : MapperBase
4 | {
5 | private byte _targetRegister;
6 | private bool _prgBankMode;
7 | private bool _chrInversion;
8 |
9 | private uint[] _register = new uint[8];
10 | private uint[] _chrBank = new uint[8];
11 | private uint[] _prgBank = new uint[4];
12 |
13 | private bool _irqEnable;
14 | private bool _irqActive;
15 | private bool _irqUpdate;
16 | private ushort _irqCounter;
17 | private ushort _irqReload;
18 |
19 | private MirrorMode _mirrorMode = MirrorMode.Horizontal;
20 | private readonly byte[] _staticRAM = new byte[32 * 1024];
21 |
22 | // TODO: Allow saving of SRAM for cartridges using this mapper
23 |
24 | public Mapper004(int prgBanks, int chrBanks) : base(prgBanks, chrBanks)
25 | {
26 | }
27 |
28 | public override bool CpuMapRead(ushort address, out uint mappedAddress, ref byte data)
29 | {
30 | mappedAddress = 0;
31 |
32 | if (address is >= 0x6000 and <= 0x7FFF)
33 | {
34 | // Write is to static ram on cartridge
35 | mappedAddress = 0xFFFFFFFF;
36 |
37 | // Write data to RAM
38 | data = _staticRAM[address & 0x1FFF];
39 |
40 | // Signal mapper has handled request
41 | return true;
42 | }
43 |
44 |
45 | if (address is >= 0x8000 and <= 0x9FFF)
46 | {
47 | mappedAddress = (uint)(_prgBank[0] + (address & 0x1FFF));
48 | return true;
49 | }
50 |
51 | if (address is >= 0xA000 and <= 0xBFFF)
52 | {
53 | mappedAddress = (uint)(_prgBank[1] + (address & 0x1FFF));
54 | return true;
55 | }
56 |
57 | if (address is >= 0xC000 and <= 0xDFFF)
58 | {
59 | mappedAddress = (uint)(_prgBank[2] + (address & 0x1FFF));
60 | return true;
61 | }
62 |
63 | if (address >= 0xE000)
64 | {
65 | mappedAddress = (uint)(_prgBank[3] + (address & 0x1FFF));
66 | return true;
67 | }
68 |
69 | return false;
70 | }
71 |
72 | public override bool CpuMapWrite(ushort address, out uint mappedAddress, byte data = 0)
73 | {
74 | mappedAddress = 0;
75 |
76 | if (address is >= 0x6000 and <= 0x7FFF)
77 | {
78 | // Write is to static ram on cartridge
79 | mappedAddress = 0xFFFFFFFF;
80 |
81 | // Write data to RAM
82 | _staticRAM[address & 0x1FFF] = data;
83 |
84 | // Signal mapper has handled request
85 | return true;
86 | }
87 |
88 | if (address is >= 0x8000 and <= 0x9FFF)
89 | {
90 | // Bank Select
91 | if ((address & 0x0001) == 0)
92 | {
93 | _targetRegister = (byte)(data & 0x07);
94 | _prgBankMode = (data & 0x40) != 0;
95 | _chrInversion = (data & 0x80) != 0;
96 | }
97 | else
98 | {
99 | // Update target register
100 | _register[_targetRegister] = data;
101 |
102 | // Update Pointer Table
103 | if (_chrInversion)
104 | {
105 | _chrBank[0] = _register[2] * 0x0400;
106 | _chrBank[1] = _register[3] * 0x0400;
107 | _chrBank[2] = _register[4] * 0x0400;
108 | _chrBank[3] = _register[5] * 0x0400;
109 | _chrBank[4] = (_register[0] & 0xFE) * 0x0400;
110 | _chrBank[5] = _register[0] * 0x0400 + 0x0400;
111 | _chrBank[6] = (_register[1] & 0xFE) * 0x0400;
112 | _chrBank[7] = _register[1] * 0x0400 + 0x0400;
113 | }
114 | else
115 | {
116 | _chrBank[0] = (_register[0] & 0xFE) * 0x0400;
117 | _chrBank[1] = _register[0] * 0x0400 + 0x0400;
118 | _chrBank[2] = (_register[1] & 0xFE) * 0x0400;
119 | _chrBank[3] = _register[1] * 0x0400 + 0x0400;
120 | _chrBank[4] = _register[2] * 0x0400;
121 | _chrBank[5] = _register[3] * 0x0400;
122 | _chrBank[6] = _register[4] * 0x0400;
123 | _chrBank[7] = _register[5] * 0x0400;
124 | }
125 |
126 | if (_prgBankMode)
127 | {
128 | _prgBank[2] = (_register[6] & 0x3F) * 0x2000;
129 | _prgBank[0] = (uint)((PrgBanks * 2 - 2) * 0x2000);
130 | }
131 | else
132 | {
133 | _prgBank[0] = (_register[6] & 0x3F) * 0x2000;
134 | _prgBank[2] = (uint)((PrgBanks * 2 - 2) * 0x2000);
135 | }
136 |
137 | _prgBank[1] = (_register[7] & 0x3F) * 0x2000;
138 | _prgBank[3] = (uint)((PrgBanks * 2 - 1) * 0x2000);
139 |
140 | }
141 |
142 | return false;
143 | }
144 |
145 | if (address is >= 0xA000 and <= 0xBFFF)
146 | {
147 | if ((address & 0x0001) == 0)
148 | {
149 | // Mirroring
150 | _mirrorMode = (data & 0x01) != 0 ? MirrorMode.Horizontal : MirrorMode.Vertical;
151 | }
152 | else
153 | {
154 | // PRG Ram Protect
155 | // TODO:
156 | }
157 | return false;
158 | }
159 |
160 | if (address is >= 0xC000 and <= 0xDFFF)
161 | {
162 | if ((address & 0x0001) == 0)
163 | {
164 | _irqReload = data;
165 | }
166 | else
167 | {
168 | _irqCounter = 0x0000;
169 | }
170 | return false;
171 | }
172 |
173 | if (address >= 0xE000)
174 | {
175 | if ((address & 0x0001) == 0)
176 | {
177 | _irqEnable = false;
178 | _irqActive = false;
179 | }
180 | else
181 | {
182 | _irqEnable = true;
183 | }
184 | return false;
185 | }
186 |
187 | return false;
188 | }
189 |
190 | public override bool PpuMapRead(ushort address, out uint mappedAddress)
191 | {
192 | mappedAddress = 0;
193 |
194 | if (address <= 0x03FF)
195 | {
196 | mappedAddress = (uint)(_chrBank[0] + (address & 0x03FF));
197 | return true;
198 | }
199 |
200 | if (address is >= 0x0400 and <= 0x07FF)
201 | {
202 | mappedAddress = (uint)(_chrBank[1] + (address & 0x03FF));
203 | return true;
204 | }
205 |
206 | if (address is >= 0x0800 and <= 0x0BFF)
207 | {
208 | mappedAddress = (uint)(_chrBank[2] + (address & 0x03FF));
209 | return true;
210 | }
211 |
212 | if (address is >= 0x0C00 and <= 0x0FFF)
213 | {
214 | mappedAddress = (uint)(_chrBank[3] + (address & 0x03FF));
215 | return true;
216 | }
217 |
218 | if (address is >= 0x1000 and <= 0x13FF)
219 | {
220 | mappedAddress = (uint)(_chrBank[4] + (address & 0x03FF));
221 | return true;
222 | }
223 |
224 | if (address is >= 0x1400 and <= 0x17FF)
225 | {
226 | mappedAddress = (uint)(_chrBank[5] + (address & 0x03FF));
227 | return true;
228 | }
229 |
230 | if (address is >= 0x1800 and <= 0x1BFF)
231 | {
232 | mappedAddress = (uint)(_chrBank[6] + (address & 0x03FF));
233 | return true;
234 | }
235 |
236 | if (address is >= 0x1C00 and <= 0x1FFF)
237 | {
238 | mappedAddress = (uint)(_chrBank[7] + (address & 0x03FF));
239 | return true;
240 | }
241 |
242 | return false;
243 | }
244 |
245 | public override bool PpuMapWrite(ushort address, out uint mappedAddress)
246 | {
247 | mappedAddress = 0;
248 | return false;
249 | }
250 |
251 | public override void Reset()
252 | {
253 | _targetRegister = 0x00;
254 | _prgBankMode = false;
255 | _chrInversion = false;
256 | _mirrorMode = MirrorMode.Horizontal;
257 |
258 | _irqActive = false;
259 | _irqEnable = false;
260 | _irqUpdate = false;
261 | _irqCounter = 0x0000;
262 | _irqReload = 0x0000;
263 |
264 | Array.Clear(_prgBank, 0, _prgBank.Length);
265 | Array.Clear(_chrBank, 0, _chrBank.Length);
266 | Array.Clear(_register, 0, _register.Length);
267 |
268 | _prgBank[0] = 0 * 0x2000;
269 | _prgBank[1] = 1 * 0x2000;
270 | _prgBank[2] = (uint)((PrgBanks * 2 - 2) * 0x2000);
271 | _prgBank[3] = (uint)((PrgBanks * 2 - 1) * 0x2000);
272 | }
273 |
274 | public override bool IRQState()
275 | {
276 | return _irqActive;
277 | }
278 |
279 | public override void IRQClear()
280 | {
281 | _irqActive = false;
282 | }
283 |
284 | public override void ScanLine()
285 | {
286 | if (_irqCounter == 0)
287 | {
288 | _irqCounter = _irqReload;
289 | }
290 | else
291 | {
292 | _irqCounter--;
293 | }
294 |
295 | if (_irqCounter == 0 && _irqEnable)
296 | {
297 | _irqActive = true;
298 | }
299 | }
300 |
301 | public override MirrorMode Mirror()
302 | {
303 | return _mirrorMode;
304 | }
305 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # Tye
66 | .tye/
67 |
68 | # ASP.NET Scaffolding
69 | ScaffoldingReadMe.txt
70 |
71 | # StyleCop
72 | StyleCopReport.xml
73 |
74 | # Files built by Visual Studio
75 | *_i.c
76 | *_p.c
77 | *_h.h
78 | *.ilk
79 | *.meta
80 | *.obj
81 | *.iobj
82 | *.pch
83 | *.pdb
84 | *.ipdb
85 | *.pgc
86 | *.pgd
87 | *.rsp
88 | *.sbr
89 | *.tlb
90 | *.tli
91 | *.tlh
92 | *.tmp
93 | *.tmp_proj
94 | *_wpftmp.csproj
95 | *.log
96 | *.tlog
97 | *.vspscc
98 | *.vssscc
99 | .builds
100 | *.pidb
101 | *.svclog
102 | *.scc
103 |
104 | # Chutzpah Test files
105 | _Chutzpah*
106 |
107 | # Visual C++ cache files
108 | ipch/
109 | *.aps
110 | *.ncb
111 | *.opendb
112 | *.opensdf
113 | *.sdf
114 | *.cachefile
115 | *.VC.db
116 | *.VC.VC.opendb
117 |
118 | # Visual Studio profiler
119 | *.psess
120 | *.vsp
121 | *.vspx
122 | *.sap
123 |
124 | # Visual Studio Trace Files
125 | *.e2e
126 |
127 | # TFS 2012 Local Workspace
128 | $tf/
129 |
130 | # Guidance Automation Toolkit
131 | *.gpState
132 |
133 | # ReSharper is a .NET coding add-in
134 | _ReSharper*/
135 | *.[Rr]e[Ss]harper
136 | *.DotSettings.user
137 |
138 | # TeamCity is a build add-in
139 | _TeamCity*
140 |
141 | # DotCover is a Code Coverage Tool
142 | *.dotCover
143 |
144 | # AxoCover is a Code Coverage Tool
145 | .axoCover/*
146 | !.axoCover/settings.json
147 |
148 | # Coverlet is a free, cross platform Code Coverage Tool
149 | coverage*.json
150 | coverage*.xml
151 | coverage*.info
152 |
153 | # Visual Studio code coverage results
154 | *.coverage
155 | *.coveragexml
156 |
157 | # NCrunch
158 | _NCrunch_*
159 | .*crunch*.local.xml
160 | nCrunchTemp_*
161 |
162 | # MightyMoose
163 | *.mm.*
164 | AutoTest.Net/
165 |
166 | # Web workbench (sass)
167 | .sass-cache/
168 |
169 | # Installshield output folder
170 | [Ee]xpress/
171 |
172 | # DocProject is a documentation generator add-in
173 | DocProject/buildhelp/
174 | DocProject/Help/*.HxT
175 | DocProject/Help/*.HxC
176 | DocProject/Help/*.hhc
177 | DocProject/Help/*.hhk
178 | DocProject/Help/*.hhp
179 | DocProject/Help/Html2
180 | DocProject/Help/html
181 |
182 | # Click-Once directory
183 | publish/
184 |
185 | # Publish Web Output
186 | *.[Pp]ublish.xml
187 | *.azurePubxml
188 | # Note: Comment the next line if you want to checkin your web deploy settings,
189 | # but database connection strings (with potential passwords) will be unencrypted
190 | *.pubxml
191 | *.publishproj
192 |
193 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
194 | # checkin your Azure Web App publish settings, but sensitive information contained
195 | # in these scripts will be unencrypted
196 | PublishScripts/
197 |
198 | # NuGet Packages
199 | *.nupkg
200 | # NuGet Symbol Packages
201 | *.snupkg
202 | # The packages folder can be ignored because of Package Restore
203 | **/[Pp]ackages/*
204 | # except build/, which is used as an MSBuild target.
205 | !**/[Pp]ackages/build/
206 | # Uncomment if necessary however generally it will be regenerated when needed
207 | #!**/[Pp]ackages/repositories.config
208 | # NuGet v3's project.json files produces more ignorable files
209 | *.nuget.props
210 | *.nuget.targets
211 |
212 | # Microsoft Azure Build Output
213 | csx/
214 | *.build.csdef
215 |
216 | # Microsoft Azure Emulator
217 | ecf/
218 | rcf/
219 |
220 | # Windows Store app package directories and files
221 | AppPackages/
222 | BundleArtifacts/
223 | Package.StoreAssociation.xml
224 | _pkginfo.txt
225 | *.appx
226 | *.appxbundle
227 | *.appxupload
228 |
229 | # Visual Studio cache files
230 | # files ending in .cache can be ignored
231 | *.[Cc]ache
232 | # but keep track of directories ending in .cache
233 | !?*.[Cc]ache/
234 |
235 | # Others
236 | ClientBin/
237 | ~$*
238 | *~
239 | *.dbmdl
240 | *.dbproj.schemaview
241 | *.jfm
242 | *.pfx
243 | *.publishsettings
244 | orleans.codegen.cs
245 |
246 | # Including strong name files can present a security risk
247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
248 | #*.snk
249 |
250 | # Since there are multiple workflows, uncomment next line to ignore bower_components
251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
252 | #bower_components/
253 |
254 | # RIA/Silverlight projects
255 | Generated_Code/
256 |
257 | # Backup & report files from converting an old project file
258 | # to a newer Visual Studio version. Backup files are not needed,
259 | # because we have git ;-)
260 | _UpgradeReport_Files/
261 | Backup*/
262 | UpgradeLog*.XML
263 | UpgradeLog*.htm
264 | ServiceFabricBackup/
265 | *.rptproj.bak
266 |
267 | # SQL Server files
268 | *.mdf
269 | *.ldf
270 | *.ndf
271 |
272 | # Business Intelligence projects
273 | *.rdl.data
274 | *.bim.layout
275 | *.bim_*.settings
276 | *.rptproj.rsuser
277 | *- [Bb]ackup.rdl
278 | *- [Bb]ackup ([0-9]).rdl
279 | *- [Bb]ackup ([0-9][0-9]).rdl
280 |
281 | # Microsoft Fakes
282 | FakesAssemblies/
283 |
284 | # GhostDoc plugin setting file
285 | *.GhostDoc.xml
286 |
287 | # Node.js Tools for Visual Studio
288 | .ntvs_analysis.dat
289 | node_modules/
290 |
291 | # Visual Studio 6 build log
292 | *.plg
293 |
294 | # Visual Studio 6 workspace options file
295 | *.opt
296 |
297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
298 | *.vbw
299 |
300 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
301 | *.vbp
302 |
303 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
304 | *.dsw
305 | *.dsp
306 |
307 | # Visual Studio 6 technical files
308 | *.ncb
309 | *.aps
310 |
311 | # Visual Studio LightSwitch build output
312 | **/*.HTMLClient/GeneratedArtifacts
313 | **/*.DesktopClient/GeneratedArtifacts
314 | **/*.DesktopClient/ModelManifest.xml
315 | **/*.Server/GeneratedArtifacts
316 | **/*.Server/ModelManifest.xml
317 | _Pvt_Extensions
318 |
319 | # Paket dependency manager
320 | .paket/paket.exe
321 | paket-files/
322 |
323 | # FAKE - F# Make
324 | .fake/
325 |
326 | # CodeRush personal settings
327 | .cr/personal
328 |
329 | # Python Tools for Visual Studio (PTVS)
330 | __pycache__/
331 | *.pyc
332 |
333 | # Cake - Uncomment if you are using it
334 | # tools/**
335 | # !tools/packages.config
336 |
337 | # Tabs Studio
338 | *.tss
339 |
340 | # Telerik's JustMock configuration file
341 | *.jmconfig
342 |
343 | # BizTalk build output
344 | *.btp.cs
345 | *.btm.cs
346 | *.odx.cs
347 | *.xsd.cs
348 |
349 | # OpenCover UI analysis results
350 | OpenCover/
351 |
352 | # Azure Stream Analytics local run output
353 | ASALocalRun/
354 |
355 | # MSBuild Binary and Structured Log
356 | *.binlog
357 |
358 | # NVidia Nsight GPU debugger configuration file
359 | *.nvuser
360 |
361 | # MFractors (Xamarin productivity tool) working folder
362 | .mfractor/
363 |
364 | # Local History for Visual Studio
365 | .localhistory/
366 |
367 | # Visual Studio History (VSHistory) files
368 | .vshistory/
369 |
370 | # BeatPulse healthcheck temp database
371 | healthchecksdb
372 |
373 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
374 | MigrationBackup/
375 |
376 | # Ionide (cross platform F# VS Code tools) working folder
377 | .ionide/
378 |
379 | # Fody - auto-generated XML schema
380 | FodyWeavers.xsd
381 |
382 | # VS Code files for those working on multiple tools
383 | .vscode/*
384 | !.vscode/settings.json
385 | !.vscode/tasks.json
386 | !.vscode/launch.json
387 | !.vscode/extensions.json
388 | *.code-workspace
389 |
390 | # Local History for Visual Studio Code
391 | .history/
392 |
393 | # Windows Installer files from build outputs
394 | *.cab
395 | *.msi
396 | *.msix
397 | *.msm
398 | *.msp
399 |
400 | # JetBrains Rider
401 | *.sln.iml
402 |
403 | ##
404 | ## Visual studio for Mac
405 | ##
406 |
407 |
408 | # globs
409 | Makefile.in
410 | *.userprefs
411 | *.usertasks
412 | config.make
413 | config.status
414 | aclocal.m4
415 | install-sh
416 | autom4te.cache/
417 | *.tar.gz
418 | tarballs/
419 | test-results/
420 |
421 | # Mac bundle stuff
422 | *.dmg
423 | *.app
424 |
425 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
426 | # General
427 | .DS_Store
428 | .AppleDouble
429 | .LSOverride
430 |
431 | # Icon must end with two \r
432 | Icon
433 |
434 |
435 | # Thumbnails
436 | ._*
437 |
438 | # Files that might appear in the root of a volume
439 | .DocumentRevisions-V100
440 | .fseventsd
441 | .Spotlight-V100
442 | .TemporaryItems
443 | .Trashes
444 | .VolumeIcon.icns
445 | .com.apple.timemachine.donotpresent
446 |
447 | # Directories potentially created on remote AFP share
448 | .AppleDB
449 | .AppleDesktop
450 | Network Trash Folder
451 | Temporary Items
452 | .apdisk
453 |
454 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
455 | # Windows thumbnail cache files
456 | Thumbs.db
457 | ehthumbs.db
458 | ehthumbs_vista.db
459 |
460 | # Dump file
461 | *.stackdump
462 |
463 | # Folder config file
464 | [Dd]esktop.ini
465 |
466 | # Recycle Bin used on file shares
467 | $RECYCLE.BIN/
468 |
469 | # Windows Installer files
470 | *.cab
471 | *.msi
472 | *.msix
473 | *.msm
474 | *.msp
475 |
476 | # Windows shortcuts
477 | *.lnk
478 |
--------------------------------------------------------------------------------
/NesSharp.WinForms/EmulatorWindow.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing.Imaging;
2 | using NesSharp.Core;
3 | using NesSharp.Core.Sound;
4 | using NesSharp.WinForms.Sound;
5 |
6 | namespace NesSharp.WinForms
7 | {
8 | public partial class EmulatorWindow : Form
9 | {
10 | private Thread? _emulationThread;
11 | private CancellationTokenSource _emulatorCancellationTokenSource = new();
12 | private CancellationToken _cancellationToken;
13 |
14 | private Bus? _nesSystem;
15 |
16 | private readonly Dictionary _playerOne = new();
17 |
18 | private readonly Bitmap[] _buffer = new Bitmap[2];
19 | private int _bufferIndex;
20 | private static readonly Rectangle Rect = new(0, 0, 256, 240);
21 |
22 | private readonly ISoundDriver _soundDriver = new NAudioSoundDriver();
23 |
24 | private DebugWindow? _debugWindow;
25 |
26 | public EmulatorWindow()
27 | {
28 | InitializeComponent();
29 |
30 | _buffer[0] = new Bitmap(256, 240, PixelFormat.Format32bppArgb);
31 | _buffer[1] = new Bitmap(256, 240, PixelFormat.Format32bppArgb);
32 | }
33 |
34 | private void EmulatorWindow_Load(object sender, EventArgs e)
35 | {
36 | _nesSystem = new Bus();
37 |
38 | InitializeInput();
39 | InitializeSound();
40 | }
41 |
42 | private void EmulatorWindow_Closing(object sender, FormClosingEventArgs e)
43 | {
44 | _emulatorCancellationTokenSource.Cancel();
45 | }
46 |
47 | private void exitToolStripMenuItem_Click(object sender, EventArgs e)
48 | {
49 | Close();
50 | }
51 |
52 | private void showDebugWindowToolStripMenuItem_Click(object sender, EventArgs e)
53 | {
54 | if (_debugWindow is not null)
55 | {
56 | _debugWindow.Focus();
57 | return;
58 | }
59 |
60 | _debugWindow = new DebugWindow();
61 | _debugWindow.FormClosed += OnDebugWindowClosed;
62 | _debugWindow.Show();
63 | _debugWindow.SetPpu(_nesSystem!.Ppu);
64 | }
65 |
66 | private void OnDebugWindowClosed(object? sender, FormClosedEventArgs e)
67 | {
68 | _debugWindow?.Dispose();
69 | _debugWindow = null;
70 | }
71 |
72 | #region Emulation
73 |
74 | private async void OnLoadRomClicked(object sender, EventArgs e)
75 | {
76 | StopEmulator();
77 |
78 | using var openFileDialog = new OpenFileDialog();
79 |
80 | openFileDialog.Filter = @"NES ROM files (*.nes)|*.nes|All files (*.*)|*.*";
81 | openFileDialog.FilterIndex = 1;
82 | openFileDialog.RestoreDirectory = true;
83 | openFileDialog.AutoUpgradeEnabled = true;
84 | openFileDialog.CheckFileExists = true;
85 |
86 | if (openFileDialog.ShowDialog() != DialogResult.OK)
87 | {
88 | return;
89 | }
90 |
91 | var fileName = openFileDialog.FileName;
92 | if (string.IsNullOrEmpty(fileName))
93 | {
94 | return;
95 | }
96 |
97 | var cartridge = await Cartridge.FromFile(fileName);
98 | StartEmulator(cartridge);
99 | }
100 |
101 | private void StartEmulator(Cartridge cartridge)
102 | {
103 | _nesSystem!.InsertCartridge(cartridge);
104 |
105 | _nesSystem.Reset();
106 | _emulatorCancellationTokenSource = new();
107 |
108 | var cancelToken = _emulatorCancellationTokenSource.Token;
109 | var emulatorState = new EmulatorState(_nesSystem, cancelToken);
110 |
111 | _emulationThread = new Thread(UpdateGame);
112 | _emulationThread.Start(emulatorState);
113 |
114 | _cancellationToken = cancelToken;
115 | }
116 |
117 | private record EmulatorState(Bus NesSystem, CancellationToken CancellationToken = default);
118 |
119 | private void UpdateGame(object? obj)
120 | {
121 | if (obj is not EmulatorState emulatorState)
122 | {
123 | return;
124 | }
125 |
126 | while (!emulatorState.CancellationToken.IsCancellationRequested)
127 | {
128 | RunEmulator(emulatorState);
129 | }
130 | }
131 |
132 | private void RunEmulator(EmulatorState emulatorState)
133 | {
134 | emulatorState.NesSystem.SetControllerState(0, _playerOne[P1KeyUp], _playerOne[P1KeyDown], _playerOne[P1KeyLeft], _playerOne[P1KeyRight], _playerOne[P1KeyStart], _playerOne[P1KeySelect], _playerOne[P1KeyA], _playerOne[P1KeyB]);
135 |
136 | if (emulatorState.CancellationToken.IsCancellationRequested)
137 | {
138 | return;
139 | }
140 |
141 | var ppu = emulatorState.NesSystem.Ppu;
142 | if (!ppu.FrameComplete)
143 | {
144 | return;
145 | }
146 |
147 | UpdateEmulatorOutputAndDebugWindow(ppu);
148 | }
149 |
150 | private void UpdateEmulatorOutputAndDebugWindow(Ppu ppu)
151 | {
152 | ppu.FrameComplete = false;
153 |
154 | // Draw the screen
155 | BitmapData? bitmapData = null;
156 | var bitmap = _buffer[_bufferIndex];
157 |
158 | try
159 | {
160 | bitmapData = bitmap.LockBits(Rect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
161 | unsafe
162 | {
163 | var dstPointer = (byte*)bitmapData.Scan0.ToPointer();
164 | var src = ppu.Screen;
165 |
166 | for (var y = 0; y < 240; y++)
167 | {
168 | for (var x = 0; x < 256; x++)
169 | {
170 | var pixel = src[y * 256 + x];
171 | var offset = (y * bitmapData.Stride) + (x * 4);
172 |
173 | dstPointer[offset + 0] = pixel.B;
174 | dstPointer[offset + 1] = pixel.G;
175 | dstPointer[offset + 2] = pixel.R;
176 | dstPointer[offset + 3] = 255;
177 | }
178 | }
179 | }
180 | }
181 | finally
182 | {
183 | if (bitmapData is not null)
184 | {
185 | bitmap.UnlockBits(bitmapData);
186 | }
187 |
188 | try
189 | {
190 | emulatorOutput.Invoke(() => { emulatorOutput.Image = bitmap; });
191 | }
192 | catch
193 | {
194 | // ignored
195 | }
196 |
197 | // Swap buffers
198 | _bufferIndex++;
199 | _bufferIndex %= 2;
200 | }
201 |
202 | _debugWindow?.DebugUpdate();
203 | }
204 |
205 | private void StopEmulator()
206 | {
207 | _nesSystem!.Stop();
208 | _emulatorCancellationTokenSource.Cancel();
209 | }
210 |
211 | #endregion
212 |
213 | #region Input
214 |
215 | // Player 1 Inputs
216 | public Keys P1KeyUp { get; set; }
217 | public Keys P1KeyDown { get; set; }
218 | public Keys P1KeyLeft { get; set; }
219 | public Keys P1KeyRight { get; set; }
220 | public Keys P1KeyStart { get; set; }
221 | public Keys P1KeySelect { get; set; }
222 | public Keys P1KeyA { get; set; }
223 | public Keys P1KeyB { get; set; }
224 |
225 | private void InitializeInput()
226 | {
227 | P1KeyUp = Keys.W;
228 | P1KeyDown = Keys.S;
229 | P1KeyLeft = Keys.A;
230 | P1KeyRight = Keys.D;
231 | P1KeyStart = Keys.Enter;
232 | P1KeySelect = Keys.Back;
233 | P1KeyA = Keys.O;
234 | P1KeyB = Keys.K;
235 |
236 | _playerOne.Add(P1KeyUp, false);
237 | _playerOne.Add(P1KeyDown, false);
238 | _playerOne.Add(P1KeyLeft, false);
239 | _playerOne.Add(P1KeyRight, false);
240 | _playerOne.Add(P1KeyStart, false);
241 | _playerOne.Add(P1KeySelect, false);
242 | _playerOne.Add(P1KeyA, false);
243 | _playerOne.Add(P1KeyB, false);
244 | }
245 |
246 | private void OnKeyDown(object sender, KeyEventArgs e)
247 | {
248 | if (_playerOne.ContainsKey(e.KeyCode))
249 | {
250 | _playerOne[e.KeyCode] = true;
251 | }
252 | }
253 |
254 | private void OnKeyUp(object sender, KeyEventArgs e)
255 | {
256 | if (_playerOne.ContainsKey(e.KeyCode))
257 | {
258 | _playerOne[e.KeyCode] = false;
259 | }
260 | }
261 |
262 | #endregion
263 |
264 | #region Sound
265 |
266 | private void InitializeSound()
267 | {
268 | _nesSystem!.SetSampleFrequency(44100);
269 |
270 | _soundDriver.InitializeAudio(44100, 1, 16, 1024);
271 | _soundDriver.SetSoundOutMethod(SoundOut);
272 | }
273 |
274 | private float SoundOut(uint channel, float globalTime, float timeStep)
275 | {
276 | if (_nesSystem is null || !_nesSystem.IsRunning)
277 | {
278 | return 0f;
279 | }
280 |
281 | if (channel == 0)
282 | {
283 | while (!_nesSystem.Clock() && !_cancellationToken.IsCancellationRequested)
284 | {
285 | // keep emulating until we have a sample to play. This is to keep the timing of the sound accurate.
286 | }
287 |
288 | return (float)_nesSystem.AudioSample;
289 | }
290 |
291 | return 0f;
292 | }
293 |
294 | private void DisposeSoundOutput()
295 | {
296 | _soundDriver.Dispose();
297 | }
298 |
299 | #endregion
300 |
301 | private void DisposeBuffers()
302 | {
303 | StopEmulator();
304 |
305 | _buffer[0].Dispose();
306 | _buffer[1].Dispose();
307 | }
308 | }
309 | }
--------------------------------------------------------------------------------
/NesSharp.Core/Apu.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core;
2 |
3 | ///
4 | /// Audio Processing Unit
5 | ///
6 | public sealed class Apu
7 | {
8 | private uint _clockCounter;
9 | private uint _frameClockCounter;
10 | private double _globalTime;
11 |
12 | private bool _useRawMode = false;
13 |
14 | private bool _pulse1Enable;
15 | private bool _pulse1Halt;
16 | private double _pulse1Sample;
17 | private double _pulse1Output;
18 | private Sequencer _pulse1Sequencer = new();
19 | private OscillatorPulse _pulse1Oscillator = new();
20 | private Envelope _pulse1Envelope = new();
21 | private LengthCounter _pulse1LengthCounter = new();
22 | private Sweeper _pulse1Sweep = new();
23 |
24 | private bool _pulse2Enable;
25 | private bool _pulse2Halt;
26 | private double _pulse2Sample;
27 | private double _pulse2Output;
28 | private Sequencer _pulse2Sequencer = new();
29 | private OscillatorPulse _pulse2Oscillator = new();
30 | private Envelope _pulse2Envelope = new();
31 | private LengthCounter _pulse2LengthCounter = new();
32 | private Sweeper _pulse2Sweep = new();
33 |
34 | private bool _noiseEnable;
35 | private bool _noiseHalt;
36 | private double _noiseSample;
37 | private double _noiseOutput;
38 | private Sequencer _noiseSequencer = new()
39 | {
40 | Sequence = 0xDBDB
41 | };
42 | private Envelope _noiseEnvelope = new();
43 | private LengthCounter _noiseLengthCounter = new();
44 |
45 | private static readonly byte[] LengthTable =
46 | {
47 | 10, 254, 20, 2, 40, 4, 80, 6,
48 | 160, 8, 60, 10, 14, 12, 26, 14,
49 | 12, 16, 24, 18, 48, 20, 96, 22,
50 | 192, 24, 72, 26, 16, 28, 32, 30
51 | };
52 |
53 | public void CpuWrite(ushort address, byte data)
54 | {
55 | switch (address)
56 | {
57 | // Pulse Channel 1
58 | case 0x4000:
59 | switch ((data & 0xC0) >>> 6)
60 | {
61 | case 0x00:
62 | _pulse1Sequencer.NewSequence = 0b00000001;
63 | _pulse1Oscillator.DutyCycle = 0.125;
64 | break;
65 |
66 | case 0x01:
67 | _pulse1Sequencer.NewSequence = 0b00000011;
68 | _pulse1Oscillator.DutyCycle = 0.25;
69 | break;
70 |
71 | case 0x02:
72 | _pulse1Sequencer.NewSequence = 0b00001111;
73 | _pulse1Oscillator.DutyCycle = 0.5;
74 | break;
75 |
76 | case 0x03:
77 | _pulse1Sequencer.NewSequence = 0b11111100;
78 | _pulse1Oscillator.DutyCycle = 0.75;
79 | break;
80 | }
81 | _pulse1Sequencer.Sequence = _pulse1Sequencer.NewSequence;
82 | _pulse1Halt = (data & 0x20) != 0;
83 | _pulse1Envelope.Volume = (ushort)(data & 0x0F);
84 | _pulse1Envelope.Disable = (data & 0x10) != 0;
85 | break;
86 |
87 | case 0x4001:
88 | _pulse1Sweep.Enabled = (data & 0x80) != 0;
89 | _pulse1Sweep.Period = (byte)((data & 0x70) >>> 4);
90 | _pulse1Sweep.Down = (data & 0x08) != 0;
91 | _pulse1Sweep.Shift = (byte)(data & 0x07);
92 | _pulse1Sweep.Reload = true;
93 | break;
94 |
95 | case 0x4002:
96 | _pulse1Sequencer.Reload = (ushort)((_pulse1Sequencer.Reload & 0xFF00) | data);
97 | break;
98 |
99 | case 0x4003:
100 | _pulse1Sequencer.Reload = (ushort)((_pulse1Sequencer.Reload & 0x00FF) | ((data & 0x07) << 8));
101 | _pulse1Sequencer.Timer = _pulse1Sequencer.Reload;
102 | _pulse1Sequencer.Sequence = _pulse1Sequencer.NewSequence;
103 | _pulse1LengthCounter.Counter = LengthTable[(data & 0xF8) >>> 3];
104 | _pulse1Envelope.Start = true;
105 | break;
106 |
107 | // Pulse Channel 2
108 | case 0x4004:
109 | switch ((data & 0xC0) >>> 6)
110 | {
111 | case 0x00:
112 | _pulse2Sequencer.NewSequence = 0b00000001;
113 | _pulse2Oscillator.DutyCycle = 0.125;
114 | break;
115 |
116 | case 0x01:
117 | _pulse2Sequencer.NewSequence = 0b00000011;
118 | _pulse2Oscillator.DutyCycle = 0.25;
119 | break;
120 |
121 | case 0x02:
122 | _pulse2Sequencer.NewSequence = 0b00001111;
123 | _pulse2Oscillator.DutyCycle = 0.5;
124 | break;
125 |
126 | case 0x03:
127 | _pulse2Sequencer.NewSequence = 0b11111100;
128 | _pulse2Oscillator.DutyCycle = 0.75;
129 | break;
130 | }
131 | _pulse2Sequencer.Sequence = _pulse2Sequencer.NewSequence;
132 | _pulse2Halt = (data & 0x20) != 0;
133 | _pulse2Envelope.Volume = (ushort)(data & 0x0F);
134 | _pulse2Envelope.Disable = (data & 0x10) != 0;
135 | break;
136 |
137 | case 0x4005:
138 | _pulse2Sweep.Enabled = (data & 0x80) != 0;
139 | _pulse2Sweep.Period = (byte)((data & 0x70) >>> 4);
140 | _pulse2Sweep.Down = (data & 0x08) != 0;
141 | _pulse2Sweep.Shift = (byte)(data & 0x07);
142 | _pulse2Sweep.Reload = true;
143 | break;
144 |
145 | case 0x4006:
146 | _pulse2Sequencer.Reload = (ushort)((_pulse2Sequencer.Reload & 0xFF00) | data);
147 | break;
148 |
149 | case 0x4007:
150 | _pulse2Sequencer.Reload = (ushort)((_pulse2Sequencer.Reload & 0x00FF) | ((data & 0x07) << 8));
151 | _pulse2Sequencer.Timer = _pulse2Sequencer.Reload;
152 | _pulse2Sequencer.Sequence = _pulse2Sequencer.NewSequence;
153 | _pulse2LengthCounter.Counter = LengthTable[(data & 0xF8) >>> 3];
154 | _pulse2Envelope.Start = true;
155 | break;
156 |
157 | // Triangle Channel
158 | case 0x4008:
159 | break;
160 |
161 | case 0x4009:
162 | break;
163 |
164 | case 0x400A:
165 | break;
166 |
167 | case 0x400B:
168 | break;
169 |
170 | // Noise Channel
171 | case 0x400C:
172 | _noiseEnvelope.Volume = (ushort)(data & 0x0F);
173 | _noiseEnvelope.Disable = (data & 0x10) != 0;
174 | _noiseHalt = (data & 0x20) != 0;
175 | break;
176 |
177 | case 0x400D:
178 | break;
179 |
180 | case 0x400E:
181 | switch (data & 0x0F)
182 | {
183 | case 0x00: _noiseSequencer.Reload = 0; break;
184 | case 0x01: _noiseSequencer.Reload = 4; break;
185 | case 0x02: _noiseSequencer.Reload = 8; break;
186 | case 0x03: _noiseSequencer.Reload = 16; break;
187 | case 0x04: _noiseSequencer.Reload = 32; break;
188 | case 0x05: _noiseSequencer.Reload = 64; break;
189 | case 0x06: _noiseSequencer.Reload = 96; break;
190 | case 0x07: _noiseSequencer.Reload = 128; break;
191 | case 0x08: _noiseSequencer.Reload = 160; break;
192 | case 0x09: _noiseSequencer.Reload = 202; break;
193 | case 0x0A: _noiseSequencer.Reload = 254; break;
194 | case 0x0B: _noiseSequencer.Reload = 380; break;
195 | case 0x0C: _noiseSequencer.Reload = 508; break;
196 | case 0x0D: _noiseSequencer.Reload = 1016; break;
197 | case 0x0E: _noiseSequencer.Reload = 2034; break;
198 | case 0x0F: _noiseSequencer.Reload = 4068; break;
199 | }
200 | break;
201 |
202 | case 0x400F:
203 | _pulse1Envelope.Start = true;
204 | _pulse2Envelope.Start = true;
205 | _noiseEnvelope.Start = true;
206 | _noiseLengthCounter.Counter = LengthTable[(data & 0xF8) >>> 3];
207 | break;
208 |
209 | // DMC Channel
210 | case 0x4010:
211 | break;
212 |
213 | case 0x4011:
214 | break;
215 |
216 | case 0x4012:
217 | break;
218 |
219 | case 0x4013:
220 | break;
221 |
222 | // Control
223 | case 0x4015:
224 | _pulse1Enable = (data & 0x01) != 0;
225 | _pulse2Enable = (data & 0x02) != 0;
226 | _noiseEnable = (data & 0x04) != 0;
227 | break;
228 |
229 | // Frame Counter
230 | case 0x4017:
231 | break;
232 | }
233 | }
234 |
235 | public byte CpuRead(ushort address)
236 | {
237 | return 0;
238 | }
239 |
240 | public void Clock()
241 | {
242 | var quarterFrameClock = false;
243 | var halfFrameClock = false;
244 |
245 | _globalTime += (0.3333333333 / 1789773);
246 |
247 | if (_clockCounter % 6 == 0)
248 | {
249 | _frameClockCounter++;
250 |
251 | if (_frameClockCounter == 3729)
252 | {
253 | quarterFrameClock = true;
254 | }
255 |
256 | if (_frameClockCounter == 7457)
257 | {
258 | quarterFrameClock = true;
259 | halfFrameClock = true;
260 | }
261 |
262 | if (_frameClockCounter == 11186)
263 | {
264 | quarterFrameClock = true;
265 | }
266 |
267 | if (_frameClockCounter == 14916)
268 | {
269 | quarterFrameClock = true;
270 | halfFrameClock = true;
271 | _frameClockCounter = 0;
272 | }
273 |
274 | if (quarterFrameClock)
275 | {
276 | // volume envelope
277 | _pulse1Envelope.Clock(_pulse1Halt);
278 | _pulse2Envelope.Clock(_pulse2Halt);
279 | _noiseEnvelope.Clock(_noiseHalt);
280 | }
281 |
282 | if (halfFrameClock)
283 | {
284 | // frequency sweep and note length
285 | _pulse1LengthCounter.Clock(_pulse1Enable, _pulse1Halt);
286 | _pulse2LengthCounter.Clock(_pulse2Enable, _pulse2Halt);
287 | _noiseLengthCounter.Clock(_noiseEnable, _noiseHalt);
288 | _pulse1Sweep.Clock(ref _pulse1Sequencer.Reload, 0);
289 | _pulse2Sweep.Clock(ref _pulse2Sequencer.Reload, 1);
290 | }
291 |
292 | // if (bUseRawMode)
293 | {
294 | // Update Pulse1 Channel ================================
295 | _pulse1Sequencer.Clock(_pulse1Enable, s => ((s & 0x0001) << 7) | ((s & 0x00FE) >>> 1));
296 | // _pulse1Sample = (double)_pulse1Sequencer.output;
297 | }
298 | //else
299 | {
300 | _pulse1Oscillator.Frequency = 1789773.0 / (16.0 * (double)(_pulse1Sequencer.Reload + 1));
301 | _pulse1Oscillator.Amplitude = (double)(_pulse1Envelope.Output - 1) / 16.0;
302 | _pulse1Sample = _pulse1Oscillator.Sample(_globalTime);
303 |
304 | if (_pulse1LengthCounter.Counter > 0 && _pulse1Sequencer.Timer >= 8 && !_pulse1Sweep.Mute && _pulse1Envelope.Output > 2)
305 | _pulse1Output += (_pulse1Sample - _pulse1Output) * 0.5;
306 | else
307 | _pulse1Output = 0;
308 | }
309 |
310 | //if (bUseRawMode)
311 | {
312 | // Update Pulse1 Channel ================================
313 | _pulse2Sequencer.Clock(_pulse2Enable, s => ((s & 0x0001) << 7) | ((s & 0x00FE) >> 1));
314 | // _pulse2Sample = (double)_pulse2Sequencer.output;
315 |
316 | }
317 | // else
318 | {
319 | _pulse2Oscillator.Frequency = 1789773.0 / (16.0 * (double)(_pulse2Sequencer.Reload + 1));
320 | _pulse2Oscillator.Amplitude = (double)(_pulse2Envelope.Output - 1) / 16.0;
321 | _pulse2Sample = _pulse2Oscillator.Sample(_globalTime);
322 |
323 | if (_pulse2LengthCounter.Counter > 0 && _pulse2Sequencer.Timer >= 8 && !_pulse2Sweep.Mute && _pulse2Envelope.Output > 2)
324 | _pulse2Output += (_pulse2Sample - _pulse2Output) * 0.5;
325 | else
326 | _pulse2Output = 0;
327 | }
328 |
329 |
330 | _noiseSequencer.Clock(_noiseEnable, s => (((s & 0x0001) ^ ((s & 0x0002) >> 1)) << 14) | ((s & 0x7FFF) >>> 1));
331 |
332 | if (_noiseLengthCounter.Counter > 0 && _noiseSequencer.Timer >= 8)
333 | {
334 | _noiseOutput = (double)_noiseSequencer.Output * ((double)(_noiseEnvelope.Output - 1) / 16.0);
335 | }
336 |
337 | if (!_pulse1Enable)
338 | {
339 | _pulse1Output = 0;
340 | }
341 |
342 | if (!_pulse2Enable)
343 | {
344 | _pulse2Output = 0;
345 | }
346 |
347 | if (!_noiseEnable)
348 | {
349 | _noiseOutput = 0;
350 | }
351 | }
352 |
353 | // Frequency sweepers change at high frequency
354 | _pulse1Sweep.Track(_pulse1Sequencer.Reload);
355 | _pulse2Sweep.Track(_pulse2Sequencer.Reload);
356 |
357 | _clockCounter++;
358 | }
359 |
360 | public void Reset()
361 | {
362 |
363 | }
364 |
365 | public double GetOutputSample()
366 | {
367 | if (_useRawMode)
368 | {
369 | return (_pulse1Sample - 0.5) * 0.5
370 | + (_pulse2Sample - 0.5) * 0.5;
371 | }
372 | else
373 | {
374 | return ((1.0 * _pulse1Output) - 0.8) * 0.1 +
375 | ((1.0 * _pulse2Output) - 0.8) * 0.1 +
376 | ((2.0 * (_noiseOutput - 0.5))) * 0.1;
377 | }
378 | }
379 |
380 | private struct Sequencer
381 | {
382 | public uint Sequence { get; set; }
383 | public uint NewSequence { get; set; }
384 | public ushort Timer { get; set; }
385 | public ushort Reload;
386 |
387 | public byte Output { get; private set; }
388 |
389 | public byte Clock(bool enable, Func manipulator)
390 | {
391 | if (enable)
392 | {
393 | Timer--;
394 | if (Timer == 0xFFFF)
395 | {
396 | Timer = (ushort)(Reload + 1);
397 | Sequence = manipulator(Sequence);
398 | Output = (byte)(Sequence & 1);
399 | }
400 | }
401 |
402 | return Output;
403 | }
404 | }
405 |
406 | private struct LengthCounter
407 | {
408 | public byte Counter { get; set; }
409 |
410 | public byte Clock(bool enable, bool halt)
411 | {
412 | if (!enable)
413 | {
414 | Counter = 0;
415 | }
416 | else if (Counter > 0 && !halt)
417 | {
418 | Counter--;
419 | }
420 |
421 | return Counter;
422 | }
423 | }
424 |
425 | private struct Envelope
426 | {
427 | public Envelope()
428 | {
429 |
430 | }
431 |
432 | public bool Start { get; set; } = false;
433 | public bool Disable { get; set; } = false;
434 | public ushort DividerCount { get; set; } = 0;
435 | public ushort Volume { get; set; } = 0;
436 | public ushort Output { get; set; } = 0;
437 | public ushort DecayCount { get; set; } = 0;
438 |
439 | public void Clock(bool loop)
440 | {
441 | if (!Start)
442 | {
443 | if (DividerCount == 0)
444 | {
445 | DividerCount = Volume;
446 | if (DecayCount == 0)
447 | {
448 | if (loop)
449 | {
450 | DecayCount = 15;
451 | }
452 | }
453 | else
454 | {
455 | DecayCount--;
456 | }
457 | }
458 | else
459 | {
460 | DividerCount--;
461 | }
462 | }
463 | else
464 | {
465 | Start = false;
466 | DecayCount = 15;
467 | DividerCount = Volume;
468 | }
469 |
470 | Output = Disable ? Volume : DecayCount;
471 | }
472 | }
473 |
474 | private struct OscillatorPulse
475 | {
476 | public OscillatorPulse()
477 | {
478 |
479 | }
480 |
481 | public double Frequency { get; set; } = 0;
482 | public double DutyCycle { get; set; } = 0;
483 | public double Amplitude { get; set; } = 1;
484 | public double Pi { get; set; } = 3.14159;
485 | public double Harmonics { get; set; } = 20;
486 |
487 | public double Sample(double t)
488 | {
489 | var a = 0.0;
490 | var b = 0.0;
491 | var p = DutyCycle * 2.0 * Pi;
492 |
493 | for (double n = 1; n < Harmonics; n++)
494 | {
495 | var c = n * Frequency * 2.0 * Pi * t;
496 | a += -ApproxSin(c) / n;
497 | b += -ApproxSin(c - p * n) / n;
498 | }
499 |
500 | return (a - b) * (2.0 * Amplitude / Pi);
501 | }
502 |
503 | private static double ApproxSin(double x)
504 | {
505 | var j = x * 0.15915f;
506 | j -= (int)j;
507 | return 20.785 * j * (j - 0.5) * (j - 1.0);
508 | }
509 | }
510 |
511 | private struct Sweeper
512 | {
513 | public Sweeper()
514 | {
515 |
516 | }
517 |
518 | public bool Enabled { get; set; } = false;
519 | public bool Down { get; set; } = false;
520 | public bool Reload { get; set; } = false;
521 | public byte Shift { get; set; } = 0x00;
522 | public byte Timer { get; set; } = 0x00;
523 | public byte Period { get; set; } = 0x00;
524 | public ushort Change { get; set; } = 0;
525 | public bool Mute { get; set; } = false;
526 |
527 | public void Track(ushort target)
528 | {
529 | if (Enabled)
530 | {
531 | Change = (ushort)(target >>> Shift);
532 | Mute = target is < 8 or > 0x7FF;
533 | }
534 | }
535 |
536 | public bool Clock(ref ushort target, byte channel)
537 | {
538 | var changed = false;
539 | if (Timer == 0 && Enabled && Shift > 0 && !Mute)
540 | {
541 | if (target >= 8 && Change < 0x7FF)
542 | {
543 | if (Down)
544 | {
545 | target -= (ushort)(Change - channel);
546 | }
547 | else
548 | {
549 | target += Change;
550 | }
551 | changed = true;
552 | }
553 | }
554 |
555 | if (Reload || Timer == 0)
556 | {
557 | Timer = Period;
558 | Reload = false;
559 | }
560 | else
561 | {
562 | Timer--;
563 | }
564 |
565 | Mute = target is < 8 or > 0x7FF;
566 |
567 | return changed;
568 | }
569 | }
570 | }
--------------------------------------------------------------------------------
/NesSharp.WinForms/Debugging/PaletteControl.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.WinForms.Debugging
2 | {
3 | partial class PaletteControl
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Component Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | label1 = new Label();
32 | palette00 = new PictureBox();
33 | palette01 = new PictureBox();
34 | palette02 = new PictureBox();
35 | palette03 = new PictureBox();
36 | palette13 = new PictureBox();
37 | palette12 = new PictureBox();
38 | palette11 = new PictureBox();
39 | palette10 = new PictureBox();
40 | palette23 = new PictureBox();
41 | palette22 = new PictureBox();
42 | palette21 = new PictureBox();
43 | palette20 = new PictureBox();
44 | palette33 = new PictureBox();
45 | palette32 = new PictureBox();
46 | palette31 = new PictureBox();
47 | palette30 = new PictureBox();
48 | palette73 = new PictureBox();
49 | palette72 = new PictureBox();
50 | palette71 = new PictureBox();
51 | palette70 = new PictureBox();
52 | palette63 = new PictureBox();
53 | palette62 = new PictureBox();
54 | palette61 = new PictureBox();
55 | palette60 = new PictureBox();
56 | palette53 = new PictureBox();
57 | palette52 = new PictureBox();
58 | palette51 = new PictureBox();
59 | palette50 = new PictureBox();
60 | palette43 = new PictureBox();
61 | palette42 = new PictureBox();
62 | palette41 = new PictureBox();
63 | palette40 = new PictureBox();
64 | ((System.ComponentModel.ISupportInitialize)palette00).BeginInit();
65 | ((System.ComponentModel.ISupportInitialize)palette01).BeginInit();
66 | ((System.ComponentModel.ISupportInitialize)palette02).BeginInit();
67 | ((System.ComponentModel.ISupportInitialize)palette03).BeginInit();
68 | ((System.ComponentModel.ISupportInitialize)palette13).BeginInit();
69 | ((System.ComponentModel.ISupportInitialize)palette12).BeginInit();
70 | ((System.ComponentModel.ISupportInitialize)palette11).BeginInit();
71 | ((System.ComponentModel.ISupportInitialize)palette10).BeginInit();
72 | ((System.ComponentModel.ISupportInitialize)palette23).BeginInit();
73 | ((System.ComponentModel.ISupportInitialize)palette22).BeginInit();
74 | ((System.ComponentModel.ISupportInitialize)palette21).BeginInit();
75 | ((System.ComponentModel.ISupportInitialize)palette20).BeginInit();
76 | ((System.ComponentModel.ISupportInitialize)palette33).BeginInit();
77 | ((System.ComponentModel.ISupportInitialize)palette32).BeginInit();
78 | ((System.ComponentModel.ISupportInitialize)palette31).BeginInit();
79 | ((System.ComponentModel.ISupportInitialize)palette30).BeginInit();
80 | ((System.ComponentModel.ISupportInitialize)palette73).BeginInit();
81 | ((System.ComponentModel.ISupportInitialize)palette72).BeginInit();
82 | ((System.ComponentModel.ISupportInitialize)palette71).BeginInit();
83 | ((System.ComponentModel.ISupportInitialize)palette70).BeginInit();
84 | ((System.ComponentModel.ISupportInitialize)palette63).BeginInit();
85 | ((System.ComponentModel.ISupportInitialize)palette62).BeginInit();
86 | ((System.ComponentModel.ISupportInitialize)palette61).BeginInit();
87 | ((System.ComponentModel.ISupportInitialize)palette60).BeginInit();
88 | ((System.ComponentModel.ISupportInitialize)palette53).BeginInit();
89 | ((System.ComponentModel.ISupportInitialize)palette52).BeginInit();
90 | ((System.ComponentModel.ISupportInitialize)palette51).BeginInit();
91 | ((System.ComponentModel.ISupportInitialize)palette50).BeginInit();
92 | ((System.ComponentModel.ISupportInitialize)palette43).BeginInit();
93 | ((System.ComponentModel.ISupportInitialize)palette42).BeginInit();
94 | ((System.ComponentModel.ISupportInitialize)palette41).BeginInit();
95 | ((System.ComponentModel.ISupportInitialize)palette40).BeginInit();
96 | SuspendLayout();
97 | //
98 | // label1
99 | //
100 | label1.AutoSize = true;
101 | label1.Location = new Point(3, 0);
102 | label1.Name = "label1";
103 | label1.Size = new Size(51, 15);
104 | label1.TabIndex = 0;
105 | label1.Text = "Palettes:";
106 | //
107 | // palette00
108 | //
109 | palette00.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
110 | palette00.Location = new Point(3, 19);
111 | palette00.Name = "palette00";
112 | palette00.Size = new Size(30, 30);
113 | palette00.TabIndex = 1;
114 | palette00.TabStop = false;
115 | //
116 | // palette01
117 | //
118 | palette01.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
119 | palette01.Location = new Point(33, 19);
120 | palette01.Name = "palette01";
121 | palette01.Size = new Size(30, 30);
122 | palette01.TabIndex = 2;
123 | palette01.TabStop = false;
124 | //
125 | // palette02
126 | //
127 | palette02.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
128 | palette02.Location = new Point(63, 19);
129 | palette02.Name = "palette02";
130 | palette02.Size = new Size(30, 30);
131 | palette02.TabIndex = 3;
132 | palette02.TabStop = false;
133 | //
134 | // palette03
135 | //
136 | palette03.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
137 | palette03.Location = new Point(93, 19);
138 | palette03.Name = "palette03";
139 | palette03.Size = new Size(30, 30);
140 | palette03.TabIndex = 4;
141 | palette03.TabStop = false;
142 | //
143 | // palette13
144 | //
145 | palette13.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
146 | palette13.Location = new Point(219, 19);
147 | palette13.Name = "palette13";
148 | palette13.Size = new Size(30, 30);
149 | palette13.TabIndex = 8;
150 | palette13.TabStop = false;
151 | //
152 | // palette12
153 | //
154 | palette12.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
155 | palette12.Location = new Point(189, 19);
156 | palette12.Name = "palette12";
157 | palette12.Size = new Size(30, 30);
158 | palette12.TabIndex = 7;
159 | palette12.TabStop = false;
160 | //
161 | // palette11
162 | //
163 | palette11.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
164 | palette11.Location = new Point(159, 19);
165 | palette11.Name = "palette11";
166 | palette11.Size = new Size(30, 30);
167 | palette11.TabIndex = 6;
168 | palette11.TabStop = false;
169 | //
170 | // palette10
171 | //
172 | palette10.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
173 | palette10.Location = new Point(129, 19);
174 | palette10.Name = "palette10";
175 | palette10.Size = new Size(30, 30);
176 | palette10.TabIndex = 5;
177 | palette10.TabStop = false;
178 | //
179 | // palette23
180 | //
181 | palette23.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
182 | palette23.Location = new Point(345, 19);
183 | palette23.Name = "palette23";
184 | palette23.Size = new Size(30, 30);
185 | palette23.TabIndex = 12;
186 | palette23.TabStop = false;
187 | //
188 | // palette22
189 | //
190 | palette22.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
191 | palette22.Location = new Point(315, 19);
192 | palette22.Name = "palette22";
193 | palette22.Size = new Size(30, 30);
194 | palette22.TabIndex = 11;
195 | palette22.TabStop = false;
196 | //
197 | // palette21
198 | //
199 | palette21.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
200 | palette21.Location = new Point(285, 19);
201 | palette21.Name = "palette21";
202 | palette21.Size = new Size(30, 30);
203 | palette21.TabIndex = 10;
204 | palette21.TabStop = false;
205 | //
206 | // palette20
207 | //
208 | palette20.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
209 | palette20.Location = new Point(255, 19);
210 | palette20.Name = "palette20";
211 | palette20.Size = new Size(30, 30);
212 | palette20.TabIndex = 9;
213 | palette20.TabStop = false;
214 | //
215 | // palette33
216 | //
217 | palette33.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
218 | palette33.Location = new Point(471, 19);
219 | palette33.Name = "palette33";
220 | palette33.Size = new Size(30, 30);
221 | palette33.TabIndex = 16;
222 | palette33.TabStop = false;
223 | //
224 | // palette32
225 | //
226 | palette32.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
227 | palette32.Location = new Point(441, 19);
228 | palette32.Name = "palette32";
229 | palette32.Size = new Size(30, 30);
230 | palette32.TabIndex = 15;
231 | palette32.TabStop = false;
232 | //
233 | // palette31
234 | //
235 | palette31.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
236 | palette31.Location = new Point(411, 19);
237 | palette31.Name = "palette31";
238 | palette31.Size = new Size(30, 30);
239 | palette31.TabIndex = 14;
240 | palette31.TabStop = false;
241 | //
242 | // palette30
243 | //
244 | palette30.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
245 | palette30.Location = new Point(381, 19);
246 | palette30.Name = "palette30";
247 | palette30.Size = new Size(30, 30);
248 | palette30.TabIndex = 13;
249 | palette30.TabStop = false;
250 | //
251 | // palette73
252 | //
253 | palette73.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
254 | palette73.Location = new Point(975, 19);
255 | palette73.Name = "palette73";
256 | palette73.Size = new Size(30, 30);
257 | palette73.TabIndex = 32;
258 | palette73.TabStop = false;
259 | //
260 | // palette72
261 | //
262 | palette72.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
263 | palette72.Location = new Point(945, 19);
264 | palette72.Name = "palette72";
265 | palette72.Size = new Size(30, 30);
266 | palette72.TabIndex = 31;
267 | palette72.TabStop = false;
268 | //
269 | // palette71
270 | //
271 | palette71.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
272 | palette71.Location = new Point(915, 19);
273 | palette71.Name = "palette71";
274 | palette71.Size = new Size(30, 30);
275 | palette71.TabIndex = 30;
276 | palette71.TabStop = false;
277 | //
278 | // palette70
279 | //
280 | palette70.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
281 | palette70.Location = new Point(885, 19);
282 | palette70.Name = "palette70";
283 | palette70.Size = new Size(30, 30);
284 | palette70.TabIndex = 29;
285 | palette70.TabStop = false;
286 | //
287 | // palette63
288 | //
289 | palette63.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
290 | palette63.Location = new Point(849, 19);
291 | palette63.Name = "palette63";
292 | palette63.Size = new Size(30, 30);
293 | palette63.TabIndex = 28;
294 | palette63.TabStop = false;
295 | //
296 | // palette62
297 | //
298 | palette62.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
299 | palette62.Location = new Point(819, 19);
300 | palette62.Name = "palette62";
301 | palette62.Size = new Size(30, 30);
302 | palette62.TabIndex = 27;
303 | palette62.TabStop = false;
304 | //
305 | // palette61
306 | //
307 | palette61.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
308 | palette61.Location = new Point(789, 19);
309 | palette61.Name = "palette61";
310 | palette61.Size = new Size(30, 30);
311 | palette61.TabIndex = 26;
312 | palette61.TabStop = false;
313 | //
314 | // palette60
315 | //
316 | palette60.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
317 | palette60.Location = new Point(759, 19);
318 | palette60.Name = "palette60";
319 | palette60.Size = new Size(30, 30);
320 | palette60.TabIndex = 25;
321 | palette60.TabStop = false;
322 | //
323 | // palette53
324 | //
325 | palette53.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
326 | palette53.Location = new Point(723, 19);
327 | palette53.Name = "palette53";
328 | palette53.Size = new Size(30, 30);
329 | palette53.TabIndex = 24;
330 | palette53.TabStop = false;
331 | //
332 | // palette52
333 | //
334 | palette52.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
335 | palette52.Location = new Point(693, 19);
336 | palette52.Name = "palette52";
337 | palette52.Size = new Size(30, 30);
338 | palette52.TabIndex = 23;
339 | palette52.TabStop = false;
340 | //
341 | // palette51
342 | //
343 | palette51.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
344 | palette51.Location = new Point(663, 19);
345 | palette51.Name = "palette51";
346 | palette51.Size = new Size(30, 30);
347 | palette51.TabIndex = 22;
348 | palette51.TabStop = false;
349 | //
350 | // palette50
351 | //
352 | palette50.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
353 | palette50.Location = new Point(633, 19);
354 | palette50.Name = "palette50";
355 | palette50.Size = new Size(30, 30);
356 | palette50.TabIndex = 21;
357 | palette50.TabStop = false;
358 | //
359 | // palette43
360 | //
361 | palette43.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
362 | palette43.Location = new Point(597, 19);
363 | palette43.Name = "palette43";
364 | palette43.Size = new Size(30, 30);
365 | palette43.TabIndex = 20;
366 | palette43.TabStop = false;
367 | //
368 | // palette42
369 | //
370 | palette42.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
371 | palette42.Location = new Point(567, 19);
372 | palette42.Name = "palette42";
373 | palette42.Size = new Size(30, 30);
374 | palette42.TabIndex = 19;
375 | palette42.TabStop = false;
376 | //
377 | // palette41
378 | //
379 | palette41.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
380 | palette41.Location = new Point(537, 19);
381 | palette41.Name = "palette41";
382 | palette41.Size = new Size(30, 30);
383 | palette41.TabIndex = 18;
384 | palette41.TabStop = false;
385 | //
386 | // palette40
387 | //
388 | palette40.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
389 | palette40.Location = new Point(507, 19);
390 | palette40.Name = "palette40";
391 | palette40.Size = new Size(30, 30);
392 | palette40.TabIndex = 17;
393 | palette40.TabStop = false;
394 | //
395 | // PaletteControl
396 | //
397 | AutoScaleDimensions = new SizeF(7F, 15F);
398 | AutoScaleMode = AutoScaleMode.Font;
399 | Controls.Add(palette73);
400 | Controls.Add(palette72);
401 | Controls.Add(palette71);
402 | Controls.Add(palette70);
403 | Controls.Add(palette63);
404 | Controls.Add(palette62);
405 | Controls.Add(palette61);
406 | Controls.Add(palette60);
407 | Controls.Add(palette53);
408 | Controls.Add(palette52);
409 | Controls.Add(palette51);
410 | Controls.Add(palette50);
411 | Controls.Add(palette43);
412 | Controls.Add(palette42);
413 | Controls.Add(palette41);
414 | Controls.Add(palette40);
415 | Controls.Add(palette33);
416 | Controls.Add(palette32);
417 | Controls.Add(palette31);
418 | Controls.Add(palette30);
419 | Controls.Add(palette23);
420 | Controls.Add(palette22);
421 | Controls.Add(palette21);
422 | Controls.Add(palette20);
423 | Controls.Add(palette13);
424 | Controls.Add(palette12);
425 | Controls.Add(palette11);
426 | Controls.Add(palette10);
427 | Controls.Add(palette03);
428 | Controls.Add(palette02);
429 | Controls.Add(palette01);
430 | Controls.Add(palette00);
431 | Controls.Add(label1);
432 | MaximumSize = new Size(1011, 52);
433 | MinimumSize = new Size(1011, 52);
434 | Name = "PaletteControl";
435 | Size = new Size(1011, 52);
436 | ((System.ComponentModel.ISupportInitialize)palette00).EndInit();
437 | ((System.ComponentModel.ISupportInitialize)palette01).EndInit();
438 | ((System.ComponentModel.ISupportInitialize)palette02).EndInit();
439 | ((System.ComponentModel.ISupportInitialize)palette03).EndInit();
440 | ((System.ComponentModel.ISupportInitialize)palette13).EndInit();
441 | ((System.ComponentModel.ISupportInitialize)palette12).EndInit();
442 | ((System.ComponentModel.ISupportInitialize)palette11).EndInit();
443 | ((System.ComponentModel.ISupportInitialize)palette10).EndInit();
444 | ((System.ComponentModel.ISupportInitialize)palette23).EndInit();
445 | ((System.ComponentModel.ISupportInitialize)palette22).EndInit();
446 | ((System.ComponentModel.ISupportInitialize)palette21).EndInit();
447 | ((System.ComponentModel.ISupportInitialize)palette20).EndInit();
448 | ((System.ComponentModel.ISupportInitialize)palette33).EndInit();
449 | ((System.ComponentModel.ISupportInitialize)palette32).EndInit();
450 | ((System.ComponentModel.ISupportInitialize)palette31).EndInit();
451 | ((System.ComponentModel.ISupportInitialize)palette30).EndInit();
452 | ((System.ComponentModel.ISupportInitialize)palette73).EndInit();
453 | ((System.ComponentModel.ISupportInitialize)palette72).EndInit();
454 | ((System.ComponentModel.ISupportInitialize)palette71).EndInit();
455 | ((System.ComponentModel.ISupportInitialize)palette70).EndInit();
456 | ((System.ComponentModel.ISupportInitialize)palette63).EndInit();
457 | ((System.ComponentModel.ISupportInitialize)palette62).EndInit();
458 | ((System.ComponentModel.ISupportInitialize)palette61).EndInit();
459 | ((System.ComponentModel.ISupportInitialize)palette60).EndInit();
460 | ((System.ComponentModel.ISupportInitialize)palette53).EndInit();
461 | ((System.ComponentModel.ISupportInitialize)palette52).EndInit();
462 | ((System.ComponentModel.ISupportInitialize)palette51).EndInit();
463 | ((System.ComponentModel.ISupportInitialize)palette50).EndInit();
464 | ((System.ComponentModel.ISupportInitialize)palette43).EndInit();
465 | ((System.ComponentModel.ISupportInitialize)palette42).EndInit();
466 | ((System.ComponentModel.ISupportInitialize)palette41).EndInit();
467 | ((System.ComponentModel.ISupportInitialize)palette40).EndInit();
468 | ResumeLayout(false);
469 | PerformLayout();
470 | }
471 |
472 | #endregion
473 |
474 | private Label label1;
475 | private PictureBox palette00;
476 | private PictureBox palette01;
477 | private PictureBox palette02;
478 | private PictureBox palette03;
479 | private PictureBox palette13;
480 | private PictureBox palette12;
481 | private PictureBox palette11;
482 | private PictureBox palette10;
483 | private PictureBox palette23;
484 | private PictureBox palette22;
485 | private PictureBox palette21;
486 | private PictureBox palette20;
487 | private PictureBox palette33;
488 | private PictureBox palette32;
489 | private PictureBox palette31;
490 | private PictureBox palette30;
491 | private PictureBox palette73;
492 | private PictureBox palette72;
493 | private PictureBox palette71;
494 | private PictureBox palette70;
495 | private PictureBox palette63;
496 | private PictureBox palette62;
497 | private PictureBox palette61;
498 | private PictureBox palette60;
499 | private PictureBox palette53;
500 | private PictureBox palette52;
501 | private PictureBox palette51;
502 | private PictureBox palette50;
503 | private PictureBox palette43;
504 | private PictureBox palette42;
505 | private PictureBox palette41;
506 | private PictureBox palette40;
507 | }
508 | }
509 |
--------------------------------------------------------------------------------
/NesSharp.Core/Cpu.cs:
--------------------------------------------------------------------------------
1 | namespace NesSharp.Core;
2 |
3 | ///
4 | /// Class representing the 6502 CPU
5 | ///
6 | public sealed class Cpu
7 | {
8 | private byte _flags;
9 | private Bus? _bus;
10 |
11 | private byte _fetchedData;
12 | private ushort _absoluteAddress;
13 | private ushort _relativeAddress;
14 | private byte _opCode;
15 | private byte _cycles;
16 |
17 | private readonly Instruction[] _instructionSet;
18 | private bool _isHalted;
19 |
20 | public Cpu()
21 | {
22 | _instructionSet = new Instruction[] {
23 | new("BRK", BRK, IMM, AddressingMode.IMM, 7), new("ORA", ORA, IZX, AddressingMode.IZX, 6), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("???", NOP, IMP, AddressingMode.IMP, 3), new("ORA", ORA, ZP0, AddressingMode.ZP0, 3), new("ASL", ASL, ZP0, AddressingMode.ZP0, 5), new("???", XXX, IMP, AddressingMode.IMP, 5), new("PHP", PHP, IMP, AddressingMode.IMP, 3), new("ORA", ORA, IMM, AddressingMode.IMM, 2), new("ASL", ASL, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", NOP, IMP, AddressingMode.IMP, 4), new("ORA", ORA, ABS, AddressingMode.ABS, 4), new("ASL", ASL, ABS, AddressingMode.ABS, 6), new("???", XXX, IMP, AddressingMode.IMP, 6),
24 | new("BPL", BPL, REL, AddressingMode.REL, 2), new("ORA", ORA, IZY, AddressingMode.IZY, 5), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("???", NOP, IMP, AddressingMode.IMP, 4), new("ORA", ORA, ZPX, AddressingMode.ZPX, 4), new("ASL", ASL, ZPX, AddressingMode.ZPX, 6), new("???", XXX, IMP, AddressingMode.IMP, 6), new("CLC", CLC, IMP, AddressingMode.IMP, 2), new("ORA", ORA, ABY, AddressingMode.ABY, 4), new("???", NOP, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 7), new("???", NOP, IMP, AddressingMode.IMP, 4), new("ORA", ORA, ABX, AddressingMode.ABX, 4), new("ASL", ASL, ABX, AddressingMode.ABX, 7), new("???", XXX, IMP, AddressingMode.IMP, 7),
25 | new("JSR", JSR, ABS, AddressingMode.ABS, 6), new("AND", AND, IZX, AddressingMode.IZX, 6), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("BIT", BIT, ZP0, AddressingMode.ZP0, 3), new("AND", AND, ZP0, AddressingMode.ZP0, 3), new("ROL", ROL, ZP0, AddressingMode.ZP0, 5), new("???", XXX, IMP, AddressingMode.IMP, 5), new("PLP", PLP, IMP, AddressingMode.IMP, 4), new("AND", AND, IMM, AddressingMode.IMM, 2), new("ROL", ROL, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 2), new("BIT", BIT, ABS, AddressingMode.ABS, 4), new("AND", AND, ABS, AddressingMode.ABS, 4), new("ROL", ROL, ABS, AddressingMode.ABS, 6), new("???", XXX, IMP, AddressingMode.IMP, 6),
26 | new("BMI", BMI, REL, AddressingMode.REL, 2), new("AND", AND, IZY, AddressingMode.IZY, 5), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("???", NOP, IMP, AddressingMode.IMP, 4), new("AND", AND, ZPX, AddressingMode.ZPX, 4), new("ROL", ROL, ZPX, AddressingMode.ZPX, 6), new("???", XXX, IMP, AddressingMode.IMP, 6), new("SEC", SEC, IMP, AddressingMode.IMP, 2), new("AND", AND, ABY, AddressingMode.ABY, 4), new("???", NOP, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 7), new("???", NOP, IMP, AddressingMode.IMP, 4), new("AND", AND, ABX, AddressingMode.ABX, 4), new("ROL", ROL, ABX, AddressingMode.ABX, 7), new("???", XXX, IMP, AddressingMode.IMP, 7),
27 | new("RTI", RTI, IMP, AddressingMode.IMP, 6), new("EOR", EOR, IZX, AddressingMode.IZX, 6), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("???", NOP, IMP, AddressingMode.IMP, 3), new("EOR", EOR, ZP0, AddressingMode.ZP0, 3), new("LSR", LSR, ZP0, AddressingMode.ZP0, 5), new("???", XXX, IMP, AddressingMode.IMP, 5), new("PHA", PHA, IMP, AddressingMode.IMP, 3), new("EOR", EOR, IMM, AddressingMode.IMM, 2), new("LSR", LSR, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 2), new("JMP", JMP, ABS, AddressingMode.ABS, 3), new("EOR", EOR, ABS, AddressingMode.ABS, 4), new("LSR", LSR, ABS, AddressingMode.ABS, 6), new("???", XXX, IMP, AddressingMode.IMP, 6),
28 | new("BVC", BVC, REL, AddressingMode.REL, 2), new("EOR", EOR, IZY, AddressingMode.IZY, 5), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("???", NOP, IMP, AddressingMode.IMP, 4), new("EOR", EOR, ZPX, AddressingMode.ZPX, 4), new("LSR", LSR, ZPX, AddressingMode.ZPX, 6), new("???", XXX, IMP, AddressingMode.IMP, 6), new("CLI", CLI, IMP, AddressingMode.IMP, 2), new("EOR", EOR, ABY, AddressingMode.ABY, 4), new("???", NOP, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 7), new("???", NOP, IMP, AddressingMode.IMP, 4), new("EOR", EOR, ABX, AddressingMode.ABX, 4), new("LSR", LSR, ABX, AddressingMode.ABX, 7), new("???", XXX, IMP, AddressingMode.IMP, 7),
29 | new("RTS", RTS, IMP, AddressingMode.IMP, 6), new("ADC", ADC, IZX, AddressingMode.IZX, 6), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("???", NOP, IMP, AddressingMode.IMP, 3), new("ADC", ADC, ZP0, AddressingMode.ZP0, 3), new("ROR", ROR, ZP0, AddressingMode.ZP0, 5), new("???", XXX, IMP, AddressingMode.IMP, 5), new("PLA", PLA, IMP, AddressingMode.IMP, 4), new("ADC", ADC, IMM, AddressingMode.IMM, 2), new("ROR", ROR, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 2), new("JMP", JMP, IND, AddressingMode.IND, 5), new("ADC", ADC, ABS, AddressingMode.ABS, 4), new("ROR", ROR, ABS, AddressingMode.ABS, 6), new("???", XXX, IMP, AddressingMode.IMP, 6),
30 | new("BVS", BVS, REL, AddressingMode.REL, 2), new("ADC", ADC, IZY, AddressingMode.IZY, 5), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("???", NOP, IMP, AddressingMode.IMP, 4), new("ADC", ADC, ZPX, AddressingMode.ZPX, 4), new("ROR", ROR, ZPX, AddressingMode.ZPX, 6), new("???", XXX, IMP, AddressingMode.IMP, 6), new("SEI", SEI, IMP, AddressingMode.IMP, 2), new("ADC", ADC, ABY, AddressingMode.ABY, 4), new("???", NOP, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 7), new("???", NOP, IMP, AddressingMode.IMP, 4), new("ADC", ADC, ABX, AddressingMode.ABX, 4), new("ROR", ROR, ABX, AddressingMode.ABX, 7), new("???", XXX, IMP, AddressingMode.IMP, 7),
31 | new("???", NOP, IMP, AddressingMode.IMP, 2), new("STA", STA, IZX, AddressingMode.IZX, 6), new("???", NOP, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 6), new("STY", STY, ZP0, AddressingMode.ZP0, 3), new("STA", STA, ZP0, AddressingMode.ZP0, 3), new("STX", STX, ZP0, AddressingMode.ZP0, 3), new("???", XXX, IMP, AddressingMode.IMP, 3), new("DEY", DEY, IMP, AddressingMode.IMP, 2), new("???", NOP, IMP, AddressingMode.IMP, 2), new("TXA", TXA, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 2), new("STY", STY, ABS, AddressingMode.ABS, 4), new("STA", STA, ABS, AddressingMode.ABS, 4), new("STX", STX, ABS, AddressingMode.ABS, 4), new("???", XXX, IMP, AddressingMode.IMP, 4),
32 | new("BCC", BCC, REL, AddressingMode.REL, 2), new("STA", STA, IZY, AddressingMode.IZY, 6), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 6), new("STY", STY, ZPX, AddressingMode.ZPX, 4), new("STA", STA, ZPX, AddressingMode.ZPX, 4), new("STX", STX, ZPY, AddressingMode.ZPX, 4), new("???", XXX, IMP, AddressingMode.IMP, 4), new("TYA", TYA, IMP, AddressingMode.IMP, 2), new("STA", STA, ABY, AddressingMode.ABY, 5), new("TXS", TXS, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 5), new("???", NOP, IMP, AddressingMode.IMP, 5), new("STA", STA, ABX, AddressingMode.ABX, 5), new("???", XXX, IMP, AddressingMode.IMP, 5), new("???", XXX, IMP, AddressingMode.IMP, 5),
33 | new("LDY", LDY, IMM, AddressingMode.IMM, 2), new("LDA", LDA, IZX, AddressingMode.IZX, 6), new("LDX", LDX, IMM, AddressingMode.IMM, 2), new("???", XXX, IMP, AddressingMode.IMP, 6), new("LDY", LDY, ZP0, AddressingMode.ZP0, 3), new("LDA", LDA, ZP0, AddressingMode.ZP0, 3), new("LDX", LDX, ZP0, AddressingMode.ZP0, 3), new("???", XXX, IMP, AddressingMode.IMP, 3), new("TAY", TAY, IMP, AddressingMode.IMP, 2), new("LDA", LDA, IMM, AddressingMode.IMM, 2), new("TAX", TAX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 2), new("LDY", LDY, ABS, AddressingMode.ABS, 4), new("LDA", LDA, ABS, AddressingMode.ABS, 4), new("LDX", LDX, ABS, AddressingMode.ABS, 4), new("???", XXX, IMP, AddressingMode.IMP, 4),
34 | new("BCS", BCS, REL, AddressingMode.REL, 2), new("LDA", LDA, IZY, AddressingMode.IZY, 5), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 5), new("LDY", LDY, ZPX, AddressingMode.ZPX, 4), new("LDA", LDA, ZPX, AddressingMode.ZPX, 4), new("LDX", LDX, ZPY, AddressingMode.ZPX, 4), new("???", XXX, IMP, AddressingMode.IMP, 4), new("CLV", CLV, IMP, AddressingMode.IMP, 2), new("LDA", LDA, ABY, AddressingMode.ABY, 4), new("TSX", TSX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 4), new("LDY", LDY, ABX, AddressingMode.ABX, 4), new("LDA", LDA, ABX, AddressingMode.ABX, 4), new("LDX", LDX, ABY, AddressingMode.ABY, 4), new("???", XXX, IMP, AddressingMode.IMP, 4),
35 | new("CPY", CPY, IMM, AddressingMode.IMM, 2), new("CMP", CMP, IZX, AddressingMode.IZX, 6), new("???", NOP, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("CPY", CPY, ZP0, AddressingMode.ZP0, 3), new("CMP", CMP, ZP0, AddressingMode.ZP0, 3), new("DEC", DEC, ZP0, AddressingMode.ZP0, 5), new("???", XXX, IMP, AddressingMode.IMP, 5), new("INY", INY, IMP, AddressingMode.IMP, 2), new("CMP", CMP, IMM, AddressingMode.IMM, 2), new("DEX", DEX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 2), new("CPY", CPY, ABS, AddressingMode.ABS, 4), new("CMP", CMP, ABS, AddressingMode.ABS, 4), new("DEC", DEC, ABS, AddressingMode.ABS, 6), new("???", XXX, IMP, AddressingMode.IMP, 6),
36 | new("BNE", BNE, REL, AddressingMode.REL, 2), new("CMP", CMP, IZY, AddressingMode.IZY, 5), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("???", NOP, IMP, AddressingMode.IMP, 4), new("CMP", CMP, ZPX, AddressingMode.ZPX, 4), new("DEC", DEC, ZPX, AddressingMode.ZPX, 6), new("???", XXX, IMP, AddressingMode.IMP, 6), new("CLD", CLD, IMP, AddressingMode.IMP, 2), new("CMP", CMP, ABY, AddressingMode.ABY, 4), new("NOP", NOP, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 7), new("???", NOP, IMP, AddressingMode.IMP, 4), new("CMP", CMP, ABX, AddressingMode.ABX, 4), new("DEC", DEC, ABX, AddressingMode.ABX, 7), new("???", XXX, IMP, AddressingMode.IMP, 7),
37 | new("CPX", CPX, IMM, AddressingMode.IMM, 2), new("SBC", SBC, IZX, AddressingMode.IZX, 6), new("???", NOP, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("CPX", CPX, ZP0, AddressingMode.ZP0, 3), new("SBC", SBC, ZP0, AddressingMode.ZP0, 3), new("INC", INC, ZP0, AddressingMode.ZP0, 5), new("???", XXX, IMP, AddressingMode.IMP, 5), new("INX", INX, IMP, AddressingMode.IMP, 2), new("SBC", SBC, IMM, AddressingMode.IMM, 2), new("NOP", NOP, IMP, AddressingMode.IMP, 2), new("???", SBC, IMP, AddressingMode.IMP, 2), new("CPX", CPX, ABS, AddressingMode.ABS, 4), new("SBC", SBC, ABS, AddressingMode.ABS, 4), new("INC", INC, ABS, AddressingMode.ABS, 6), new("???", XXX, IMP, AddressingMode.IMP, 6),
38 | new("BEQ", BEQ, REL, AddressingMode.REL, 2), new("SBC", SBC, IZY, AddressingMode.IZY, 5), new("???", XXX, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 8), new("???", NOP, IMP, AddressingMode.IMP, 4), new("SBC", SBC, ZPX, AddressingMode.ZPX, 4), new("INC", INC, ZPX, AddressingMode.ZPX, 6), new("???", XXX, IMP, AddressingMode.IMP, 6), new("SED", SED, IMP, AddressingMode.IMP, 2), new("SBC", SBC, ABY, AddressingMode.ABY, 4), new("NOP", NOP, IMP, AddressingMode.IMP, 2), new("???", XXX, IMP, AddressingMode.IMP, 7), new("???", NOP, IMP, AddressingMode.IMP, 4), new("SBC", SBC, ABX, AddressingMode.ABX, 4), new("INC", INC, ABX, AddressingMode.ABX, 7), new("???", XXX, IMP, AddressingMode.IMP, 7),
39 | };
40 | }
41 |
42 | public void ConnectBus(Bus bus)
43 | {
44 | _bus = bus;
45 | _bus.Initialize();
46 | }
47 |
48 | // Registers
49 |
50 | ///
51 | /// Program Counter
52 | ///
53 | public ushort PC { get; set; }
54 |
55 | ///
56 | /// Stack Pointer
57 | ///
58 | public byte SP { get; set; }
59 |
60 | ///
61 | /// Accumulator
62 | ///
63 | public byte A { get; set; }
64 |
65 | ///
66 | /// Index Register X
67 | ///
68 | public byte X { get; set; }
69 |
70 | ///
71 | /// Index Register Y
72 | ///
73 | public byte Y { get; set; }
74 |
75 | // Flags
76 | public bool CarryFlag
77 | {
78 | get => (_flags & 0b00000001) == 0b00000001;
79 | set => _flags = (byte)(value ? _flags | 0b00000001 : _flags & 0b11111110);
80 | }
81 |
82 | public bool ZeroFlag
83 | {
84 | get => (_flags & 0b00000010) == 0b00000010;
85 | set => _flags = (byte)(value ? _flags | 0b00000010 : _flags & 0b11111101);
86 | }
87 |
88 | public bool InterruptDisableFlag
89 | {
90 | get => (_flags & 0b00000100) == 0b00000100;
91 | set => _flags = (byte)(value ? _flags | 0b00000100 : _flags & 0b11111011);
92 | }
93 |
94 | public bool DecimalModeFlag
95 | {
96 | get => (_flags & 0b00001000) == 0b00001000;
97 | set => _flags = (byte)(value ? _flags | 0b00001000 : _flags & 0b11110111);
98 | }
99 |
100 | public bool BreakCommandFlag
101 | {
102 | get => (_flags & 0b00010000) == 0b00010000;
103 | set => _flags = (byte)(value ? _flags | 0b00010000 : _flags & 0b11101111);
104 | }
105 |
106 | public bool UnusedFlag
107 | {
108 | get => (_flags & 0b00100000) == 0b00100000;
109 | set => _flags = (byte)(value ? _flags | 0b00100000 : _flags & 0b11011111);
110 | }
111 |
112 | public bool OverflowFlag
113 | {
114 | get => (_flags & 0b01000000) == 0b01000000;
115 | set => _flags = (byte)(value ? _flags | 0b01000000 : _flags & 0b10111111);
116 | }
117 |
118 | public bool NegativeFlag
119 | {
120 | get => (_flags & 0b10000000) == 0b10000000;
121 | set => _flags = (byte)(value ? _flags | 0b10000000 : _flags & 0b01111111);
122 | }
123 |
124 | private void WriteByte(ushort address, byte data)
125 | {
126 | _bus!.CpuWrite(address, data);
127 | }
128 |
129 | private byte ReadByte(ushort address)
130 | {
131 | return _bus!.CpuRead(address);
132 | }
133 |
134 | // Signals
135 |
136 | public void Reset()
137 | {
138 | _absoluteAddress = 0xFFFC;
139 | ushort lo = ReadByte(_absoluteAddress);
140 | ushort hi = ReadByte((ushort)(_absoluteAddress + 1));
141 |
142 | PC = (ushort)((hi << 8) | lo);
143 |
144 | A = X = Y = 0;
145 | SP = 0xFD;
146 |
147 | _flags = 0;
148 | UnusedFlag = true;
149 |
150 | _absoluteAddress = 0;
151 | _relativeAddress = 0;
152 | _fetchedData = 0;
153 |
154 | _cycles = 8;
155 | _isHalted = false;
156 | }
157 |
158 | public void Stop()
159 | {
160 | _isHalted = true;
161 | }
162 |
163 | ///
164 | /// Interrupt Request
165 | ///
166 | /// These kinds of interrupts can be ignored if the interrupt enable flag is not set.
167 | public void IRQ()
168 | {
169 | if (InterruptDisableFlag || _isHalted)
170 | {
171 | return;
172 | }
173 |
174 | WriteByte((ushort)(0x0100 + SP), (byte)((PC >>> 8) & 0x00FF));
175 | SP--;
176 | WriteByte((ushort)(0x0100 + SP), (byte)(PC & 0x00FF));
177 | SP--;
178 |
179 | BreakCommandFlag = false;
180 | UnusedFlag = true;
181 | InterruptDisableFlag = true;
182 | WriteByte((ushort)(0x0100 + SP), _flags);
183 | SP--;
184 |
185 | _absoluteAddress = 0xFFFE;
186 | ushort lo = ReadByte(_absoluteAddress);
187 | ushort hi = ReadByte((ushort)(_absoluteAddress + 1));
188 | PC = (ushort)((hi << 8) | lo);
189 |
190 | _cycles = 7;
191 | }
192 |
193 | ///
194 | /// Non-Maskable Interrupt
195 | ///
196 | /// These interrupts cannot be ignored.
197 | public void NMI()
198 | {
199 | if (_isHalted)
200 | {
201 | return;
202 | }
203 |
204 | WriteByte((ushort)(0x0100 + SP), (byte)((PC >>> 8) & 0x00FF));
205 | SP--;
206 | WriteByte((ushort)(0x0100 + SP), (byte)(PC & 0x00FF));
207 | SP--;
208 |
209 | BreakCommandFlag = false;
210 | UnusedFlag = true;
211 | InterruptDisableFlag = true;
212 | WriteByte((ushort)(0x0100 + SP), _flags);
213 | SP--;
214 |
215 | _absoluteAddress = 0xFFFA;
216 | ushort lo = ReadByte(_absoluteAddress);
217 | ushort hi = ReadByte((ushort)(_absoluteAddress + 1));
218 | PC = (ushort)((hi << 8) | lo);
219 |
220 | _cycles = 8;
221 | }
222 |
223 | public void Clock()
224 | {
225 | if (_isHalted)
226 | {
227 | return;
228 | }
229 |
230 | if (_cycles == 0)
231 | {
232 | _opCode = ReadByte(PC);
233 | UnusedFlag = true;
234 | PC++;
235 |
236 | var instruction = _instructionSet[_opCode];
237 | _cycles = instruction.Cycles;
238 |
239 | var additionalCycle1 = instruction.AddressingMode();
240 | var additionalCycle2 = instruction.OpCode();
241 |
242 | if (additionalCycle1 && additionalCycle2)
243 | {
244 | _cycles++;
245 | }
246 |
247 | UnusedFlag = true;
248 | }
249 |
250 | _cycles--;
251 | }
252 |
253 | // Addressing modes
254 | // ReSharper disable InconsistentNaming
255 | public bool IMP()
256 | {
257 | _fetchedData = A;
258 | return false;
259 | }
260 |
261 | public bool IMM()
262 | {
263 | _absoluteAddress = PC++;
264 | return false;
265 | }
266 |
267 | public bool ZP0()
268 | {
269 | _absoluteAddress = ReadByte(PC++);
270 | _absoluteAddress &= 0x00FF;
271 | return false;
272 | }
273 |
274 | public bool ZPX()
275 | {
276 | _absoluteAddress = (ushort)(ReadByte(PC++) + X);
277 | _absoluteAddress &= 0x00FF;
278 | return false;
279 | }
280 |
281 | public bool ZPY()
282 | {
283 | _absoluteAddress = (ushort)(ReadByte(PC++) + Y);
284 | _absoluteAddress &= 0x00FF;
285 | return false;
286 | }
287 |
288 | public bool REL()
289 | {
290 | _relativeAddress = ReadByte(PC++);
291 |
292 | if ((_relativeAddress & 0x80) != 0)
293 | {
294 | _relativeAddress |= 0xFF00;
295 | }
296 |
297 | return false;
298 | }
299 |
300 | public bool ABS()
301 | {
302 | ushort lo = ReadByte(PC++);
303 | ushort hi = ReadByte(PC++);
304 |
305 | _absoluteAddress = (ushort)((hi << 8) | lo);
306 | return false;
307 | }
308 |
309 | public bool ABX()
310 | {
311 | ushort lo = ReadByte(PC++);
312 | ushort hi = ReadByte(PC++);
313 |
314 | _absoluteAddress = (ushort)((hi << 8) | lo);
315 | _absoluteAddress += X;
316 |
317 | // Needs an additional clock cycle if the page boundary is crossed
318 | return (_absoluteAddress & 0xFF00) != (hi << 8);
319 | }
320 |
321 | public bool ABY()
322 | {
323 | ushort lo = ReadByte(PC++);
324 | ushort hi = ReadByte(PC++);
325 |
326 | _absoluteAddress = (ushort)((hi << 8) | lo);
327 | _absoluteAddress += Y;
328 |
329 | // Needs an additional clock cycle if the page boundary is crossed
330 | return (_absoluteAddress & 0xFF00) != (hi << 8);
331 | }
332 |
333 | public bool IND()
334 | {
335 | ushort ptr_lo = ReadByte(PC++);
336 | ushort ptr_hi = ReadByte(PC++);
337 |
338 | var ptr = (ushort)((ptr_hi << 8) | ptr_lo);
339 | if (ptr_lo == 0x00FF) // Simulate page boundary hardware bug
340 | {
341 | _absoluteAddress = (ushort)((ReadByte((ushort)(ptr & 0xFF00)) << 8) | ReadByte(ptr));
342 | }
343 | else // Behave normally
344 | {
345 | _absoluteAddress = (ushort)((ReadByte((ushort)(ptr + 1)) << 8) | ReadByte(ptr));
346 | }
347 |
348 | return false;
349 | }
350 |
351 | public bool IZX()
352 | {
353 | ushort t = ReadByte(PC++);
354 | ushort lo = ReadByte((ushort)((t + X) & 0x00FF));
355 | ushort hi = ReadByte((ushort)((t + X + 1) & 0x00FF));
356 |
357 | _absoluteAddress = (ushort)((hi << 8) | lo);
358 |
359 | return false;
360 | }
361 |
362 | public bool IZY()
363 | {
364 | ushort t = ReadByte(PC++);
365 | ushort lo = ReadByte((ushort)(t & 0x00FF));
366 | ushort hi = ReadByte((ushort)((t + 1) & 0x00FF));
367 |
368 | _absoluteAddress = (ushort)((hi << 8) | lo);
369 | _absoluteAddress += Y;
370 |
371 | // Needs an additional clock cycle if the page boundary is crossed
372 | return (_absoluteAddress & 0xFF00) != (hi << 8);
373 | }
374 | // ReSharper restore InconsistentNaming
375 |
376 | private byte Fetch()
377 | {
378 | if (_instructionSet[_opCode].Mode != AddressingMode.IMP)
379 | {
380 | _fetchedData = ReadByte(_absoluteAddress);
381 | }
382 |
383 | return _fetchedData;
384 | }
385 |
386 | // OpCodes
387 | // ReSharper disable InconsistentNaming
388 | public bool ADC()
389 | {
390 | Fetch();
391 |
392 | var temp = (ushort)(A + _fetchedData + (CarryFlag ? 1 : 0));
393 |
394 | CarryFlag = temp > 255;
395 | ZeroFlag = (temp & 0x00FF) == 0;
396 | NegativeFlag = (temp & 0x80) != 0;
397 |
398 | OverflowFlag = (~(A ^ _fetchedData) & (A ^ temp) & 0x80) != 0;
399 |
400 | A = (byte)(temp & 0x00FF);
401 | return true;
402 | }
403 |
404 | public bool AND()
405 | {
406 | Fetch();
407 | A &= _fetchedData;
408 | ZeroFlag = A == 0;
409 | NegativeFlag = (A & 0x80) != 0;
410 | return true;
411 | }
412 |
413 | public bool ASL()
414 | {
415 | Fetch();
416 | var temp = (ushort)(_fetchedData << 1);
417 | CarryFlag = (temp & 0xFF00) > 0;
418 | ZeroFlag = (temp & 0x00FF) == 0;
419 | NegativeFlag = (temp & 0x80) != 0;
420 |
421 | if (_instructionSet[_opCode].Mode == AddressingMode.IMP)
422 | {
423 | A = (byte)(temp & 0x00FF);
424 | }
425 | else
426 | {
427 | WriteByte(_absoluteAddress, (byte)(temp & 0x00FF));
428 | }
429 |
430 | return false;
431 | }
432 |
433 | public bool BCC()
434 | {
435 | if (!CarryFlag)
436 | {
437 | _cycles++;
438 | _absoluteAddress = (ushort)(PC + _relativeAddress);
439 | if ((_absoluteAddress & 0xFF00) != (PC & 0xFF00))
440 | {
441 | _cycles++;
442 | }
443 |
444 | PC = _absoluteAddress;
445 | }
446 |
447 | return false;
448 | }
449 |
450 | public bool BCS()
451 | {
452 | if (CarryFlag)
453 | {
454 | _cycles++;
455 | _absoluteAddress = (ushort)(PC + _relativeAddress);
456 | if ((_absoluteAddress & 0xFF00) != (PC & 0xFF00))
457 | {
458 | _cycles++;
459 | }
460 |
461 | PC = _absoluteAddress;
462 | }
463 |
464 | return false;
465 | }
466 |
467 | public bool BEQ()
468 | {
469 | if (ZeroFlag)
470 | {
471 | _cycles++;
472 | _absoluteAddress = (ushort)(PC + _relativeAddress);
473 | if ((_absoluteAddress & 0xFF00) != (PC & 0xFF00))
474 | {
475 | _cycles++;
476 | }
477 |
478 | PC = _absoluteAddress;
479 | }
480 |
481 | return false;
482 | }
483 |
484 | public bool BIT()
485 | {
486 | Fetch();
487 | var temp = (ushort)(A & _fetchedData);
488 | ZeroFlag = (temp & 0x00FF) == 0;
489 | NegativeFlag = (temp & 0x80) != 0;
490 | OverflowFlag = (temp & 0x40) != 0;
491 | return false;
492 | }
493 |
494 | public bool BMI()
495 | {
496 | if (NegativeFlag)
497 | {
498 | _cycles++;
499 | _absoluteAddress = (ushort)(PC + _relativeAddress);
500 | if ((_absoluteAddress & 0xFF00) != (PC & 0xFF00))
501 | {
502 | _cycles++;
503 | }
504 |
505 | PC = _absoluteAddress;
506 | }
507 |
508 | return false;
509 | }
510 |
511 | public bool BNE()
512 | {
513 | if (!ZeroFlag)
514 | {
515 | _cycles++;
516 | _absoluteAddress = (ushort)(PC + _relativeAddress);
517 | if ((_absoluteAddress & 0xFF00) != (PC & 0xFF00))
518 | {
519 | _cycles++;
520 | }
521 |
522 | PC = _absoluteAddress;
523 | }
524 |
525 | return false;
526 | }
527 |
528 | public bool BPL()
529 | {
530 | if (!NegativeFlag)
531 | {
532 | _cycles++;
533 | _absoluteAddress = (ushort)(PC + _relativeAddress);
534 | if ((_absoluteAddress & 0xFF00) != (PC & 0xFF00))
535 | {
536 | _cycles++;
537 | }
538 |
539 | PC = _absoluteAddress;
540 | }
541 |
542 | return false;
543 | }
544 |
545 | public bool BRK()
546 | {
547 | PC++;
548 |
549 | InterruptDisableFlag = true;
550 | WriteByte((ushort)(0x0100 + SP), (byte)((PC >> 8) & 0x00FF));
551 | SP--;
552 | WriteByte((ushort)(0x0100 + SP), (byte)(PC & 0x00FF));
553 | SP--;
554 |
555 | BreakCommandFlag = true;
556 | WriteByte((ushort)(0x0100 + SP), _flags);
557 | SP--;
558 | BreakCommandFlag = false;
559 |
560 | PC = (ushort)(ReadByte(0xFFFE) | (ReadByte(0xFFFF) << 8));
561 | return false;
562 | }
563 |
564 | public bool BVC()
565 | {
566 | if (!OverflowFlag)
567 | {
568 | _cycles++;
569 | _absoluteAddress = (ushort)(PC + _relativeAddress);
570 | if ((_absoluteAddress & 0xFF00) != (PC & 0xFF00))
571 | {
572 | _cycles++;
573 | }
574 |
575 | PC = _absoluteAddress;
576 | }
577 |
578 | return false;
579 | }
580 |
581 | public bool BVS()
582 | {
583 | if (OverflowFlag)
584 | {
585 | _cycles++;
586 | _absoluteAddress = (ushort)(PC + _relativeAddress);
587 | if ((_absoluteAddress & 0xFF00) != (PC & 0xFF00))
588 | {
589 | _cycles++;
590 | }
591 |
592 | PC = _absoluteAddress;
593 | }
594 |
595 | return false;
596 | }
597 |
598 | public bool CLC()
599 | {
600 | CarryFlag = false;
601 | return false;
602 | }
603 |
604 | public bool CLD()
605 | {
606 | DecimalModeFlag = false;
607 | return false;
608 | }
609 |
610 | public bool CLI()
611 | {
612 | InterruptDisableFlag = false;
613 | return false;
614 | }
615 |
616 | public bool CLV()
617 | {
618 | OverflowFlag = false;
619 | return false;
620 | }
621 |
622 | public bool CMP()
623 | {
624 | Fetch();
625 | var temp = A - _fetchedData;
626 | CarryFlag = A >= _fetchedData;
627 | ZeroFlag = (temp & 0x00FF) == 0;
628 | NegativeFlag = (temp & 0x80) != 0;
629 | return true;
630 | }
631 |
632 | public bool CPX()
633 | {
634 | Fetch();
635 | var temp = (ushort)(X - _fetchedData);
636 | CarryFlag = X >= _fetchedData;
637 | ZeroFlag = (temp & 0x00FF) == 0;
638 | NegativeFlag = (temp & 0x80) != 0;
639 | return false;
640 | }
641 |
642 | public bool CPY()
643 | {
644 | Fetch();
645 | var temp = (ushort)(Y - _fetchedData);
646 | CarryFlag = Y >= _fetchedData;
647 | ZeroFlag = (temp & 0x00FF) == 0;
648 | NegativeFlag = (temp & 0x80) != 0;
649 | return false;
650 | }
651 |
652 | public bool DEC()
653 | {
654 | Fetch();
655 | var temp = (ushort)(_fetchedData - 1);
656 | WriteByte(_absoluteAddress, (byte)(temp & 0x00FF));
657 | ZeroFlag = (temp & 0x00FF) == 0;
658 | NegativeFlag = (temp & 0x80) != 0;
659 | return false;
660 | }
661 |
662 | public bool DEX()
663 | {
664 | X--;
665 | ZeroFlag = X == 0;
666 | NegativeFlag = (X & 0x80) != 0;
667 | return false;
668 | }
669 |
670 | public bool DEY()
671 | {
672 | Y--;
673 | ZeroFlag = Y == 0;
674 | NegativeFlag = (Y & 0x80) != 0;
675 | return false;
676 | }
677 |
678 | public bool EOR()
679 | {
680 | Fetch();
681 | A ^= _fetchedData;
682 | ZeroFlag = A == 0;
683 | NegativeFlag = (A & 0x80) != 0;
684 | return true;
685 | }
686 |
687 | public bool INC()
688 | {
689 | Fetch();
690 | var temp = (ushort)(_fetchedData + 1);
691 | WriteByte(_absoluteAddress, (byte)(temp & 0x00FF));
692 | ZeroFlag = (temp & 0x00FF) == 0;
693 | NegativeFlag = (temp & 0x80) != 0;
694 | return false;
695 | }
696 |
697 | public bool INX()
698 | {
699 | X++;
700 | ZeroFlag = X == 0;
701 | NegativeFlag = (X & 0x80) != 0;
702 | return false;
703 | }
704 |
705 | public bool INY()
706 | {
707 | Y++;
708 | ZeroFlag = Y == 0;
709 | NegativeFlag = (Y & 0x80) != 0;
710 | return false;
711 | }
712 |
713 | public bool JMP()
714 | {
715 | PC = _absoluteAddress;
716 | return false;
717 | }
718 |
719 | public bool JSR()
720 | {
721 | PC--;
722 |
723 | WriteByte((ushort)(0x0100 + SP), (byte)((PC >> 8) & 0x00FF));
724 | SP--;
725 | WriteByte((ushort)(0x0100 + SP), (byte)(PC & 0x00FF));
726 | SP--;
727 |
728 | PC = _absoluteAddress;
729 | return false;
730 | }
731 |
732 | public bool LDA()
733 | {
734 | Fetch();
735 | A = _fetchedData;
736 | ZeroFlag = A == 0;
737 | NegativeFlag = (A & 0x80) != 0;
738 | return true;
739 | }
740 |
741 | public bool LDX()
742 | {
743 | Fetch();
744 | X = _fetchedData;
745 | ZeroFlag = X == 0;
746 | NegativeFlag = (X & 0x80) != 0;
747 | return true;
748 | }
749 |
750 | public bool LDY()
751 | {
752 | Fetch();
753 | Y = _fetchedData;
754 | ZeroFlag = Y == 0;
755 | NegativeFlag = (Y & 0x80) != 0;
756 | return true;
757 | }
758 |
759 | public bool LSR()
760 | {
761 | Fetch();
762 | CarryFlag = (_fetchedData & 0x0001) != 0;
763 | var temp = (ushort)(_fetchedData >>> 1);
764 | ZeroFlag = (temp & 0x00FF) == 0;
765 | NegativeFlag = (temp & 0x80) != 0;
766 | if (_instructionSet[_opCode].Mode == AddressingMode.IMP)
767 | {
768 | A = (byte)(temp & 0x00FF);
769 | }
770 | else
771 | {
772 | WriteByte(_absoluteAddress, (byte)(temp & 0x00FF));
773 | }
774 |
775 | return false;
776 | }
777 |
778 | public bool NOP()
779 | {
780 | switch (_opCode)
781 | {
782 | case 0x1C:
783 | case 0x3C:
784 | case 0x5C:
785 | case 0x7C:
786 | case 0xDC:
787 | case 0xFC:
788 | return true;
789 | }
790 |
791 | return false;
792 | }
793 |
794 | public bool ORA()
795 | {
796 | Fetch();
797 | A |= _fetchedData;
798 | ZeroFlag = A == 0;
799 | NegativeFlag = (A & 0x80) != 0;
800 | return true;
801 | }
802 |
803 | public bool PHA()
804 | {
805 | WriteByte((ushort)(0x0100 + SP), A);
806 | SP--;
807 | return false;
808 | }
809 |
810 | public bool PHP()
811 | {
812 | BreakCommandFlag = true;
813 | UnusedFlag = true;
814 | WriteByte((ushort)(0x0100 + SP), _flags);
815 | BreakCommandFlag = false;
816 | UnusedFlag = false;
817 |
818 | SP--;
819 | return false;
820 | }
821 |
822 | public bool PLA()
823 | {
824 | SP++;
825 | A = ReadByte((ushort)(0x0100 + SP));
826 | ZeroFlag = A == 0;
827 | NegativeFlag = (A & 0x80) != 0;
828 | return false;
829 | }
830 |
831 | public bool PLP()
832 | {
833 | SP++;
834 | _flags = ReadByte((ushort)(0x0100 + SP));
835 | UnusedFlag = true;
836 |
837 | return false;
838 | }
839 |
840 | public bool ROL()
841 | {
842 | Fetch();
843 | var temp = (ushort)(_fetchedData << 1) | (CarryFlag ? 1 : 0);
844 | CarryFlag = (temp & 0xFF00) != 0;
845 | ZeroFlag = (temp & 0x00FF) == 0;
846 | NegativeFlag = (temp & 0x80) != 0;
847 | if (_instructionSet[_opCode].Mode == AddressingMode.IMP)
848 | {
849 | A = (byte)(temp & 0x00FF);
850 | }
851 | else
852 | {
853 | WriteByte(_absoluteAddress, (byte)(temp & 0x00FF));
854 | }
855 |
856 | return false;
857 | }
858 |
859 | public bool ROR()
860 | {
861 | Fetch();
862 | var temp = (ushort)(CarryFlag ? 0x80 : 0) | (_fetchedData >>> 1);
863 | CarryFlag = (_fetchedData & 0x01) != 0;
864 | ZeroFlag = (temp & 0x00FF) == 0;
865 | NegativeFlag = (temp & 0x80) != 0;
866 | if (_instructionSet[_opCode].Mode == AddressingMode.IMP)
867 | {
868 | A = (byte)(temp & 0x00FF);
869 | }
870 | else
871 | {
872 | WriteByte(_absoluteAddress, (byte)(temp & 0x00FF));
873 | }
874 |
875 | return false;
876 | }
877 |
878 | public bool RTI()
879 | {
880 | SP++;
881 | _flags = ReadByte((ushort)(0x0100 + SP));
882 | BreakCommandFlag = false;
883 | UnusedFlag = false;
884 |
885 | SP++;
886 | PC = ReadByte((ushort)(0x0100 + SP));
887 |
888 | SP++;
889 | PC |= (ushort)(ReadByte((ushort)(0x0100 + SP)) << 8);
890 |
891 | return false;
892 | }
893 |
894 | public bool RTS()
895 | {
896 | SP++;
897 | PC = ReadByte((ushort)(0x0100 + SP));
898 |
899 | SP++;
900 | PC |= (ushort)(ReadByte((ushort)(0x0100 + SP)) << 8);
901 |
902 | PC++;
903 | return false;
904 | }
905 |
906 | public bool SBC()
907 | {
908 | Fetch();
909 |
910 | var value = (ushort)(_fetchedData ^ 0x00FF);
911 | var temp = (ushort)(A + value + (CarryFlag ? 1 : 0));
912 |
913 | CarryFlag = temp > 255;
914 | ZeroFlag = (temp & 0x00FF) == 0;
915 | NegativeFlag = (temp & 0x80) != 0;
916 |
917 | OverflowFlag = ((temp ^ A) & (temp ^ value) & 0x80) != 0;
918 |
919 | A = (byte)(temp & 0x00FF);
920 | return true;
921 | }
922 |
923 | public bool SEC()
924 | {
925 | CarryFlag = true;
926 | return false;
927 | }
928 |
929 | public bool SED()
930 | {
931 | DecimalModeFlag = true;
932 | return false;
933 | }
934 |
935 | public bool SEI()
936 | {
937 | InterruptDisableFlag = true;
938 | return false;
939 | }
940 |
941 | public bool STA()
942 | {
943 | WriteByte(_absoluteAddress, A);
944 | return false;
945 | }
946 |
947 | public bool STX()
948 | {
949 | WriteByte(_absoluteAddress, X);
950 | return false;
951 | }
952 |
953 | public bool STY()
954 | {
955 | WriteByte(_absoluteAddress, Y);
956 | return false;
957 | }
958 |
959 | public bool TAX()
960 | {
961 | X = A;
962 | ZeroFlag = X == 0;
963 | NegativeFlag = (X & 0x80) != 0;
964 | return false;
965 | }
966 |
967 | public bool TAY()
968 | {
969 | Y = A;
970 | ZeroFlag = Y == 0;
971 | NegativeFlag = (Y & 0x80) != 0;
972 | return false;
973 | }
974 |
975 | public bool TSX()
976 | {
977 | X = SP;
978 | ZeroFlag = X == 0;
979 | NegativeFlag = (X & 0x80) != 0;
980 | return false;
981 | }
982 |
983 | public bool TXA()
984 | {
985 | A = X;
986 | ZeroFlag = A == 0;
987 | NegativeFlag = (A & 0x80) != 0;
988 | return false;
989 | }
990 |
991 | public bool TXS()
992 | {
993 | SP = X;
994 | return false;
995 | }
996 |
997 | public bool TYA()
998 | {
999 | A = Y;
1000 | ZeroFlag = A == 0;
1001 | NegativeFlag = (A & 0x80) != 0;
1002 | return false;
1003 | }
1004 |
1005 | ///
1006 | /// Invalid opcode
1007 | ///
1008 | public bool XXX()
1009 | {
1010 | return false;
1011 | }
1012 | // ReSharper restore InconsistentNaming
1013 |
1014 | private readonly record struct Instruction(string Name, Func OpCode, Func AddressingMode, AddressingMode Mode, byte Cycles = 0);
1015 | private enum AddressingMode
1016 | {
1017 | IMP,
1018 | IMM,
1019 | ZP0,
1020 | ZPX,
1021 | ZPY,
1022 | REL,
1023 | ABS,
1024 | ABX,
1025 | ABY,
1026 | IND,
1027 | IZX,
1028 | IZY
1029 | }
1030 | }
--------------------------------------------------------------------------------