using BizHawk.Common; using BizHawk.Common.BizInvoke; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Sound; using BizHawk.Emulation.Cores.Waterbox; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Consoles.SNK { [Core("Dual NeoPop", "Thomas Klausner, Mednafen Team, natt", true, false, "0.9.44.1", "https://mednafen.github.io/releases/", false)] public class DualNeoGeoPort : IEmulator { private NeoGeoPort _left; private NeoGeoPort _right; private readonly BasicServiceProvider _serviceProvider; private bool _disposed = false; private readonly DualSyncSound _soundProvider; private readonly SideBySideVideo _videoProvider; private readonly LinkInterop _leftEnd; private readonly LinkInterop _rightEnd; private readonly LinkCable _linkCable; [CoreConstructor("DNGP")] public DualNeoGeoPort(CoreComm comm, byte[] rom, bool deterministic) { CoreComm = comm; _left = new NeoGeoPort(comm, rom, new NeoGeoPort.SyncSettings { Language = LibNeoGeoPort.Language.English }, deterministic, PeRunner.CanonicalStart); _right = new NeoGeoPort(comm, rom, new NeoGeoPort.SyncSettings { Language = LibNeoGeoPort.Language.English }, deterministic, PeRunner.AlternateStart); _linkCable = new LinkCable(); _leftEnd = new LinkInterop(_left, _linkCable.LeftIn, _linkCable.LeftOut); _rightEnd = new LinkInterop(_right, _linkCable.RightIn, _linkCable.RightOut); _serviceProvider = new BasicServiceProvider(this); _soundProvider = new DualSyncSound(_left, _right); _serviceProvider.Register(_soundProvider); _videoProvider = new SideBySideVideo(_left, _right); _serviceProvider.Register(_videoProvider); } public void FrameAdvance(IController controller, bool render, bool rendersound = true) { var t1 = Task.Run(() => { _left.FrameAdvance(new PrefixController(controller, "P1 "), render, rendersound); _leftEnd.SignalEndOfFrame(); }); var t2 = Task.Run(() => { _right.FrameAdvance(new PrefixController(controller, "P2 "), render, rendersound); _rightEnd.SignalEndOfFrame(); }); var t3 = Task.Run(() => { _linkCable.RunFrame(); }); Task.WaitAll(t1, t2, t3); Frame++; _soundProvider.Fetch(); _videoProvider.Fetch(); } #region link cable private class LinkCable { public readonly BlockingCollection LeftIn = new BlockingCollection(); public readonly BlockingCollection LeftOut = new BlockingCollection(); public readonly BlockingCollection RightIn = new BlockingCollection(); public readonly BlockingCollection RightOut = new BlockingCollection(); private readonly Queue _leftData = new Queue(); private readonly Queue _rightData = new Queue(); public void RunFrame() { LinkRequest l = LeftIn.Take(); LinkRequest r = RightIn.Take(); while (true) { switch (l.RequestType) { case LinkRequest.RequestTypes.EndOfFrame: if (r.RequestType == LinkRequest.RequestTypes.EndOfFrame) { Console.WriteLine("\nEnd of Frame {0} {1}", _leftData.Count, _rightData.Count); return; } break; case LinkRequest.RequestTypes.Write: Console.Write("LW "); _leftData.Enqueue(l.Data); l = LeftIn.Take(); continue; case LinkRequest.RequestTypes.Read: case LinkRequest.RequestTypes.Poll: if (_rightData.Count > 0) { if (l.RequestType == LinkRequest.RequestTypes.Read) Console.Write("LR "); LeftOut.Add(new LinkResult { Data = l.RequestType == LinkRequest.RequestTypes.Read ? _rightData.Dequeue() : _rightData.Peek(), Return = true }); l = LeftIn.Take(); continue; } else if (r.RequestType != LinkRequest.RequestTypes.Write) { if (l.RequestType == LinkRequest.RequestTypes.Read) Console.Write("L! "); LeftOut.Add(new LinkResult { Data = l.Data, Return = false }); l = LeftIn.Take(); continue; } else { break; } } switch (r.RequestType) { case LinkRequest.RequestTypes.Write: Console.Write("RW "); _rightData.Enqueue(r.Data); r = RightIn.Take(); continue; case LinkRequest.RequestTypes.Read: case LinkRequest.RequestTypes.Poll: if (_leftData.Count > 0) { if (r.RequestType == LinkRequest.RequestTypes.Read) Console.Write("RR "); RightOut.Add(new LinkResult { Data = r.RequestType == LinkRequest.RequestTypes.Read ? _leftData.Dequeue() : _leftData.Peek(), Return = true }); r = RightIn.Take(); continue; } else if (l.RequestType != LinkRequest.RequestTypes.Write) { if (r.RequestType == LinkRequest.RequestTypes.Read) Console.Write("R! "); RightOut.Add(new LinkResult { Data = r.Data, Return = false }); r = RightIn.Take(); continue; } else { break; } } } } } public struct LinkRequest { public enum RequestTypes : byte { Read, Poll, Write, EndOfFrame } public RequestTypes RequestType; public byte Data; } public struct LinkResult { public byte Data; public bool Return; } private unsafe class LinkInterop { private readonly BlockingCollection _push; private readonly BlockingCollection _pull; private NeoGeoPort _core; private readonly IntPtr _readcb; private readonly IntPtr _pollcb; private readonly IntPtr _writecb; private readonly IImportResolver _exporter; public LinkInterop(NeoGeoPort core, BlockingCollection push, BlockingCollection pull) { _core = core; _push = push; _pull = pull; _exporter = BizExvoker.GetExvoker(this, CallingConventionAdapters.Waterbox); _readcb = _exporter.SafeResolve("CommsReadCallback"); _pollcb = _exporter.SafeResolve("CommsPollCallback"); _writecb = _exporter.SafeResolve("CommsWriteCallback"); ConnectPointers(); } private void ConnectPointers() { _core._neopop.SetCommsCallbacks(_readcb, _pollcb, _writecb); } private bool CommsPollNoBuffer() { _push.Add(new LinkRequest { RequestType = LinkRequest.RequestTypes.Poll }); return _pull.Take().Return; } [BizExport(CallingConvention.Cdecl)] public bool CommsReadCallback(byte* buffer) { if (buffer == null) return CommsPollNoBuffer(); _push.Add(new LinkRequest { RequestType = LinkRequest.RequestTypes.Read, Data = *buffer }); var r = _pull.Take(); *buffer = r.Data; return r.Return; } [BizExport(CallingConvention.Cdecl)] public bool CommsPollCallback(byte* buffer) { if (buffer == null) return CommsPollNoBuffer(); _push.Add(new LinkRequest { RequestType = LinkRequest.RequestTypes.Poll, Data = *buffer }); var r = _pull.Take(); *buffer = r.Data; return r.Return; } [BizExport(CallingConvention.Cdecl)] public void CommsWriteCallback(byte data) { _push.Add(new LinkRequest { RequestType = LinkRequest.RequestTypes.Write, Data = data }); } public void SignalEndOfFrame() { _push.Add(new LinkRequest { RequestType = LinkRequest.RequestTypes.EndOfFrame }); } public void PostLoadState() { ConnectPointers(); } } #endregion private class PrefixController : IController { public PrefixController(IController controller, string prefix) { _controller = controller; _prefix = prefix; } private readonly IController _controller; private readonly string _prefix; public ControllerDefinition Definition => null; public float GetFloat(string name) { return _controller.GetFloat(_prefix + name); } public bool IsPressed(string button) { return _controller.IsPressed(_prefix + button); } } public ControllerDefinition ControllerDefinition => DualNeoGeoPortController; private static readonly ControllerDefinition DualNeoGeoPortController = new ControllerDefinition { BoolButtons = { "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 A", "P1 B", "P1 Option", "P1 Power", "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 A", "P2 B", "P2 Option", "P2 Power" }, Name = "Dual NeoGeo Portable Controller" }; public void ResetCounters() { Frame = 0; } public int Frame { get; private set; } public IEmulatorServiceProvider ServiceProvider => _serviceProvider; public CoreComm CoreComm { get; } public bool DeterministicEmulation => _left.DeterministicEmulation && _right.DeterministicEmulation; public string SystemId => "DNGP"; public void Dispose() { if (!_disposed) { _left.Dispose(); _right.Dispose(); _left = null; _right = null; _disposed = true; } } } }