using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using System.Text; using Newtonsoft.Json; using BizHawk.Common.BufferExtensions; using BizHawk.Emulation.Common; using BizHawk.Common; using BizHawk.Common.CollectionExtensions; using BizHawk.Emulation.Common.BizInvoke; namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES { [CoreAttributes( "QuickNes", "", isPorted: true, isReleased: true, portedVersion: "0.7.0", portedUrl: "https://github.com/kode54/QuickNES" )] [ServiceNotApplicable(typeof(IDriveLight))] public partial class QuickNES : IEmulator, IVideoProvider, ISyncSoundProvider, ISaveRam, IInputPollable, IStatable, IDebuggable, ISettable, Cores.Nintendo.NES.INESPPUViewable { static readonly LibQuickNES QN; static readonly Win32LibraryImportResolver Resolver; static QuickNES() { Resolver = new Win32LibraryImportResolver(LibQuickNES.dllname); QN = BizInvoker.GetInvoker(Resolver); } [CoreConstructor("NES")] public QuickNES(CoreComm comm, byte[] file, object Settings, object SyncSettings) { using (FP.Save()) { ServiceProvider = new BasicServiceProvider(this); CoreComm = comm; Context = QN.qn_new(); if (Context == IntPtr.Zero) throw new InvalidOperationException("qn_new() returned NULL"); try { unsafe { fixed (byte* p = file) { Console.WriteLine((IntPtr)p); LibQuickNES.ThrowStringError(QN.qn_loadines(Context, file, file.Length)); } } InitSaveRamBuff(); InitSaveStateBuff(); InitAudio(); InitMemoryDomains(); int mapper = 0; string mappername = Marshal.PtrToStringAnsi(QN.qn_get_mapper(Context, ref mapper)); Console.WriteLine("QuickNES: Booted with Mapper #{0} \"{1}\"", mapper, mappername); BoardName = mappername; CoreComm.VsyncNum = 39375000; CoreComm.VsyncDen = 655171; PutSettings((QuickNESSettings)Settings ?? new QuickNESSettings()); _syncSettings = (QuickNESSyncSettings)SyncSettings ?? new QuickNESSyncSettings(); _syncSettingsNext = _syncSettings.Clone(); SetControllerDefinition(); ComputeBootGod(); ConnectTracer(); } catch { Dispose(); throw; } } } 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; } public IController Controller { get; set; } void SetControllerDefinition() { var def = new ControllerDefinition(); def.Name = "NES Controller"; def.BoolButtons.AddRange(new[] { "Reset", "Power" }); // console buttons if (_syncSettings.LeftPortConnected || _syncSettings.RightPortConnected) def.BoolButtons.AddRange(PadP1.Select(p => p.Name)); if (_syncSettings.LeftPortConnected && _syncSettings.RightPortConnected) def.BoolButtons.AddRange(PadP2.Select(p => p.Name)); ControllerDefinition = def; } private struct PadEnt { public readonly string Name; public readonly int Mask; public PadEnt(string Name, int Mask) { this.Name = Name; this.Mask = Mask; } } private static PadEnt[] GetPadList(int player) { string prefix = string.Format("P{0} ", player); return PadNames.Zip(PadMasks, (s, i) => new PadEnt(prefix + s, i)).ToArray(); } private static string[] PadNames = new[] { "Up", "Down", "Left", "Right", "Start", "Select", "B", "A" }; private static int[] PadMasks = new[] { 16, 32, 64, 128, 8, 4, 2, 1 }; private static PadEnt[] PadP1 = GetPadList(1); private static PadEnt[] PadP2 = GetPadList(2); private int GetPad(IEnumerable buttons) { int ret = 0; foreach (var b in buttons) { if (Controller[b.Name]) ret |= b.Mask; } return ret; } void SetPads(out int j1, out int j2) { if (_syncSettings.LeftPortConnected) j1 = GetPad(PadP1) | unchecked((int)0xffffff00); else j1 = 0; if (_syncSettings.RightPortConnected) j2 = GetPad(_syncSettings.LeftPortConnected ? PadP2 : PadP1) | unchecked((int)0xffffff00); else j2 = 0; } #endregion public void FrameAdvance(bool render, bool rendersound = true) { CheckDisposed(); using (FP.Save()) { if (Controller["Power"]) QN.qn_reset(Context, true); if (Controller["Reset"]) QN.qn_reset(Context, false); int j1, j2; SetPads(out j1, out j2); if (Tracer.Enabled) QN.qn_set_tracecb(Context, _tracecb); else QN.qn_set_tracecb(Context, null); Frame++; LibQuickNES.ThrowStringError(QN.qn_emulate_frame(Context, j1, j2)); IsLagFrame = QN.qn_get_joypad_read_count(Context) == 0; if (IsLagFrame) LagCount++; if (render) Blit(); if (rendersound) DrainAudio(); if (CB1 != null) CB1(); if (CB2 != null) CB2(); } } IntPtr Context; public int Frame { get; private set; } public string SystemId { get { return "NES"; } } public bool DeterministicEmulation { get { return true; } } public string BoardName { get; private set; } public void ResetCounters() { Frame = 0; IsLagFrame = false; LagCount = 0; } public CoreComm CoreComm { get; private set; } #region bootgod public RomStatus? BootGodStatus { get; private set; } public string BootGodName { get; private set; } void ComputeBootGod() { // inefficient, sloppy, etc etc Emulation.Cores.Nintendo.NES.NES.BootGodDB.Initialize(); var chrrom = _memoryDomains["CHR VROM"]; var prgrom = _memoryDomains["PRG ROM"]; var ms = new System.IO.MemoryStream(); for (int i = 0; i < prgrom.Size; i++) ms.WriteByte(prgrom.PeekByte(i)); if (chrrom != null) for (int i = 0; i < chrrom.Size; i++) ms.WriteByte(chrrom.PeekByte(i)); string sha1 = BizHawk.Common.BufferExtensions.BufferExtensions.HashSHA1(ms.ToArray()); Console.WriteLine("Hash for BootGod: {0}", sha1); sha1 = "sha1:" + sha1; // huh? var carts = Emulation.Cores.Nintendo.NES.NES.BootGodDB.Instance.Identify(sha1); // Bail out on ROM's known to not be playable by this core switch (sha1) { case "sha1:E39CA4477D3B96E1CE3A1C61D8055187EA5F1784": // Bill and Ted's Excellent Adventure case "sha1:E8BC7E6BAE7032D571152F6834516535C34C68F0": // Bill and Ted's Excellent Adventure bad dump case "sha1:401023BAE92A38B89F7D0C2E0F023E35F1FFEEFD": // Bill and Ted's Excellent Adventure bad dump case "sha1:6270F9FF2BD0B32A23A45985D9D7FB2793E1CED3": // Bill and Ted's Excellent Adventure overdump dump case "sha1:5E3C02A3A5F6CD4F2442311630F1C44A8E9DC7E2": // Paperboy case "sha1:A65B6BC48E05C4CDBD221B811A17FBF174B45632": // Pro Action Replay (E) (REVA) [!] throw new UnsupportedGameException("Game known to not be playable in this core"); default: break; } if (carts.Count > 0) { Console.WriteLine("BootGod entry found: {0}", carts[0].name); switch (carts[0].system) { case "NES-PAL": case "NES-PAL-A": case "NES-PAL-B": case "Dendy": Console.WriteLine("Bad region {0}! Failing over...", carts[0].system); throw new UnsupportedGameException("Unsupported region!"); default: break; } BootGodStatus = RomStatus.GoodDump; BootGodName = carts[0].name; } else { Console.WriteLine("No BootGod entry found."); BootGodStatus = null; BootGodName = null; } } #endregion public void Dispose() { if (Context != IntPtr.Zero) { QN.qn_delete(Context); Context = IntPtr.Zero; } } void CheckDisposed() { if (Context == IntPtr.Zero) throw new ObjectDisposedException(GetType().Name); } #region SoundProvider public ISoundProvider SoundProvider { get { return null; } } public ISyncSoundProvider SyncSoundProvider { get { return this; } } public bool StartAsyncSound() { return false; } public void EndAsyncSound() { } void InitAudio() { LibQuickNES.ThrowStringError(QN.qn_set_sample_rate(Context, 44100)); } void DrainAudio() { NumSamples = QN.qn_read_audio(Context, MonoBuff, MonoBuff.Length); unsafe { fixed (short* _src = &MonoBuff[0], _dst = &StereoBuff[0]) { short* src = _src; short* dst = _dst; for (int i = 0; i < NumSamples; i++) { *dst++ = *src; *dst++ = *src++; } } } } short[] MonoBuff = new short[1024]; short[] StereoBuff = new short[2048]; int NumSamples = 0; public void GetSamples(out short[] samples, out int nsamp) { samples = StereoBuff; nsamp = NumSamples; } public void DiscardSamples() { } #endregion } }