├── 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 | } --------------------------------------------------------------------------------