2014-01-05 20:58:36 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2014-07-03 18:54:53 +00:00
|
|
|
|
using System.ComponentModel;
|
2014-01-05 20:58:36 +00:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Runtime.InteropServices;
|
2014-07-03 18:54:53 +00:00
|
|
|
|
using System.Text;
|
|
|
|
|
|
2014-01-09 23:50:10 +00:00
|
|
|
|
using Newtonsoft.Json;
|
2014-01-05 20:58:36 +00:00
|
|
|
|
|
2014-07-03 18:54:53 +00:00
|
|
|
|
using BizHawk.Common.BufferExtensions;
|
|
|
|
|
using BizHawk.Emulation.Common;
|
2014-07-17 22:27:09 +00:00
|
|
|
|
using BizHawk.Common;
|
2014-10-29 18:58:43 +00:00
|
|
|
|
using BizHawk.Common.CollectionExtensions;
|
2014-07-03 18:54:53 +00:00
|
|
|
|
|
2014-01-05 20:58:36 +00:00
|
|
|
|
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
|
|
|
|
|
{
|
2014-04-25 01:19:57 +00:00
|
|
|
|
[CoreAttributes(
|
|
|
|
|
"QuickNes",
|
2014-06-01 01:57:22 +00:00
|
|
|
|
"",
|
2014-04-25 01:19:57 +00:00
|
|
|
|
isPorted: true,
|
2014-06-01 01:57:22 +00:00
|
|
|
|
isReleased: true,
|
|
|
|
|
portedVersion: "0.7.0",
|
|
|
|
|
portedUrl: "https://github.com/kode54/QuickNES"
|
2014-04-25 01:19:57 +00:00
|
|
|
|
)]
|
2014-12-12 01:58:12 +00:00
|
|
|
|
[ServiceNotApplicable(typeof(IDriveLight))]
|
2015-01-16 01:38:47 +00:00
|
|
|
|
public partial class QuickNES : IEmulator, IVideoProvider, ISyncSoundProvider, ISaveRam, IInputPollable,
|
2014-12-17 03:27:44 +00:00
|
|
|
|
IStatable, IDebuggable, ISettable<QuickNES.QuickNESSettings, QuickNES.QuickNESSyncSettings>, Cores.Nintendo.NES.INESPPUViewable
|
2014-01-05 20:58:36 +00:00
|
|
|
|
{
|
2014-01-05 22:32:49 +00:00
|
|
|
|
static QuickNES()
|
|
|
|
|
{
|
|
|
|
|
LibQuickNES.qn_setup_mappers();
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-23 19:06:37 +00:00
|
|
|
|
[CoreConstructor("NES")]
|
2014-11-01 17:44:04 +00:00
|
|
|
|
public QuickNES(CoreComm comm, byte[] file, object Settings, object SyncSettings)
|
2014-01-05 20:58:36 +00:00
|
|
|
|
{
|
2014-01-06 19:31:13 +00:00
|
|
|
|
using (FP.Save())
|
2014-01-05 20:58:36 +00:00
|
|
|
|
{
|
2014-12-04 03:38:30 +00:00
|
|
|
|
ServiceProvider = new BasicServiceProvider(this);
|
2014-08-23 19:06:37 +00:00
|
|
|
|
CoreComm = comm;
|
2014-01-05 20:58:36 +00:00
|
|
|
|
|
2014-01-06 19:31:13 +00:00
|
|
|
|
Context = LibQuickNES.qn_new();
|
|
|
|
|
if (Context == IntPtr.Zero)
|
|
|
|
|
throw new InvalidOperationException("qn_new() returned NULL");
|
|
|
|
|
try
|
|
|
|
|
{
|
2014-09-12 15:39:04 +00:00
|
|
|
|
LibQuickNES.ThrowStringError(LibQuickNES.qn_loadines(Context, file, file.Length));
|
2014-01-06 19:31:13 +00:00
|
|
|
|
|
|
|
|
|
InitSaveRamBuff();
|
|
|
|
|
InitSaveStateBuff();
|
|
|
|
|
InitAudio();
|
2014-01-06 22:14:24 +00:00
|
|
|
|
InitMemoryDomains();
|
2014-01-07 01:52:00 +00:00
|
|
|
|
|
|
|
|
|
int mapper = 0;
|
|
|
|
|
string mappername = Marshal.PtrToStringAnsi(LibQuickNES.qn_get_mapper(Context, ref mapper));
|
|
|
|
|
Console.WriteLine("QuickNES: Booted with Mapper #{0} \"{1}\"", mapper, mappername);
|
|
|
|
|
BoardName = mappername;
|
|
|
|
|
CoreComm.VsyncNum = 39375000;
|
|
|
|
|
CoreComm.VsyncDen = 655171;
|
2014-10-19 01:22:47 +00:00
|
|
|
|
PutSettings((QuickNESSettings)Settings ?? new QuickNESSettings());
|
2014-07-23 02:48:55 +00:00
|
|
|
|
|
2015-01-16 01:38:47 +00:00
|
|
|
|
_syncSettings = (QuickNESSyncSettings)SyncSettings ?? new QuickNESSyncSettings();
|
|
|
|
|
_syncSettingsNext = _syncSettings.Clone();
|
2014-11-01 17:44:04 +00:00
|
|
|
|
|
|
|
|
|
SetControllerDefinition();
|
2014-07-23 02:48:55 +00:00
|
|
|
|
ComputeBootGod();
|
2014-01-06 19:31:13 +00:00
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
Dispose();
|
|
|
|
|
throw;
|
|
|
|
|
}
|
2014-01-05 20:58:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-04 03:38:30 +00:00
|
|
|
|
public IEmulatorServiceProvider ServiceProvider { get; private set; }
|
|
|
|
|
|
2015-01-16 01:38:47 +00:00
|
|
|
|
#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
|
|
|
|
|
|
2014-01-05 20:58:36 +00:00
|
|
|
|
#region Controller
|
|
|
|
|
|
2014-11-01 17:44:04 +00:00
|
|
|
|
public ControllerDefinition ControllerDefinition { get; private set; }
|
2014-01-05 20:58:36 +00:00
|
|
|
|
public IController Controller { get; set; }
|
|
|
|
|
|
2014-11-01 17:44:04 +00:00
|
|
|
|
void SetControllerDefinition()
|
|
|
|
|
{
|
|
|
|
|
var def = new ControllerDefinition();
|
|
|
|
|
def.Name = "NES Controller";
|
|
|
|
|
def.BoolButtons.AddRange(new[] { "Reset", "Power" }); // console buttons
|
2015-01-16 01:38:47 +00:00
|
|
|
|
if (_syncSettings.LeftPortConnected || _syncSettings.RightPortConnected)
|
2014-11-01 17:44:04 +00:00
|
|
|
|
def.BoolButtons.AddRange(PadP1.Select(p => p.Name));
|
2015-01-16 01:38:47 +00:00
|
|
|
|
if (_syncSettings.LeftPortConnected && _syncSettings.RightPortConnected)
|
2014-11-01 17:44:04 +00:00
|
|
|
|
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<PadEnt> buttons)
|
|
|
|
|
{
|
|
|
|
|
int ret = 0;
|
|
|
|
|
foreach (var b in buttons)
|
|
|
|
|
{
|
|
|
|
|
if (Controller[b.Name])
|
|
|
|
|
ret |= b.Mask;
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-05 20:58:36 +00:00
|
|
|
|
void SetPads(out int j1, out int j2)
|
|
|
|
|
{
|
2015-01-16 01:38:47 +00:00
|
|
|
|
if (_syncSettings.LeftPortConnected)
|
2014-11-01 17:44:04 +00:00
|
|
|
|
j1 = GetPad(PadP1) | unchecked((int)0xffffff00);
|
|
|
|
|
else
|
|
|
|
|
j1 = 0;
|
2015-01-16 01:38:47 +00:00
|
|
|
|
if (_syncSettings.RightPortConnected)
|
|
|
|
|
j2 = GetPad(_syncSettings.LeftPortConnected ? PadP2 : PadP1) | unchecked((int)0xffffff00);
|
2014-11-01 17:44:04 +00:00
|
|
|
|
else
|
|
|
|
|
j2 = 0;
|
2014-01-05 20:58:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
public void FrameAdvance(bool render, bool rendersound = true)
|
|
|
|
|
{
|
2014-12-21 17:40:06 +00:00
|
|
|
|
CheckDisposed();
|
2014-01-06 19:31:13 +00:00
|
|
|
|
using (FP.Save())
|
|
|
|
|
{
|
|
|
|
|
if (Controller["Power"])
|
|
|
|
|
LibQuickNES.qn_reset(Context, true);
|
|
|
|
|
if (Controller["Reset"])
|
|
|
|
|
LibQuickNES.qn_reset(Context, false);
|
|
|
|
|
|
|
|
|
|
int j1, j2;
|
|
|
|
|
SetPads(out j1, out j2);
|
|
|
|
|
|
|
|
|
|
Frame++;
|
|
|
|
|
LibQuickNES.ThrowStringError(LibQuickNES.qn_emulate_frame(Context, j1, j2));
|
|
|
|
|
IsLagFrame = LibQuickNES.qn_get_joypad_read_count(Context) == 0;
|
|
|
|
|
if (IsLagFrame)
|
|
|
|
|
LagCount++;
|
|
|
|
|
|
2014-08-13 17:22:16 +00:00
|
|
|
|
if (render)
|
|
|
|
|
Blit();
|
|
|
|
|
if (rendersound)
|
|
|
|
|
DrainAudio();
|
2014-12-17 03:27:44 +00:00
|
|
|
|
|
|
|
|
|
if (CB1 != null) CB1();
|
|
|
|
|
if (CB2 != null) CB2();
|
2014-01-06 19:31:13 +00:00
|
|
|
|
}
|
2014-01-05 20:58:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IntPtr Context;
|
|
|
|
|
|
|
|
|
|
public int Frame { get; private set; }
|
|
|
|
|
|
|
|
|
|
public string SystemId { get { return "NES"; } }
|
|
|
|
|
public bool DeterministicEmulation { get { return true; } }
|
2014-01-07 01:52:00 +00:00
|
|
|
|
public string BoardName { get; private set; }
|
2014-01-05 20:58:36 +00:00
|
|
|
|
|
|
|
|
|
public void ResetCounters()
|
|
|
|
|
{
|
|
|
|
|
Frame = 0;
|
|
|
|
|
IsLagFrame = false;
|
|
|
|
|
LagCount = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public CoreComm CoreComm
|
|
|
|
|
{
|
|
|
|
|
get;
|
|
|
|
|
private set;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-23 02:48:55 +00:00
|
|
|
|
#region bootgod
|
|
|
|
|
|
|
|
|
|
public RomStatus? BootGodStatus { get; private set; }
|
|
|
|
|
public string BootGodName { get; private set; }
|
|
|
|
|
|
|
|
|
|
void ComputeBootGod()
|
|
|
|
|
{
|
|
|
|
|
// inefficient, sloppy, etc etc
|
2014-11-01 17:44:04 +00:00
|
|
|
|
Emulation.Cores.Nintendo.NES.NES.BootGodDB.Initialize();
|
2015-01-14 21:55:48 +00:00
|
|
|
|
var chrrom = _memoryDomains["CHR VROM"];
|
|
|
|
|
var prgrom = _memoryDomains["PRG ROM"];
|
2014-07-23 02:48:55 +00:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
if (carts.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("BootGod entry found: {0}", carts[0].name);
|
2014-10-29 18:58:43 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2014-07-23 02:48:55 +00:00
|
|
|
|
BootGodStatus = RomStatus.GoodDump;
|
|
|
|
|
BootGodName = carts[0].name;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("No BootGod entry found.");
|
|
|
|
|
BootGodStatus = null;
|
|
|
|
|
BootGodName = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2014-01-05 20:58:36 +00:00
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
if (Context != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
LibQuickNES.qn_delete(Context);
|
|
|
|
|
Context = IntPtr.Zero;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-21 17:40:06 +00:00
|
|
|
|
void CheckDisposed()
|
|
|
|
|
{
|
|
|
|
|
if (Context == IntPtr.Zero)
|
|
|
|
|
throw new ObjectDisposedException(GetType().Name);
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-05 20:58:36 +00:00
|
|
|
|
#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(LibQuickNES.qn_set_sample_rate(Context, 44100));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DrainAudio()
|
|
|
|
|
{
|
2014-01-06 19:31:13 +00:00
|
|
|
|
NumSamples = LibQuickNES.qn_read_audio(Context, MonoBuff, MonoBuff.Length);
|
2014-01-05 20:58:36 +00:00
|
|
|
|
unsafe
|
|
|
|
|
{
|
2014-05-26 19:49:45 +00:00
|
|
|
|
fixed (short* _src = &MonoBuff[0], _dst = &StereoBuff[0])
|
2014-01-05 20:58:36 +00:00
|
|
|
|
{
|
|
|
|
|
short* src = _src;
|
|
|
|
|
short* dst = _dst;
|
|
|
|
|
for (int i = 0; i < NumSamples; i++)
|
|
|
|
|
{
|
|
|
|
|
*dst++ = *src;
|
|
|
|
|
*dst++ = *src++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-05-26 19:49:45 +00:00
|
|
|
|
}
|
2014-01-05 20:58:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|