diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 05919acea9..f22767b768 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -518,28 +518,28 @@ - NES.cs + NES.cs - NES.cs + NES.cs - NES.cs + NES.cs - NES.cs + NES.cs - NES.cs + NES.cs - NES.cs + NES.cs - NES.cs + NES.cs - NES.cs + NES.cs @@ -550,6 +550,30 @@ + + QuickNES.cs + + + QuickNES.cs + + + QuickNES.cs + + + QuickNES.cs + + + QuickNES.cs + + + QuickNES.cs + + + QuickNES.cs + + + QuickNES.cs + diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IDebuggable.cs new file mode 100644 index 0000000000..fe05914eb0 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IDebuggable.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES +{ + public partial class QuickNES : IDebuggable + { + public IDictionary GetCpuFlagsAndRegisters() + { + int[] regs = new int[6]; + var ret = new Dictionary(); + LibQuickNES.qn_get_cpuregs(Context, regs); + ret["A"] = (byte)regs[0]; + ret["X"] = (byte)regs[1]; + ret["Y"] = (byte)regs[2]; + ret["SP"] = (ushort)regs[3]; + ret["PC"] = (ushort)regs[4]; + ret["P"] = (byte)regs[5]; + return ret; + } + + [FeatureNotImplemented] + public void SetCpuRegister(string register, int value) + { + throw new NotImplementedException(); + } + + public bool CanStep(StepType type) { return false; } + + [FeatureNotImplemented] + public void Step(StepType type) { throw new NotImplementedException(); } + + public IMemoryCallbackSystem MemoryCallbacks + { + [FeatureNotImplemented] + get { throw new NotImplementedException(); } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IInputPollable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IInputPollable.cs new file mode 100644 index 0000000000..9164bb7653 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IInputPollable.cs @@ -0,0 +1,17 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES +{ + public partial class QuickNES : IInputPollable + { + public int LagCount { get; set; } + public bool IsLagFrame { get; private set; } + + public IInputCallbackSystem InputCallbacks + { + [FeatureNotImplemented] + get { throw new NotImplementedException(); } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IMemoryDomains.cs new file mode 100644 index 0000000000..b56a11e446 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IMemoryDomains.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES +{ + public partial class QuickNES + { + unsafe void InitMemoryDomains() + { + List mm = new List(); + for (int i = 0; ; i++) + { + IntPtr data = IntPtr.Zero; + int size = 0; + bool writable = false; + IntPtr name = IntPtr.Zero; + + if (!LibQuickNES.qn_get_memory_area(Context, i, ref data, ref size, ref writable, ref name)) + break; + + if (data != IntPtr.Zero && size > 0 && name != IntPtr.Zero) + { + mm.Add(MemoryDomain.FromIntPtr(Marshal.PtrToStringAnsi(name), size, MemoryDomain.Endian.Little, data, writable)); + } + } + // add system bus + mm.Add(new MemoryDomain + ( + "System Bus", + 0x10000, + MemoryDomain.Endian.Unknown, + delegate(int addr) + { + if (addr < 0 || addr >= 0x10000) + { + throw new ArgumentOutOfRangeException(); + } + + return LibQuickNES.qn_peek_prgbus(Context, addr); + }, + delegate(int addr, byte val) + { + if (addr < 0 || addr >= 0x10000) + { + throw new ArgumentOutOfRangeException(); + } + + LibQuickNES.qn_poke_prgbus(Context, addr, val); + } + )); + + _memoryDomains = new MemoryDomainList(mm, 0); + (ServiceProvider as BasicServiceProvider).Register(_memoryDomains); + } + + private IMemoryDomains _memoryDomains; + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.INESPPUViewable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.INESPPUViewable.cs new file mode 100644 index 0000000000..de9382909c --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.INESPPUViewable.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Nintendo.NES; +using System.Runtime.InteropServices; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES +{ + public partial class QuickNES : INESPPUViewable + { + // todo: don't just call the callbacks at the end of frame; use the scanline info + private Action CB1; + private Action CB2; + + public int[] GetPalette() + { + return VideoPalette; + } + + private byte R2000 { get { return LibQuickNES.qn_get_reg2000(Context); } } + + public bool BGBaseHigh + { + get { return (R2000 & 0x10) != 0; } + } + + public bool SPBaseHigh + { + get { return (R2000 & 0x08) != 0; } + } + + public bool SPTall + { + get { return (R2000 & 0x20) != 0; } + } + + private byte[] ppubusbuf = new byte[0x3000]; + public byte[] GetPPUBus() + { + LibQuickNES.qn_peek_ppubus(Context, ppubusbuf); + return ppubusbuf; + } + + private byte[] palrambuf = new byte[0x20]; + public byte[] GetPalRam() + { + Marshal.Copy(LibQuickNES.qn_get_palmem(Context), palrambuf, 0, 0x20); + return palrambuf; + } + + byte[] oambuf = new byte[0x100]; + public byte[] GetOam() + { + Marshal.Copy(LibQuickNES.qn_get_oammem(Context), oambuf, 0, 0x100); + return oambuf; + } + + public byte PeekPPU(int addr) + { + return LibQuickNES.qn_peek_ppu(Context, addr); + } + + // we don't use quicknes's MMC5 at all, so these three methods are just stubs + public byte[] GetExTiles() + { + throw new InvalidOperationException(); + } + + public bool ExActive + { + get { return false; } + } + + public byte[] GetExRam() + { + throw new InvalidOperationException(); + } + + public MemoryDomain GetCHRROM() + { + return _memoryDomains["CHR VROM"]; + } + + public void InstallCallback1(Action cb, int sl) + { + CB1 = cb; + } + + public void InstallCallback2(Action cb, int sl) + { + CB2 = cb; + } + + public void RemoveCallback1() + { + CB1 = null; + } + + public void RemoveCallback2() + { + CB2 = null; + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISaveRam.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISaveRam.cs new file mode 100644 index 0000000000..afbd2d0f04 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISaveRam.cs @@ -0,0 +1,35 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES +{ + public partial class QuickNES : ISaveRam + { + public byte[] CloneSaveRam() + { + LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_save(Context, SaveRamBuff, SaveRamBuff.Length)); + return (byte[])SaveRamBuff.Clone(); + } + + public void StoreSaveRam(byte[] data) + { + LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_load(Context, data, data.Length)); + } + + public bool SaveRamModified + { + get + { + return LibQuickNES.qn_has_battery_ram(Context); + } + } + + private byte[] SaveRamBuff; + + private void InitSaveRamBuff() + { + int size = 0; + LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_size(Context, ref size)); + SaveRamBuff = new byte[size]; + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISettable.cs new file mode 100644 index 0000000000..365ca5fa0d --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISettable.cs @@ -0,0 +1,170 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; + +using Newtonsoft.Json; + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES +{ + public partial class QuickNES : ISettable + { + public QuickNESSettings GetSettings() + { + return _settings.Clone(); + } + + public QuickNESSyncSettings GetSyncSettings() + { + return _syncSettingsNext.Clone(); + } + + public bool PutSettings(QuickNESSettings o) + { + _settings = o; + LibQuickNES.qn_set_sprite_limit(Context, _settings.NumSprites); + RecalculateCrops(); + CalculatePalette(); + return false; + } + + public bool PutSyncSettings(QuickNESSyncSettings o) + { + bool ret = QuickNESSyncSettings.NeedsReboot(_syncSettings, o); + _syncSettingsNext = o; + return ret; + } + + private QuickNESSettings _settings; + + /// + /// the syncsettings that this run of emulation is using (was passed to ctor) + /// + private QuickNESSyncSettings _syncSettings; + + /// + /// the syncsettings that were requested but won't be used yet + /// + private QuickNESSyncSettings _syncSettingsNext; + + public class QuickNESSettings + { + [DefaultValue(8)] + [Description("Set the number of sprites visible per line. 0 hides all sprites, 8 behaves like a normal NES, and 64 is maximum.")] + [DisplayName("Visible Sprites")] + public int NumSprites + { + get { return _NumSprites; } + set { _NumSprites = Math.Min(64, Math.Max(0, value)); } + } + + [JsonIgnore] + private int _NumSprites; + + [DefaultValue(false)] + [Description("Clip the left and right 8 pixels of the display, which sometimes contain nametable garbage.")] + [DisplayName("Clip Left and Right")] + public bool ClipLeftAndRight { get; set; } + + [DefaultValue(true)] + [Description("Clip the top and bottom 8 pixels of the display, which sometimes contain nametable garbage.")] + [DisplayName("Clip Top and Bottom")] + public bool ClipTopAndBottom { get; set; } + + [Browsable(false)] + public byte[] Palette + { + get { return _Palette; } + set + { + if (value == null) + throw new ArgumentNullException(); + else if (value.Length == 64 * 8 * 3) + _Palette = value; + else + throw new ArgumentOutOfRangeException(); + } + } + + [JsonIgnore] + private byte[] _Palette; + + public QuickNESSettings Clone() + { + var ret = (QuickNESSettings)MemberwiseClone(); + ret._Palette = (byte[])_Palette.Clone(); + return ret; + } + + public QuickNESSettings() + { + SettingsUtil.SetDefaultValues(this); + SetDefaultColors(); + } + + public void SetNesHawkPalette(int[,] pal) + { + if (pal.GetLength(0) != 64 || pal.GetLength(1) != 3) + { + throw new ArgumentOutOfRangeException(); + } + + for (int c = 0; c < 512; c++) + { + int a = c & 63; + byte[] inp = { (byte)pal[a, 0], (byte)pal[a, 1], (byte)pal[a, 2] }; + byte[] outp = new byte[3]; + Nes_NTSC_Colors.Emphasis(inp, outp, c); + _Palette[c * 3] = outp[0]; + _Palette[c * 3 + 1] = outp[1]; + _Palette[c * 3 + 2] = outp[2]; + } + } + + private static byte[] GetDefaultColors() + { + IntPtr src = LibQuickNES.qn_get_default_colors(); + byte[] ret = new byte[1536]; + Marshal.Copy(src, ret, 0, 1536); + return ret; + } + + public void SetDefaultColors() + { + _Palette = GetDefaultColors(); + } + } + + public class QuickNESSyncSettings + { + [DefaultValue(true)] + [DisplayName("Left Port Connected")] + [Description("Specifies whether or not the Left (Player 1) Controller is connected")] + public bool LeftPortConnected { get; set; } + + [DefaultValue(false)] + [DisplayName("Right Port Connected")] + [Description("Specifies whether or not the Right (Player 2) Controller is connected")] + public bool RightPortConnected { get; set; } + + public QuickNESSyncSettings() + { + SettingsUtil.SetDefaultValues(this); + } + + public QuickNESSyncSettings Clone() + { + return (QuickNESSyncSettings)MemberwiseClone(); + } + + public static bool NeedsReboot(QuickNESSyncSettings x, QuickNESSyncSettings y) + { + // the core can handle dynamic plugging and unplugging, but that changes + // the controllerdefinition, and we're not ready for that + return !DeepEquality.DeepEquals(x, y); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IStatable.cs new file mode 100644 index 0000000000..1a1fbbe0d6 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IStatable.cs @@ -0,0 +1,81 @@ +using System; +using System.IO; + +using BizHawk.Common.BufferExtensions; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES +{ + public partial class QuickNES : IStatable + { + public bool BinarySaveStatesPreferred { get { return true; } } + + public void SaveStateText(System.IO.TextWriter writer) + { + CheckDisposed(); + var temp = SaveStateBinary(); + temp.SaveAsHexFast(writer); + // write extra copy of stuff we don't use + writer.WriteLine("Frame {0}", Frame); + } + + public void LoadStateText(System.IO.TextReader reader) + { + CheckDisposed(); + string hex = reader.ReadLine(); + byte[] state = new byte[hex.Length / 2]; + state.ReadFromHexFast(hex); + LoadStateBinary(new System.IO.BinaryReader(new System.IO.MemoryStream(state))); + } + + public void SaveStateBinary(System.IO.BinaryWriter writer) + { + CheckDisposed(); + LibQuickNES.ThrowStringError(LibQuickNES.qn_state_save(Context, SaveStateBuff, SaveStateBuff.Length)); + writer.Write(SaveStateBuff.Length); + writer.Write(SaveStateBuff); + // other variables + writer.Write(IsLagFrame); + writer.Write(LagCount); + writer.Write(Frame); + } + + public void LoadStateBinary(System.IO.BinaryReader reader) + { + CheckDisposed(); + int len = reader.ReadInt32(); + if (len != SaveStateBuff.Length) + throw new InvalidOperationException("Unexpected savestate buffer length!"); + reader.Read(SaveStateBuff, 0, SaveStateBuff.Length); + LibQuickNES.ThrowStringError(LibQuickNES.qn_state_load(Context, SaveStateBuff, SaveStateBuff.Length)); + // other variables + IsLagFrame = reader.ReadBoolean(); + LagCount = reader.ReadInt32(); + Frame = reader.ReadInt32(); + } + + public byte[] SaveStateBinary() + { + CheckDisposed(); + var ms = new System.IO.MemoryStream(SaveStateBuff2, true); + var bw = new System.IO.BinaryWriter(ms); + SaveStateBinary(bw); + bw.Flush(); + if (ms.Position != SaveStateBuff2.Length) + throw new InvalidOperationException("Unexpected savestate length!"); + bw.Close(); + return SaveStateBuff2; + } + + private byte[] SaveStateBuff; + private byte[] SaveStateBuff2; + + private void InitSaveStateBuff() + { + int size = 0; + LibQuickNES.ThrowStringError(LibQuickNES.qn_state_size(Context, ref size)); + SaveStateBuff = new byte[size]; + SaveStateBuff2 = new byte[size + 13]; + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IVideoProvider.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IVideoProvider.cs new file mode 100644 index 0000000000..ac87d84b9c --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IVideoProvider.cs @@ -0,0 +1,59 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES +{ + public partial class QuickNES : IVideoProvider + { + public int BufferWidth { get; private set; } + public int BufferHeight { get; private set; } + public int BackgroundColor { get { return unchecked((int)0xff000000); } } + + public int[] GetVideoBuffer() + { + return VideoOutput; + } + + public int VirtualWidth + { + get { return (int)(BufferWidth * 1.146); } + } + + public int VirtualHeight + { + get { return BufferHeight; } + } + + private int[] VideoOutput = new int[256 * 240]; + private int[] VideoPalette = new int[512]; + + private int cropleft = 0; + private int cropright = 0; + private int croptop = 0; + private int cropbottom = 0; + + private void RecalculateCrops() + { + cropright = cropleft = _settings.ClipLeftAndRight ? 8 : 0; + cropbottom = croptop = _settings.ClipTopAndBottom ? 8 : 0; + BufferWidth = 256 - cropleft - cropright; + BufferHeight = 240 - croptop - cropbottom; + } + + private void CalculatePalette() + { + for (int i = 0; i < 512; i++) + { + VideoPalette[i] = + _settings.Palette[i * 3] << 16 | + _settings.Palette[i * 3 + 1] << 8 | + _settings.Palette[i * 3 + 2] | + unchecked((int)0xff000000); + } + } + + private void Blit() + { + LibQuickNES.qn_blit(Context, VideoOutput, VideoPalette, cropleft, croptop, cropright, cropbottom); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs index 49f9f259df..b0afa38b03 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs @@ -23,40 +23,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES portedUrl: "https://github.com/kode54/QuickNES" )] [ServiceNotApplicable(typeof(IDriveLight))] - public class QuickNES : IEmulator, IVideoProvider, ISyncSoundProvider, ISaveRam, IInputPollable, + public partial class QuickNES : IEmulator, IVideoProvider, ISyncSoundProvider, ISaveRam, IInputPollable, IStatable, IDebuggable, ISettable, Cores.Nintendo.NES.INESPPUViewable { - #region FPU precision - - private class FPCtrl : IDisposable - { - [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] - public static extern uint _control87(uint @new, uint mask); - - public static void PrintCurrentFP() - { - uint curr = _control87(0, 0); - Console.WriteLine("Current FP word: 0x{0:x8}", curr); - } - - uint cw; - - public IDisposable Save() - { - cw = _control87(0, 0); - _control87(0x00000, 0x30000); - return this; - } - public void Dispose() - { - _control87(cw, 0x30000); - } - } - - FPCtrl FP = new FPCtrl(); - - #endregion - static QuickNES() { LibQuickNES.qn_setup_mappers(); @@ -90,8 +59,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES CoreComm.VsyncDen = 655171; PutSettings((QuickNESSettings)Settings ?? new QuickNESSettings()); - _SyncSettings = (QuickNESSyncSettings)SyncSettings ?? new QuickNESSyncSettings(); - _SyncSettings_next = _SyncSettings.Clone(); + _syncSettings = (QuickNESSyncSettings)SyncSettings ?? new QuickNESSyncSettings(); + _syncSettingsNext = _syncSettings.Clone(); SetControllerDefinition(); ComputeBootGod(); @@ -106,6 +75,37 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES public IEmulatorServiceProvider ServiceProvider { get; private set; } + #region FPU precision + + private class FPCtrl : IDisposable + { + [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern uint _control87(uint @new, uint mask); + + public static void PrintCurrentFP() + { + uint curr = _control87(0, 0); + Console.WriteLine("Current FP word: 0x{0:x8}", curr); + } + + uint cw; + + public IDisposable Save() + { + cw = _control87(0, 0); + _control87(0x00000, 0x30000); + return this; + } + public void Dispose() + { + _control87(cw, 0x30000); + } + } + + FPCtrl FP = new FPCtrl(); + + #endregion + #region Controller public ControllerDefinition ControllerDefinition { get; private set; } @@ -116,9 +116,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES var def = new ControllerDefinition(); def.Name = "NES Controller"; def.BoolButtons.AddRange(new[] { "Reset", "Power" }); // console buttons - if (_SyncSettings.LeftPortConnected || _SyncSettings.RightPortConnected) + if (_syncSettings.LeftPortConnected || _syncSettings.RightPortConnected) def.BoolButtons.AddRange(PadP1.Select(p => p.Name)); - if (_SyncSettings.LeftPortConnected && _SyncSettings.RightPortConnected) + if (_syncSettings.LeftPortConnected && _syncSettings.RightPortConnected) def.BoolButtons.AddRange(PadP2.Select(p => p.Name)); ControllerDefinition = def; } @@ -165,12 +165,12 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES void SetPads(out int j1, out int j2) { - if (_SyncSettings.LeftPortConnected) + if (_syncSettings.LeftPortConnected) j1 = GetPad(PadP1) | unchecked((int)0xffffff00); else j1 = 0; - if (_SyncSettings.RightPortConnected) - j2 = GetPad(_SyncSettings.LeftPortConnected ? PadP2 : PadP1) | unchecked((int)0xffffff00); + if (_syncSettings.RightPortConnected) + j2 = GetPad(_syncSettings.LeftPortConnected ? PadP2 : PadP1) | unchecked((int)0xffffff00); else j2 = 0; } @@ -206,52 +206,14 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES } } - #region state - IntPtr Context; public int Frame { get; private set; } - public int LagCount { get; set; } - public bool IsLagFrame { get; private set; } - - #endregion public string SystemId { get { return "NES"; } } public bool DeterministicEmulation { get { return true; } } public string BoardName { get; private set; } - #region saveram - - byte[] SaveRamBuff; - - void InitSaveRamBuff() - { - int size = 0; - LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_size(Context, ref size)); - SaveRamBuff = new byte[size]; - } - - public byte[] CloneSaveRam() - { - LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_save(Context, SaveRamBuff, SaveRamBuff.Length)); - return (byte[])SaveRamBuff.Clone(); - } - - public void StoreSaveRam(byte[] data) - { - LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_load(Context, data, data.Length)); - } - - public bool SaveRamModified - { - get - { - return LibQuickNES.qn_has_battery_ram(Context); - } - } - - #endregion - public void ResetCounters() { Frame = 0; @@ -259,167 +221,12 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES LagCount = 0; } - #region savestates - - byte[] SaveStateBuff; - byte[] SaveStateBuff2; - - void InitSaveStateBuff() - { - int size = 0; - LibQuickNES.ThrowStringError(LibQuickNES.qn_state_size(Context, ref size)); - SaveStateBuff = new byte[size]; - SaveStateBuff2 = new byte[size + 13]; - } - - public void SaveStateText(System.IO.TextWriter writer) - { - CheckDisposed(); - var temp = SaveStateBinary(); - temp.SaveAsHexFast(writer); - // write extra copy of stuff we don't use - writer.WriteLine("Frame {0}", Frame); - } - - public void LoadStateText(System.IO.TextReader reader) - { - CheckDisposed(); - string hex = reader.ReadLine(); - byte[] state = new byte[hex.Length / 2]; - state.ReadFromHexFast(hex); - LoadStateBinary(new System.IO.BinaryReader(new System.IO.MemoryStream(state))); - } - - public void SaveStateBinary(System.IO.BinaryWriter writer) - { - CheckDisposed(); - LibQuickNES.ThrowStringError(LibQuickNES.qn_state_save(Context, SaveStateBuff, SaveStateBuff.Length)); - writer.Write(SaveStateBuff.Length); - writer.Write(SaveStateBuff); - // other variables - writer.Write(IsLagFrame); - writer.Write(LagCount); - writer.Write(Frame); - } - - public void LoadStateBinary(System.IO.BinaryReader reader) - { - CheckDisposed(); - int len = reader.ReadInt32(); - if (len != SaveStateBuff.Length) - throw new InvalidOperationException("Unexpected savestate buffer length!"); - reader.Read(SaveStateBuff, 0, SaveStateBuff.Length); - LibQuickNES.ThrowStringError(LibQuickNES.qn_state_load(Context, SaveStateBuff, SaveStateBuff.Length)); - // other variables - IsLagFrame = reader.ReadBoolean(); - LagCount = reader.ReadInt32(); - Frame = reader.ReadInt32(); - } - - public byte[] SaveStateBinary() - { - CheckDisposed(); - var ms = new System.IO.MemoryStream(SaveStateBuff2, true); - var bw = new System.IO.BinaryWriter(ms); - SaveStateBinary(bw); - bw.Flush(); - if (ms.Position != SaveStateBuff2.Length) - throw new InvalidOperationException("Unexpected savestate length!"); - bw.Close(); - return SaveStateBuff2; - } - - public bool BinarySaveStatesPreferred { get { return true; } } - - #endregion - public CoreComm CoreComm { get; private set; } - #region debugging - - unsafe void InitMemoryDomains() - { - List mm = new List(); - for (int i = 0; ; i++) - { - IntPtr data = IntPtr.Zero; - int size = 0; - bool writable = false; - IntPtr name = IntPtr.Zero; - - if (!LibQuickNES.qn_get_memory_area(Context, i, ref data, ref size, ref writable, ref name)) - break; - - if (data != IntPtr.Zero && size > 0 && name != IntPtr.Zero) - { - mm.Add(MemoryDomain.FromIntPtr(Marshal.PtrToStringAnsi(name), size, MemoryDomain.Endian.Little, data, writable)); - } - } - // add system bus - mm.Add(new MemoryDomain - ( - "System Bus", - 0x10000, - MemoryDomain.Endian.Unknown, - delegate(int addr) - { - if (addr < 0 || addr >= 0x10000) - throw new ArgumentOutOfRangeException(); - return LibQuickNES.qn_peek_prgbus(Context, addr); - }, - delegate(int addr, byte val) - { - if (addr < 0 || addr >= 0x10000) - throw new ArgumentOutOfRangeException(); - LibQuickNES.qn_poke_prgbus(Context, addr, val); - } - )); - - _memoryDomains = new MemoryDomainList(mm, 0); - (ServiceProvider as BasicServiceProvider).Register(_memoryDomains); - } - - private IMemoryDomains _memoryDomains; - - public IDictionary GetCpuFlagsAndRegisters() - { - int[] regs = new int[6]; - var ret = new Dictionary(); - LibQuickNES.qn_get_cpuregs(Context, regs); - ret["A"] = (byte)regs[0]; - ret["X"] = (byte)regs[1]; - ret["Y"] = (byte)regs[2]; - ret["SP"] = (ushort)regs[3]; - ret["PC"] = (ushort)regs[4]; - ret["P"] = (byte)regs[5]; - return ret; - } - - [FeatureNotImplemented] - public void SetCpuRegister(string register, int value) - { - throw new NotImplementedException(); - } - - public bool CanStep(StepType type) { return false; } - - [FeatureNotImplemented] - public void Step(StepType type) { throw new NotImplementedException(); } - - public IMemoryCallbackSystem MemoryCallbacks - { - [FeatureNotImplemented] - get { throw new NotImplementedException(); } - } - - #endregion - - public IInputCallbackSystem InputCallbacks { [FeatureNotImplemented]get { throw new NotImplementedException(); } } - #region bootgod public RomStatus? BootGodStatus { get; private set; } @@ -470,158 +277,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES #endregion - #region settings - - public class QuickNESSettings - { - [DefaultValue(8)] - [Description("Set the number of sprites visible per line. 0 hides all sprites, 8 behaves like a normal NES, and 64 is maximum.")] - [DisplayName("Visible Sprites")] - public int NumSprites - { - get { return _NumSprites; } - set { _NumSprites = Math.Min(64, Math.Max(0, value)); } - } - [JsonIgnore] - private int _NumSprites; - - [DefaultValue(false)] - [Description("Clip the left and right 8 pixels of the display, which sometimes contain nametable garbage.")] - [DisplayName("Clip Left and Right")] - public bool ClipLeftAndRight { get; set; } - - [DefaultValue(true)] - [Description("Clip the top and bottom 8 pixels of the display, which sometimes contain nametable garbage.")] - [DisplayName("Clip Top and Bottom")] - public bool ClipTopAndBottom { get; set; } - - [Browsable(false)] - public byte[] Palette - { - get { return _Palette; } - set - { - if (value == null) - throw new ArgumentNullException(); - else if (value.Length == 64 * 8 * 3) - _Palette = value; - else - throw new ArgumentOutOfRangeException(); - } - } - [JsonIgnore] - private byte[] _Palette; - - public QuickNESSettings Clone() - { - var ret = (QuickNESSettings)MemberwiseClone(); - ret._Palette = (byte[])_Palette.Clone(); - return ret; - } - public QuickNESSettings() - { - SettingsUtil.SetDefaultValues(this); - SetDefaultColors(); - } - - public void SetNesHawkPalette(int[,] pal) - { - if (pal.GetLength(0) != 64 || pal.GetLength(1) != 3) - throw new ArgumentOutOfRangeException(); - for (int c = 0; c < 512; c++) - { - int a = c & 63; - byte[] inp = { (byte)pal[a, 0], (byte)pal[a, 1], (byte)pal[a, 2] }; - byte[] outp = new byte[3]; - Nes_NTSC_Colors.Emphasis(inp, outp, c); - _Palette[c * 3] = outp[0]; - _Palette[c * 3 + 1] = outp[1]; - _Palette[c * 3 + 2] = outp[2]; - } - } - - static byte[] GetDefaultColors() - { - IntPtr src = LibQuickNES.qn_get_default_colors(); - byte[] ret = new byte[1536]; - Marshal.Copy(src, ret, 0, 1536); - return ret; - } - - public void SetDefaultColors() - { - _Palette = GetDefaultColors(); - } - } - - public class QuickNESSyncSettings - { - [DefaultValue(true)] - [DisplayName("Left Port Connected")] - [Description("Specifies whether or not the Left (Player 1) Controller is connected")] - public bool LeftPortConnected { get; set; } - - [DefaultValue(false)] - [DisplayName("Right Port Connected")] - [Description("Specifies whether or not the Right (Player 2) Controller is connected")] - public bool RightPortConnected { get; set; } - - public QuickNESSyncSettings() - { - SettingsUtil.SetDefaultValues(this); - } - - public QuickNESSyncSettings Clone() - { - return (QuickNESSyncSettings)MemberwiseClone(); - } - - public static bool NeedsReboot(QuickNESSyncSettings x, QuickNESSyncSettings y) - { - // the core can handle dynamic plugging and unplugging, but that changes - // the controllerdefinition, and we're not ready for that - return !DeepEquality.DeepEquals(x, y); - } - } - - QuickNESSettings _Settings; - /// - /// the syncsettings that this run of emulation is using (was passed to ctor) - /// - QuickNESSyncSettings _SyncSettings; - /// - /// the syncsettings that were requested but won't be used yet - /// - QuickNESSyncSettings _SyncSettings_next; - - public QuickNESSettings GetSettings() - { - return _Settings.Clone(); - } - - public QuickNESSyncSettings GetSyncSettings() - { - return _SyncSettings_next.Clone(); - } - - public bool PutSettings(QuickNESSettings o) - { - _Settings = o; - LibQuickNES.qn_set_sprite_limit(Context, _Settings.NumSprites); - RecalculateCrops(); - CalculatePalette(); - return false; - } - - public bool PutSyncSettings(QuickNESSyncSettings o) - { - bool ret = QuickNESSyncSettings.NeedsReboot(_SyncSettings, o); - _SyncSettings_next = o; - return ret; - } - - #endregion - public void Dispose() { if (Context != IntPtr.Zero) @@ -637,50 +292,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES throw new ObjectDisposedException(GetType().Name); } - #region VideoProvider - - int[] VideoOutput = new int[256 * 240]; - int[] VideoPalette = new int[512]; - - int cropleft = 0; - int cropright = 0; - int croptop = 0; - int cropbottom = 0; - - void RecalculateCrops() - { - cropright = cropleft = _Settings.ClipLeftAndRight ? 8 : 0; - cropbottom = croptop = _Settings.ClipTopAndBottom ? 8 : 0; - BufferWidth = 256 - cropleft - cropright; - BufferHeight = 240 - croptop - cropbottom; - } - - void CalculatePalette() - { - for (int i = 0; i < 512; i++) - { - VideoPalette[i] = - _Settings.Palette[i * 3] << 16 | - _Settings.Palette[i * 3 + 1] << 8 | - _Settings.Palette[i * 3 + 2] | - unchecked((int)0xff000000); - } - } - - void Blit() - { - LibQuickNES.qn_blit(Context, VideoOutput, VideoPalette, cropleft, croptop, cropright, cropbottom); - } - - public int[] GetVideoBuffer() { return VideoOutput; } - public int VirtualWidth { get { return (int)(BufferWidth * 1.146); } } - public int VirtualHeight { get { return BufferHeight; } } - public int BufferWidth { get; private set; } - public int BufferHeight { get; private set; } - public int BackgroundColor { get { return unchecked((int)0xff000000); } } - - #endregion - #region SoundProvider public ISoundProvider SoundProvider { get { return null; } } @@ -726,102 +337,5 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES } #endregion - - #region INESPPUViewable - - // todo: don't just call the callbacks at the end of frame; use the scanline info - Action CB1; - Action CB2; - - public int[] GetPalette() - { - return VideoPalette; - } - - private byte R2000 { get { return LibQuickNES.qn_get_reg2000(Context); } } - - public bool BGBaseHigh - { - get { return (R2000 & 0x10) != 0; } - } - - public bool SPBaseHigh - { - get { return (R2000 & 0x08) != 0; } - } - - public bool SPTall - { - get { return (R2000 & 0x20) != 0; } - } - - byte[] ppubusbuf = new byte[0x3000]; - public byte[] GetPPUBus() - { - LibQuickNES.qn_peek_ppubus(Context, ppubusbuf); - return ppubusbuf; - } - - byte[] palrambuf = new byte[0x20]; - public byte[] GetPalRam() - { - Marshal.Copy(LibQuickNES.qn_get_palmem(Context), palrambuf, 0, 0x20); - return palrambuf; - } - - byte[] oambuf = new byte[0x100]; - public byte[] GetOam() - { - Marshal.Copy(LibQuickNES.qn_get_oammem(Context), oambuf, 0, 0x100); - return oambuf; - } - - public byte PeekPPU(int addr) - { - return LibQuickNES.qn_peek_ppu(Context, addr); - } - - // we don't use quicknes's MMC5 at all, so these three methods are just stubs - public byte[] GetExTiles() - { - throw new InvalidOperationException(); - } - - public bool ExActive - { - get { return false; } - } - - public byte[] GetExRam() - { - throw new InvalidOperationException(); - } - - public MemoryDomain GetCHRROM() - { - return _memoryDomains["CHR VROM"]; - } - - public void InstallCallback1(Action cb, int sl) - { - CB1 = cb; - } - - public void InstallCallback2(Action cb, int sl) - { - CB2 = cb; - } - - public void RemoveCallback1() - { - CB1 = null; - } - - public void RemoveCallback2() - { - CB2 = null; - } - - #endregion } }