Refactor GambatteLink (#3002)
* refactor gambattelink to be more modular and add support for 3x/4x
This commit is contained in:
parent
1333813bb1
commit
d053a0b414
|
@ -33,11 +33,17 @@
|
|||
this.gbPrefControl1 = new GBPrefControl();
|
||||
this.tabPage2 = new System.Windows.Forms.TabPage();
|
||||
this.gbPrefControl2 = new GBPrefControl();
|
||||
this.tabPage3 = new System.Windows.Forms.TabPage();
|
||||
this.gbPrefControl3 = new GBPrefControl();
|
||||
this.tabPage4 = new System.Windows.Forms.TabPage();
|
||||
this.gbPrefControl4 = new GBPrefControl();
|
||||
this.buttonCancel = new System.Windows.Forms.Button();
|
||||
this.buttonOK = new System.Windows.Forms.Button();
|
||||
this.tabControl1.SuspendLayout();
|
||||
this.tabPage1.SuspendLayout();
|
||||
this.tabPage2.SuspendLayout();
|
||||
this.tabPage3.SuspendLayout();
|
||||
this.tabPage4.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// tabControl1
|
||||
|
@ -47,6 +53,8 @@
|
|||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.tabControl1.Controls.Add(this.tabPage1);
|
||||
this.tabControl1.Controls.Add(this.tabPage2);
|
||||
this.tabControl1.Controls.Add(this.tabPage3);
|
||||
this.tabControl1.Controls.Add(this.tabPage4);
|
||||
this.tabControl1.Location = new System.Drawing.Point(12, 12);
|
||||
this.tabControl1.Name = "tabControl1";
|
||||
this.tabControl1.SelectedIndex = 0;
|
||||
|
@ -61,7 +69,7 @@
|
|||
this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPage1.Size = new System.Drawing.Size(509, 308);
|
||||
this.tabPage1.TabIndex = 0;
|
||||
this.tabPage1.Text = "Left Gameboy";
|
||||
this.tabPage1.Text = "Player 1 Gameboy";
|
||||
this.tabPage1.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// gbPrefControl1
|
||||
|
@ -81,7 +89,7 @@
|
|||
this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPage2.Size = new System.Drawing.Size(509, 308);
|
||||
this.tabPage2.TabIndex = 1;
|
||||
this.tabPage2.Text = "Right Gameboy";
|
||||
this.tabPage2.Text = "Player 2 Gameboy";
|
||||
this.tabPage2.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// gbPrefControl2
|
||||
|
@ -93,6 +101,46 @@
|
|||
this.gbPrefControl2.Size = new System.Drawing.Size(503, 302);
|
||||
this.gbPrefControl2.TabIndex = 0;
|
||||
//
|
||||
// tabPage3
|
||||
//
|
||||
this.tabPage3.Controls.Add(this.gbPrefControl3);
|
||||
this.tabPage3.Location = new System.Drawing.Point(4, 22);
|
||||
this.tabPage3.Name = "tabPage3";
|
||||
this.tabPage3.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPage3.Size = new System.Drawing.Size(509, 308);
|
||||
this.tabPage3.TabIndex = 2;
|
||||
this.tabPage3.Text = "Player 3 Gameboy";
|
||||
this.tabPage3.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// gbPrefControl3
|
||||
//
|
||||
this.gbPrefControl3.ColorGameBoy = false;
|
||||
this.gbPrefControl3.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.gbPrefControl3.Location = new System.Drawing.Point(3, 3);
|
||||
this.gbPrefControl3.Name = "gbPrefControl3";
|
||||
this.gbPrefControl3.Size = new System.Drawing.Size(503, 302);
|
||||
this.gbPrefControl3.TabIndex = 0;
|
||||
//
|
||||
// tabPage4
|
||||
//
|
||||
this.tabPage4.Controls.Add(this.gbPrefControl4);
|
||||
this.tabPage4.Location = new System.Drawing.Point(4, 22);
|
||||
this.tabPage4.Name = "tabPage4";
|
||||
this.tabPage4.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPage4.Size = new System.Drawing.Size(509, 308);
|
||||
this.tabPage4.TabIndex = 3;
|
||||
this.tabPage4.Text = "Player 4 Gameboy";
|
||||
this.tabPage4.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// gbPrefControl4
|
||||
//
|
||||
this.gbPrefControl4.ColorGameBoy = false;
|
||||
this.gbPrefControl4.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.gbPrefControl4.Location = new System.Drawing.Point(3, 3);
|
||||
this.gbPrefControl4.Name = "gbPrefControl4";
|
||||
this.gbPrefControl4.Size = new System.Drawing.Size(503, 302);
|
||||
this.gbPrefControl4.TabIndex = 0;
|
||||
//
|
||||
// buttonCancel
|
||||
//
|
||||
this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
|
@ -130,6 +178,8 @@
|
|||
this.tabControl1.ResumeLayout(false);
|
||||
this.tabPage1.ResumeLayout(false);
|
||||
this.tabPage2.ResumeLayout(false);
|
||||
this.tabPage3.ResumeLayout(false);
|
||||
this.tabPage4.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
@ -139,10 +189,14 @@
|
|||
private System.Windows.Forms.TabControl tabControl1;
|
||||
private System.Windows.Forms.TabPage tabPage1;
|
||||
private System.Windows.Forms.TabPage tabPage2;
|
||||
private System.Windows.Forms.TabPage tabPage3;
|
||||
private System.Windows.Forms.TabPage tabPage4;
|
||||
private System.Windows.Forms.Button buttonCancel;
|
||||
private System.Windows.Forms.Button buttonOK;
|
||||
private GBPrefControl gbPrefControl1;
|
||||
private GBPrefControl gbPrefControl2;
|
||||
private GBPrefControl gbPrefControl3;
|
||||
private GBPrefControl gbPrefControl4;
|
||||
|
||||
}
|
||||
}
|
|
@ -22,25 +22,31 @@ namespace BizHawk.Client.EmuHawk
|
|||
InitializeComponent();
|
||||
gbPrefControl1.DialogParent = this;
|
||||
gbPrefControl2.DialogParent = this;
|
||||
gbPrefControl3.DialogParent = this;
|
||||
gbPrefControl4.DialogParent = this;
|
||||
Icon = Properties.Resources.DualIcon;
|
||||
}
|
||||
|
||||
private void PutSettings(GambatteLink.GambatteLinkSettings s, GambatteLink.GambatteLinkSyncSettings ss)
|
||||
{
|
||||
gbPrefControl1.PutSettings(_config, _game, _movieSession, s.L, ss.L);
|
||||
gbPrefControl2.PutSettings(_config, _game, _movieSession, s.R, ss.R);
|
||||
gbPrefControl1.PutSettings(_config, _game, _movieSession, s._linkedSettings[0], ss._linkedSyncSettings[0]);
|
||||
gbPrefControl2.PutSettings(_config, _game, _movieSession, s._linkedSettings[1], ss._linkedSyncSettings[1]);
|
||||
gbPrefControl3.PutSettings(_config, _game, _movieSession, s._linkedSettings[2], ss._linkedSyncSettings[2]);
|
||||
gbPrefControl4.PutSettings(_config, _game, _movieSession, s._linkedSettings[3], ss._linkedSyncSettings[3]);
|
||||
}
|
||||
|
||||
private void GetSettings(out GambatteLink.GambatteLinkSettings s, out GambatteLink.GambatteLinkSyncSettings ss)
|
||||
{
|
||||
gbPrefControl1.GetSettings(out var sl, out var ssl);
|
||||
gbPrefControl2.GetSettings(out var sr, out var ssr);
|
||||
gbPrefControl1.GetSettings(out var s1, out var ss1);
|
||||
gbPrefControl2.GetSettings(out var s2, out var ss2);
|
||||
gbPrefControl3.GetSettings(out var s3, out var ss3);
|
||||
gbPrefControl4.GetSettings(out var s4, out var ss4);
|
||||
|
||||
s = new GambatteLink.GambatteLinkSettings(sl, sr);
|
||||
ss = new GambatteLink.GambatteLinkSyncSettings(ssl, ssr);
|
||||
s = new GambatteLink.GambatteLinkSettings(s1, s2, s3, s4);
|
||||
ss = new GambatteLink.GambatteLinkSyncSettings(ss1, ss2, ss3, ss4);
|
||||
}
|
||||
|
||||
private bool SyncSettingsChanged => gbPrefControl1.SyncSettingsChanged || gbPrefControl2.SyncSettingsChanged;
|
||||
private bool SyncSettingsChanged => gbPrefControl1.SyncSettingsChanged || gbPrefControl2.SyncSettingsChanged || gbPrefControl3.SyncSettingsChanged || gbPrefControl4.SyncSettingsChanged;
|
||||
|
||||
public static void DoDGBPrefsDialog(IMainFormForConfig mainForm, Config config, IGameInfo game, IMovieSession movieSession, GambatteLink gambatte)
|
||||
{
|
||||
|
@ -50,8 +56,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
using var dlg = new DGBPrefs(mainForm.DialogController, config, game, movieSession);
|
||||
dlg.PutSettings(s, ss);
|
||||
|
||||
dlg.gbPrefControl1.ColorGameBoy = gambatte.IsCGBMode(false);
|
||||
dlg.gbPrefControl2.ColorGameBoy = gambatte.IsCGBMode(true);
|
||||
dlg.gbPrefControl1.ColorGameBoy = gambatte.IsCGBMode(0);
|
||||
dlg.gbPrefControl2.ColorGameBoy = gambatte.IsCGBMode(1);
|
||||
dlg.gbPrefControl3.ColorGameBoy = gambatte.IsCGBMode(2);
|
||||
dlg.gbPrefControl4.ColorGameBoy = gambatte.IsCGBMode(3);
|
||||
|
||||
if (mainForm.ShowDialogAsChild(dlg) == DialogResult.OK)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// A generic linked implementation of IDebuggable that can be used by any link core
|
||||
/// </summary>
|
||||
/// <seealso cref="IDebuggable" />
|
||||
public class LinkedDebuggable : IDebuggable
|
||||
{
|
||||
private readonly IEmulator[] _linkedCores;
|
||||
private readonly int _numCores;
|
||||
|
||||
public LinkedDebuggable(IEmulator[] linkedCores, int numCores, MemoryCallbackSystem memoryCallbacks)
|
||||
{
|
||||
_linkedCores = linkedCores;
|
||||
_numCores = numCores;
|
||||
MemoryCallbacks = memoryCallbacks;
|
||||
}
|
||||
|
||||
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
|
||||
{
|
||||
var ret = new List<KeyValuePair<string, RegisterValue>>();
|
||||
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
ret.AddRange(_linkedCores[i].AsDebuggable().GetCpuFlagsAndRegisters()
|
||||
.Select(reg => new KeyValuePair<string, RegisterValue>($"P{i + 1} " + reg.Key, reg.Value)).ToList());
|
||||
}
|
||||
|
||||
return ret.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
}
|
||||
|
||||
public void SetCpuRegister(string register, int value)
|
||||
{
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
if (register.StartsWith($"P{i + 1} "))
|
||||
{
|
||||
_linkedCores[i].AsDebuggable().SetCpuRegister(register.Replace($"P{i + 1} ", ""), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IMemoryCallbackSystem MemoryCallbacks { get; }
|
||||
|
||||
public bool CanStep(StepType type) => false;
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public void Step(StepType type) => throw new NotImplementedException();
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public long TotalExecutedCycles => throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// A generic linked implementation of IMemoryDomains that can be used by any link core
|
||||
/// </summary>
|
||||
/// <seealso cref="IMemoryDomains" />
|
||||
public class LinkedMemoryDomains : MemoryDomainList
|
||||
{
|
||||
public LinkedMemoryDomains(IEmulator[] linkedCores, int numCores)
|
||||
: base(LinkMemoryDomains(linkedCores, numCores))
|
||||
{
|
||||
SystemBus = linkedCores[0].AsMemoryDomains().SystemBus;
|
||||
}
|
||||
|
||||
private static List<MemoryDomain> LinkMemoryDomains(IEmulator[] linkedCores, int numCores)
|
||||
{
|
||||
var mm = new List<MemoryDomain>();
|
||||
|
||||
for (int i = 0; i < numCores; i++)
|
||||
{
|
||||
foreach (var md in linkedCores[i].AsMemoryDomains() as MemoryDomainList)
|
||||
{
|
||||
mm.Add(new WrappedMemoryDomain($"P{i + 1} " + md.Name, md));
|
||||
}
|
||||
}
|
||||
|
||||
return mm;
|
||||
}
|
||||
|
||||
private class WrappedMemoryDomain : MemoryDomain
|
||||
{
|
||||
private readonly MemoryDomain _m;
|
||||
|
||||
public WrappedMemoryDomain(string name, MemoryDomain m)
|
||||
{
|
||||
_m = m;
|
||||
|
||||
Name = name;
|
||||
Size = m.Size;
|
||||
WordSize = m.WordSize;
|
||||
EndianType = m.EndianType;
|
||||
Writable = m.Writable;
|
||||
}
|
||||
|
||||
public override byte PeekByte(long addr)
|
||||
{
|
||||
return _m.PeekByte(addr);
|
||||
}
|
||||
|
||||
public override void PokeByte(long addr, byte val)
|
||||
{
|
||||
_m.PokeByte(addr, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// A generic linked implementation of ISaveRam that can be used by any link core
|
||||
/// </summary>
|
||||
/// <seealso cref="ISaveRam" />
|
||||
public class LinkedSaveRam : ISaveRam
|
||||
{
|
||||
private readonly IEmulator[] _linkedCores;
|
||||
private readonly int _numCores;
|
||||
|
||||
public LinkedSaveRam(IEmulator[] linkedCores, int numCores)
|
||||
{
|
||||
_linkedCores = linkedCores;
|
||||
_numCores = numCores;
|
||||
}
|
||||
|
||||
public bool SaveRamModified => LinkedSaveRamModified();
|
||||
|
||||
private bool LinkedSaveRamModified()
|
||||
{
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
if (_linkedCores[i].AsSaveRam().SaveRamModified)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] CloneSaveRam()
|
||||
{
|
||||
var linkedBuffers = new List<byte[]>();
|
||||
int len = 0;
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
linkedBuffers.Add(_linkedCores[i].AsSaveRam().CloneSaveRam()!);
|
||||
len += linkedBuffers[i].Length;
|
||||
}
|
||||
byte[] ret = new byte[len];
|
||||
int pos = 0;
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
Buffer.BlockCopy(linkedBuffers[i], 0, ret, pos, linkedBuffers[i].Length);
|
||||
pos += linkedBuffers[i].Length;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void StoreSaveRam(byte[] data)
|
||||
{
|
||||
int pos = 0;
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
var b = new byte[_linkedCores[i].AsSaveRam().CloneSaveRam()!.Length];
|
||||
Buffer.BlockCopy(data, pos, b, 0, b.Length);
|
||||
pos += b.Length;
|
||||
_linkedCores[i].AsSaveRam().StoreSaveRam(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,21 +7,26 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
{
|
||||
public partial class Gameboy : ICodeDataLogger
|
||||
{
|
||||
public void SetCDL(ICodeDataLog cdl)
|
||||
public void SetCDL(ICodeDataLog cdl) => SetCDL(cdl, "");
|
||||
|
||||
internal void SetCDL(ICodeDataLog cdl, string which)
|
||||
{
|
||||
_cdl = cdl;
|
||||
_which = which;
|
||||
LibGambatte.gambatte_setcdcallback(GambatteState, cdl == null ? null : _cdCallback);
|
||||
}
|
||||
|
||||
public void NewCDL(ICodeDataLog cdl)
|
||||
public void NewCDL(ICodeDataLog cdl) => NewCDL(cdl, "");
|
||||
|
||||
internal void NewCDL(ICodeDataLog cdl, string which)
|
||||
{
|
||||
cdl["ROM"] = new byte[MemoryDomains["ROM"]!.Size];
|
||||
cdl[which + "ROM"] = new byte[MemoryDomains["ROM"]!.Size];
|
||||
|
||||
// cdl["HRAM"] = new byte[_memoryDomains["HRAM"]!.Size]; //this is probably useless, but it's here if someone needs it
|
||||
cdl["WRAM"] = new byte[MemoryDomains["WRAM"]!.Size];
|
||||
cdl[which + "WRAM"] = new byte[MemoryDomains["WRAM"]!.Size];
|
||||
|
||||
var found = MemoryDomains["CartRAM"];
|
||||
if (found is not null) cdl["CartRAM"] = new byte[found.Size];
|
||||
if (found is not null) cdl[which + "CartRAM"] = new byte[found.Size];
|
||||
|
||||
cdl.SubType = "GB";
|
||||
cdl.SubVer = 0;
|
||||
|
@ -33,6 +38,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
}
|
||||
|
||||
private ICodeDataLog _cdl;
|
||||
private string _which;
|
||||
private readonly LibGambatte.CDCallback _cdCallback;
|
||||
|
||||
private void CDCallbackProc(int addr, LibGambatte.CDLog_AddrType addrtype, LibGambatte.CDLog_Flags flags)
|
||||
|
@ -51,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
_ => throw new InvalidOperationException("Juniper lightbulb proxy"),
|
||||
};
|
||||
|
||||
_cdl[key][addr] |= (byte)flags;
|
||||
_cdl[_which + key][addr] |= (byte)flags;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
internal void ConnectMemoryCallbackSystem(MemoryCallbackSystem mcs)
|
||||
{
|
||||
_memorycallbacks = mcs;
|
||||
_memorycallbacks.ActiveChanged += SetMemoryCallbacks;
|
||||
}
|
||||
|
||||
private void InitMemoryCallbacks()
|
||||
|
@ -78,12 +79,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
_writecb = CreateCallback(MemoryCallbackFlags.AccessWrite, () => MemoryCallbacks.HasWrites);
|
||||
_execcb = CreateCallback(MemoryCallbackFlags.AccessExecute, () => MemoryCallbacks.HasExecutes);
|
||||
|
||||
_memorycallbacks.ActiveChanged += () =>
|
||||
{
|
||||
LibGambatte.gambatte_setreadcallback(GambatteState, MemoryCallbacks.HasReads ? _readcb : null);
|
||||
LibGambatte.gambatte_setwritecallback(GambatteState, MemoryCallbacks.HasWrites ? _writecb : null);
|
||||
LibGambatte.gambatte_setexeccallback(GambatteState, MemoryCallbacks.HasExecutes ? _execcb : null);
|
||||
};
|
||||
_memorycallbacks.ActiveChanged += SetMemoryCallbacks;
|
||||
}
|
||||
|
||||
private void SetMemoryCallbacks()
|
||||
{
|
||||
LibGambatte.gambatte_setreadcallback(GambatteState, MemoryCallbacks.HasReads ? _readcb : null);
|
||||
LibGambatte.gambatte_setwritecallback(GambatteState, MemoryCallbacks.HasWrites ? _writecb : null);
|
||||
LibGambatte.gambatte_setexeccallback(GambatteState, MemoryCallbacks.HasExecutes ? _execcb : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,19 +4,34 @@ using System.IO;
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||
{
|
||||
partial class GambatteLink
|
||||
partial class GambatteLink : ICodeDataLogger
|
||||
{
|
||||
void ICodeDataLogger.SetCDL(ICodeDataLog cdl)
|
||||
{
|
||||
((ICodeDataLogger)L).SetCDL(cdl);
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedCores[i].SetCDL(cdl, $"P{i + 1} ");
|
||||
}
|
||||
}
|
||||
|
||||
void ICodeDataLogger.NewCDL(ICodeDataLog cdl)
|
||||
{
|
||||
((ICodeDataLogger)L).NewCDL(cdl);
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedCores[i].NewCDL(cdl, $"P{i + 1} ");
|
||||
}
|
||||
}
|
||||
|
||||
void ICodeDataLogger.DisassembleCDL(Stream s, ICodeDataLog cdl) { ((ICodeDataLogger)L).DisassembleCDL(s, cdl); }
|
||||
|
||||
[FeatureNotImplemented]
|
||||
void ICodeDataLogger.DisassembleCDL(Stream s, ICodeDataLog cdl)
|
||||
{
|
||||
// this doesn't actually do anything
|
||||
/*
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedCores[i].DisassembleCDL(s, cdl);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||
{
|
||||
public partial class GambatteLink : IDebuggable
|
||||
{
|
||||
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
|
||||
{
|
||||
var left = L.GetCpuFlagsAndRegisters()
|
||||
.Select(reg => new KeyValuePair<string, RegisterValue>("Left " + reg.Key, reg.Value));
|
||||
|
||||
var right = R.GetCpuFlagsAndRegisters()
|
||||
.Select(reg => new KeyValuePair<string, RegisterValue>("Right " + reg.Key, reg.Value));
|
||||
|
||||
return left.Union(right).ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
}
|
||||
|
||||
public void SetCpuRegister(string register, int value)
|
||||
{
|
||||
if (register.StartsWith("Left "))
|
||||
{
|
||||
L.SetCpuRegister(register.Replace("Left ", ""), value);
|
||||
}
|
||||
else if (register.StartsWith("Right "))
|
||||
{
|
||||
R.SetCpuRegister(register.Replace("Right ", ""), value);
|
||||
}
|
||||
}
|
||||
|
||||
public IMemoryCallbackSystem MemoryCallbacks { get; } = new MemoryCallbackSystem(new[] { "System Bus" });
|
||||
|
||||
public bool CanStep(StepType type) => false;
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public void Step(StepType type) => throw new NotImplementedException();
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public long TotalExecutedCycles => throw new NotImplementedException();
|
||||
|
||||
private readonly MemoryCallbackSystem _memorycallbacks = new MemoryCallbackSystem(new[] { "System Bus" });
|
||||
}
|
||||
}
|
|
@ -6,56 +6,82 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
{
|
||||
public partial class GambatteLink : IEmulator, IBoardInfo
|
||||
{
|
||||
public IEmulatorServiceProvider ServiceProvider { get; }
|
||||
public IEmulatorServiceProvider ServiceProvider => _serviceProvider;
|
||||
|
||||
public ControllerDefinition ControllerDefinition => DualGbController;
|
||||
public ControllerDefinition ControllerDefinition => GBLinkController;
|
||||
|
||||
public bool FrameAdvance(IController controller, bool render, bool rendersound = true)
|
||||
{
|
||||
LCont.Clear();
|
||||
RCont.Clear();
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedConts[i].Clear();
|
||||
}
|
||||
|
||||
foreach (var s in DualGbController.BoolButtons)
|
||||
foreach (var s in GBLinkController.BoolButtons)
|
||||
{
|
||||
if (controller.IsPressed(s))
|
||||
{
|
||||
if (s.Contains("P1 "))
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
LCont.Set(s.Replace("P1 ", ""));
|
||||
}
|
||||
else if (s.Contains("P2 "))
|
||||
{
|
||||
RCont.Set(s.Replace("P2 ", ""));
|
||||
if (s.Contains($"P{i + 1} "))
|
||||
{
|
||||
_linkedConts[i].Set(s.Replace($"P{i + 1} ", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool cablediscosignalNew = controller.IsPressed("Toggle Cable");
|
||||
if (cablediscosignalNew && !_cablediscosignal)
|
||||
bool cableDiscoSignalNew = controller.IsPressed("Toggle Cable Connection");
|
||||
if (cableDiscoSignalNew && !_cableDiscoSignal)
|
||||
{
|
||||
_cableconnected ^= true;
|
||||
Console.WriteLine("Cable connect status to {0}", _cableconnected);
|
||||
_cableConnected ^= true;
|
||||
Console.WriteLine("Cable connect status to {0}", _cableConnected);
|
||||
}
|
||||
|
||||
_cablediscosignal = cablediscosignalNew;
|
||||
_cableDiscoSignal = cableDiscoSignalNew;
|
||||
|
||||
L.FrameAdvancePrep(LCont);
|
||||
R.FrameAdvancePrep(RCont);
|
||||
if (_numCores > 2)
|
||||
{
|
||||
bool cableShiftSignalNew = controller.IsPressed("Toggle Cable Shift");
|
||||
if (cableShiftSignalNew && !_cableShiftSignal)
|
||||
{
|
||||
_cableShifted ^= true;
|
||||
Console.WriteLine("Cable shift status to {0}", _cableShifted);
|
||||
}
|
||||
|
||||
_cableShifted = cableShiftSignalNew;
|
||||
|
||||
bool cableSpaceSignalNew = controller.IsPressed("Toggle Cable Spacing");
|
||||
if (cableSpaceSignalNew && !_cableSpaceSignal)
|
||||
{
|
||||
_cableSpaced ^= true;
|
||||
Console.WriteLine("Cable spacing status to {0}", _cableSpaceSignal);
|
||||
}
|
||||
|
||||
_cableSpaceSignal = cableSpaceSignalNew;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedCores[i].FrameAdvancePrep(_linkedConts[i]);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (int* leftfbuff = &FrameBuffer[0])
|
||||
fixed (int* fbuff = &FrameBuffer[0])
|
||||
{
|
||||
// use pitch to have both cores write to the same frame buffer, interleaved
|
||||
int* rightfbuff = leftfbuff + 160;
|
||||
const int Pitch = 160 * 2;
|
||||
int Pitch = 160 * _numCores;
|
||||
|
||||
fixed (short* leftsbuff = LeftBuffer, rightsbuff = RightBuffer)
|
||||
fixed (short* sbuff = &SoundBuffer[0])
|
||||
{
|
||||
const int Step = 32; // could be 1024 for GB
|
||||
|
||||
int nL = _overflowL;
|
||||
int nR = _overflowR;
|
||||
int[] n = new int[_numCores];
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
n[i] = _linkedOverflow[i];
|
||||
}
|
||||
|
||||
// slowly step our way through the frame, while continually checking and resolving link cable status
|
||||
for (int target = 0; target < SampPerFrame;)
|
||||
|
@ -66,65 +92,94 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
target = SampPerFrame; // don't run for slightly too long depending on step
|
||||
}
|
||||
|
||||
// gambatte_runfor() aborts early when a frame is produced, but we don't want that, hence the while()
|
||||
while (nL < target)
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
uint nsamp = (uint)(target - nL);
|
||||
if (LibGambatte.gambatte_runfor(L.GambatteState, leftfbuff, Pitch, leftsbuff + (nL * 2), ref nsamp) > 0)
|
||||
// gambatte_runfor() aborts early when a frame is produced, but we don't want that, hence the while()
|
||||
while (n[i] < target)
|
||||
{
|
||||
for (int i = 0; i < 144; i++)
|
||||
uint nsamp = (uint)(target - n[i]);
|
||||
if (LibGambatte.gambatte_runfor(_linkedCores[i].GambatteState, fbuff + (i * 160), Pitch, sbuff + (i * MaxSampsPerFrame) + (n[i] * 2), ref nsamp) > 0)
|
||||
{
|
||||
Array.Copy(FrameBuffer, Pitch * i, VideoBuffer, Pitch * i, 160);
|
||||
for (int j = 0; j < 144; j++)
|
||||
{
|
||||
Array.Copy(FrameBuffer, (i * 160) + (j * Pitch), VideoBuffer, (i * 160) + (j * Pitch), 160);
|
||||
}
|
||||
}
|
||||
|
||||
n[i] += (int)nsamp;
|
||||
}
|
||||
|
||||
nL += (int)nsamp;
|
||||
}
|
||||
|
||||
while (nR < target)
|
||||
{
|
||||
uint nsamp = (uint)(target - nR);
|
||||
if (LibGambatte.gambatte_runfor(R.GambatteState, rightfbuff, Pitch, rightsbuff + (nR * 2), ref nsamp) > 0)
|
||||
{
|
||||
for (int i = 0; i < 144; i++)
|
||||
{
|
||||
Array.Copy(FrameBuffer, 160 + Pitch * i, VideoBuffer, 160 + Pitch * i, 160);
|
||||
}
|
||||
}
|
||||
|
||||
nR += (int)nsamp;
|
||||
}
|
||||
|
||||
// poll link cable statuses, but not when the cable is disconnected
|
||||
if (!_cableconnected)
|
||||
if (!_cableConnected)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (LibGambatte.gambatte_linkstatus(L.GambatteState, 256) != 0) // ClockTrigger
|
||||
switch (_numCores)
|
||||
{
|
||||
LibGambatte.gambatte_linkstatus(L.GambatteState, 257); // ack
|
||||
int lo = LibGambatte.gambatte_linkstatus(L.GambatteState, 258); // GetOut
|
||||
int ro = LibGambatte.gambatte_linkstatus(R.GambatteState, 258);
|
||||
LibGambatte.gambatte_linkstatus(L.GambatteState, ro & 0xff); // ShiftIn
|
||||
LibGambatte.gambatte_linkstatus(R.GambatteState, lo & 0xff); // ShiftIn
|
||||
}
|
||||
|
||||
if (LibGambatte.gambatte_linkstatus(R.GambatteState, 256) != 0) // ClockTrigger
|
||||
{
|
||||
LibGambatte.gambatte_linkstatus(R.GambatteState, 257); // ack
|
||||
int lo = LibGambatte.gambatte_linkstatus(L.GambatteState, 258); // GetOut
|
||||
int ro = LibGambatte.gambatte_linkstatus(R.GambatteState, 258);
|
||||
LibGambatte.gambatte_linkstatus(L.GambatteState, ro & 0xff); // ShiftIn
|
||||
LibGambatte.gambatte_linkstatus(R.GambatteState, lo & 0xff); // ShiftIn
|
||||
case 2:
|
||||
{
|
||||
TryCableBytewiseTransfer(P1, P2);
|
||||
TryCableBytewiseTransfer(P2, P1);
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
if (_cableSpaced)
|
||||
{
|
||||
TryCableBytewiseTransfer(P1, P3);
|
||||
TryCableBytewiseTransfer(P3, P1);
|
||||
}
|
||||
else if (_cableShifted)
|
||||
{
|
||||
TryCableBytewiseTransfer(P2, P3);
|
||||
TryCableBytewiseTransfer(P3, P2);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryCableBytewiseTransfer(P1, P2);
|
||||
TryCableBytewiseTransfer(P2, P1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
if (_cableSpaced)
|
||||
{
|
||||
TryCableBytewiseTransfer(P1, P3);
|
||||
TryCableBytewiseTransfer(P3, P1);
|
||||
TryCableBytewiseTransfer(P2, P4);
|
||||
TryCableBytewiseTransfer(P4, P2);
|
||||
}
|
||||
else if (_cableShifted)
|
||||
{
|
||||
TryCableBytewiseTransfer(P2, P3);
|
||||
TryCableBytewiseTransfer(P3, P2);
|
||||
TryCableBytewiseTransfer(P4, P1);
|
||||
TryCableBytewiseTransfer(P1, P4);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryCableBytewiseTransfer(P1, P2);
|
||||
TryCableBytewiseTransfer(P2, P1);
|
||||
TryCableBytewiseTransfer(P3, P4);
|
||||
TryCableBytewiseTransfer(P4, P3);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
_overflowL = nL - SampPerFrame;
|
||||
_overflowR = nR - SampPerFrame;
|
||||
if (_overflowL < 0 || _overflowR < 0)
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
throw new Exception("Timing problem?");
|
||||
_linkedOverflow[i] = n[i] - SampPerFrame;
|
||||
if (_linkedOverflow[i] < 0)
|
||||
{
|
||||
throw new Exception("Timing problem?");
|
||||
}
|
||||
}
|
||||
|
||||
if (rendersound)
|
||||
|
@ -133,22 +188,25 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
}
|
||||
|
||||
// copy extra samples back to beginning
|
||||
for (int i = 0; i < _overflowL * 2; i++)
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
LeftBuffer[i] = LeftBuffer[i + (SampPerFrame * 2)];
|
||||
}
|
||||
|
||||
for (int i = 0; i < _overflowR * 2; i++)
|
||||
{
|
||||
RightBuffer[i] = RightBuffer[i + (SampPerFrame * 2)];
|
||||
for (int j = 0; j < _linkedOverflow[i] * 2; j++)
|
||||
{
|
||||
SoundBuffer[(i * MaxSampsPerFrame) + j] = SoundBuffer[(i * MaxSampsPerFrame) + j + (SampPerFrame * 2)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
L.FrameAdvancePost();
|
||||
R.FrameAdvancePost();
|
||||
IsLagFrame = L.IsLagFrame && R.IsLagFrame;
|
||||
IsLagFrame = true;
|
||||
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedCores[i].FrameAdvancePost();
|
||||
IsLagFrame &= _linkedCores[i].IsLagFrame;
|
||||
}
|
||||
|
||||
if (IsLagFrame)
|
||||
{
|
||||
LagCount++;
|
||||
|
@ -159,38 +217,71 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
return true;
|
||||
}
|
||||
|
||||
private void TryCableBytewiseTransfer(int master, int slave)
|
||||
{
|
||||
if (LibGambatte.gambatte_linkstatus(_linkedCores[master].GambatteState, 256) != 0) // ClockTrigger
|
||||
{
|
||||
LibGambatte.gambatte_linkstatus(_linkedCores[master].GambatteState, 257); // ack
|
||||
int om = LibGambatte.gambatte_linkstatus(_linkedCores[master].GambatteState, 258); // GetOut
|
||||
int os = LibGambatte.gambatte_linkstatus(_linkedCores[slave].GambatteState, 258);
|
||||
LibGambatte.gambatte_linkstatus(_linkedCores[slave].GambatteState, om & 0xff); // ShiftIn
|
||||
LibGambatte.gambatte_linkstatus(_linkedCores[master].GambatteState, os & 0xff); // ShiftIn
|
||||
}
|
||||
}
|
||||
|
||||
public int Frame { get; private set; }
|
||||
|
||||
public string SystemId => VSystemID.Raw.DGB;
|
||||
|
||||
public bool DeterministicEmulation => L.DeterministicEmulation && R.DeterministicEmulation;
|
||||
public bool DeterministicEmulation => LinkedDeterministicEmulation();
|
||||
|
||||
public string BoardName => L.BoardName + '|' + R.BoardName;
|
||||
private bool LinkedDeterministicEmulation()
|
||||
{
|
||||
bool deterministicEmulation = true;
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
deterministicEmulation &= _linkedCores[i].DeterministicEmulation;
|
||||
}
|
||||
return deterministicEmulation;
|
||||
}
|
||||
|
||||
public string BoardName => LinkedBoardName();
|
||||
|
||||
private string LinkedBoardName()
|
||||
{
|
||||
string boardName = "";
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
boardName += _linkedCores[i].BoardName + "|";
|
||||
}
|
||||
return boardName.Remove(boardName.Length - 1);
|
||||
}
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
LagCount = 0;
|
||||
IsLagFrame = false;
|
||||
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedOverflow[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
if (_numCores > 0)
|
||||
{
|
||||
L.Dispose();
|
||||
L = null;
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedCores[i].Dispose();
|
||||
_linkedCores[i] = null;
|
||||
_linkedBlips[i].Dispose();
|
||||
_linkedBlips[i] = null;
|
||||
}
|
||||
|
||||
R.Dispose();
|
||||
R = null;
|
||||
|
||||
_blipLeft.Dispose();
|
||||
_blipLeft = null;
|
||||
|
||||
_blipRight.Dispose();
|
||||
_blipRight = null;
|
||||
|
||||
_disposed = true;
|
||||
_numCores = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||
{
|
||||
public partial class GambatteLink
|
||||
{
|
||||
private IMemoryDomains _memoryDomains;
|
||||
|
||||
private void SetMemoryDomains()
|
||||
{
|
||||
var mm = new List<MemoryDomain>();
|
||||
|
||||
foreach (var md in L.MemoryDomains)
|
||||
{
|
||||
mm.Add(new WrappedMemoryDomain("L " + md.Name, md));
|
||||
}
|
||||
|
||||
foreach (var md in R.MemoryDomains)
|
||||
{
|
||||
mm.Add(new WrappedMemoryDomain("R " + md.Name, md));
|
||||
}
|
||||
|
||||
_memoryDomains = new MemoryDomainList(mm);
|
||||
(ServiceProvider as BasicServiceProvider).Register<IMemoryDomains>(_memoryDomains);
|
||||
}
|
||||
|
||||
// todo: clean this up
|
||||
private class WrappedMemoryDomain : MemoryDomain
|
||||
{
|
||||
private readonly MemoryDomain _m;
|
||||
|
||||
public WrappedMemoryDomain(string name, MemoryDomain m)
|
||||
{
|
||||
_m = m;
|
||||
|
||||
Name = name;
|
||||
Size = m.Size;
|
||||
WordSize = m.WordSize;
|
||||
EndianType = m.EndianType;
|
||||
Writable = m.Writable;
|
||||
}
|
||||
|
||||
public override byte PeekByte(long addr)
|
||||
{
|
||||
return _m.PeekByte(addr);
|
||||
}
|
||||
|
||||
public override void PokeByte(long addr, byte val)
|
||||
{
|
||||
_m.PokeByte(addr, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||
{
|
||||
public partial class GambatteLink : ISaveRam
|
||||
{
|
||||
public bool SaveRamModified => L.SaveRamModified || R.SaveRamModified;
|
||||
|
||||
public byte[] CloneSaveRam()
|
||||
{
|
||||
var lb = L.CloneSaveRam()!;
|
||||
var rb = R.CloneSaveRam()!;
|
||||
byte[] ret = new byte[lb.Length + rb.Length];
|
||||
Buffer.BlockCopy(lb, 0, ret, 0, lb.Length);
|
||||
Buffer.BlockCopy(rb, 0, ret, lb.Length, rb.Length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void StoreSaveRam(byte[] data)
|
||||
{
|
||||
var lb = new byte[L.CloneSaveRam()!.Length];
|
||||
var rb = new byte[R.CloneSaveRam()!.Length];
|
||||
Buffer.BlockCopy(data, 0, lb, 0, lb.Length);
|
||||
Buffer.BlockCopy(data, lb.Length, rb, 0, rb.Length);
|
||||
L.StoreSaveRam(lb);
|
||||
R.StoreSaveRam(rb);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,76 +4,78 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
{
|
||||
public partial class GambatteLink : ISettable<GambatteLink.GambatteLinkSettings, GambatteLink.GambatteLinkSyncSettings>
|
||||
{
|
||||
private GambatteLinkSettings _settings;
|
||||
private GambatteLinkSyncSettings _syncSettings;
|
||||
|
||||
public GambatteLinkSettings GetSettings()
|
||||
{
|
||||
return new GambatteLinkSettings
|
||||
(
|
||||
L.GetSettings(),
|
||||
R.GetSettings()
|
||||
);
|
||||
return _settings.Clone();
|
||||
}
|
||||
|
||||
public GambatteLinkSyncSettings GetSyncSettings()
|
||||
{
|
||||
return new GambatteLinkSyncSettings
|
||||
(
|
||||
L.GetSyncSettings(),
|
||||
R.GetSyncSettings()
|
||||
);
|
||||
return _syncSettings.Clone();
|
||||
}
|
||||
|
||||
public PutSettingsDirtyBits PutSettings(GambatteLinkSettings o)
|
||||
{
|
||||
return (PutSettingsDirtyBits)((int)L.PutSettings(o.L) | (int)R.PutSettings(o.R));
|
||||
_settings = o;
|
||||
var ret = PutSettingsDirtyBits.None;
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
ret |= _linkedCores[i].PutSettings(o._linkedSettings[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public PutSettingsDirtyBits PutSyncSettings(GambatteLinkSyncSettings o)
|
||||
{
|
||||
return (PutSettingsDirtyBits)((int)L.PutSyncSettings(o.L) | (int)R.PutSyncSettings(o.R));
|
||||
_syncSettings = o;
|
||||
var ret = PutSettingsDirtyBits.None;
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
ret |= _linkedCores[i].PutSyncSettings(o._linkedSyncSettings[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public class GambatteLinkSettings
|
||||
{
|
||||
public Gameboy.GambatteSettings L;
|
||||
public Gameboy.GambatteSettings R;
|
||||
public Gameboy.GambatteSettings[] _linkedSettings;
|
||||
|
||||
public GambatteLinkSettings()
|
||||
{
|
||||
L = new Gameboy.GambatteSettings();
|
||||
R = new Gameboy.GambatteSettings();
|
||||
_linkedSettings = new Gameboy.GambatteSettings[MAX_PLAYERS] { new(), new(), new(), new() };
|
||||
}
|
||||
|
||||
public GambatteLinkSettings(Gameboy.GambatteSettings L, Gameboy.GambatteSettings R)
|
||||
public GambatteLinkSettings(Gameboy.GambatteSettings one, Gameboy.GambatteSettings two, Gameboy.GambatteSettings three, Gameboy.GambatteSettings four)
|
||||
{
|
||||
this.L = L;
|
||||
this.R = R;
|
||||
_linkedSettings = new Gameboy.GambatteSettings[MAX_PLAYERS] { one, two, three, four };
|
||||
}
|
||||
|
||||
public GambatteLinkSettings Clone()
|
||||
{
|
||||
return new GambatteLinkSettings(L.Clone(), R.Clone());
|
||||
return new GambatteLinkSettings(_linkedSettings[P1].Clone(), _linkedSettings[P2].Clone(), _linkedSettings[P3].Clone(), _linkedSettings[P4].Clone());
|
||||
}
|
||||
}
|
||||
|
||||
public class GambatteLinkSyncSettings
|
||||
{
|
||||
public Gameboy.GambatteSyncSettings L;
|
||||
public Gameboy.GambatteSyncSettings R;
|
||||
public Gameboy.GambatteSyncSettings[] _linkedSyncSettings;
|
||||
|
||||
public GambatteLinkSyncSettings()
|
||||
{
|
||||
L = new Gameboy.GambatteSyncSettings();
|
||||
R = new Gameboy.GambatteSyncSettings();
|
||||
_linkedSyncSettings = new Gameboy.GambatteSyncSettings[MAX_PLAYERS] { new(), new(), new(), new() };
|
||||
}
|
||||
|
||||
public GambatteLinkSyncSettings(Gameboy.GambatteSyncSettings L, Gameboy.GambatteSyncSettings R)
|
||||
public GambatteLinkSyncSettings(Gameboy.GambatteSyncSettings one, Gameboy.GambatteSyncSettings two, Gameboy.GambatteSyncSettings three, Gameboy.GambatteSyncSettings four)
|
||||
{
|
||||
this.L = L;
|
||||
this.R = R;
|
||||
_linkedSyncSettings = new Gameboy.GambatteSyncSettings[MAX_PLAYERS] { one, two, three, four };
|
||||
}
|
||||
|
||||
public GambatteLinkSyncSettings Clone()
|
||||
{
|
||||
return new GambatteLinkSyncSettings(L.Clone(), R.Clone());
|
||||
return new GambatteLinkSyncSettings(_linkedSyncSettings[P1].Clone(), _linkedSyncSettings[P2].Clone(), _linkedSyncSettings[P3].Clone(), _linkedSyncSettings[P4].Clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,111 +1,154 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||
{
|
||||
public partial class GambatteLink : ISoundProvider
|
||||
{
|
||||
public bool CanProvideAsync => false;
|
||||
|
||||
public void SetSyncMode(SyncSoundMode mode)
|
||||
{
|
||||
if (mode == SyncSoundMode.Async)
|
||||
{
|
||||
throw new NotSupportedException("Async mode is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
||||
|
||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||
{
|
||||
nsamp = _sampleBufferContains;
|
||||
samples = SampleBuffer;
|
||||
}
|
||||
|
||||
public void GetSamplesAsync(short[] samples)
|
||||
{
|
||||
throw new InvalidOperationException("Async mode is not supported.");
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
_sampleBufferContains = 0;
|
||||
}
|
||||
|
||||
// i tried using the left and right buffers and then mixing them together... it was kind of a mess of code, and slow
|
||||
private BlipBuffer _blipLeft;
|
||||
private BlipBuffer _blipRight;
|
||||
|
||||
private readonly short[] LeftBuffer = new short[(35112 + 2064) * 2];
|
||||
private readonly short[] RightBuffer = new short[(35112 + 2064) * 2];
|
||||
|
||||
private readonly short[] SampleBuffer = new short[1536];
|
||||
private int _sampleBufferContains = 0;
|
||||
|
||||
private int _latchLeft;
|
||||
private int _latchRight;
|
||||
|
||||
private unsafe void PrepSound()
|
||||
{
|
||||
fixed (short* sl = LeftBuffer, sr = RightBuffer)
|
||||
{
|
||||
for (uint i = 0; i < SampPerFrame * 2; i += 2)
|
||||
{
|
||||
int s = (sl[i] + sl[i + 1]) / 2;
|
||||
if (s != _latchLeft)
|
||||
{
|
||||
_blipLeft.AddDelta(i, s - _latchLeft);
|
||||
_latchLeft = s;
|
||||
}
|
||||
|
||||
s = (sr[i] + sr[i + 1]) / 2;
|
||||
if (s != _latchRight)
|
||||
{
|
||||
_blipRight.AddDelta(i, s - _latchRight);
|
||||
_latchRight = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_blipLeft.EndFrame(SampPerFrame * 2);
|
||||
_blipRight.EndFrame(SampPerFrame * 2);
|
||||
int count = _blipLeft.SamplesAvailable();
|
||||
if (count != _blipRight.SamplesAvailable())
|
||||
{
|
||||
throw new Exception("Sound problem?");
|
||||
}
|
||||
|
||||
// calling blip.Clear() causes rounding fractions to be reset,
|
||||
// and if only one channel is muted, in subsequent frames we can be off by a sample or two
|
||||
// not a big deal, but we didn't account for it. so we actually complete the entire
|
||||
// audio read and then stamp it out if muted.
|
||||
_blipLeft.ReadSamplesLeft(SampleBuffer, count);
|
||||
if (L.Muted)
|
||||
{
|
||||
fixed (short* p = SampleBuffer)
|
||||
{
|
||||
for (int i = 0; i < SampleBuffer.Length; i += 2)
|
||||
{
|
||||
p[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_blipRight.ReadSamplesRight(SampleBuffer, count);
|
||||
if (R.Muted)
|
||||
{
|
||||
fixed (short* p = SampleBuffer)
|
||||
{
|
||||
for (int i = 1; i < SampleBuffer.Length; i += 2)
|
||||
{
|
||||
p[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sampleBufferContains = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||
{
|
||||
public partial class GambatteLink : ISoundProvider
|
||||
{
|
||||
public bool CanProvideAsync => false;
|
||||
|
||||
public void SetSyncMode(SyncSoundMode mode)
|
||||
{
|
||||
if (mode == SyncSoundMode.Async)
|
||||
{
|
||||
throw new NotSupportedException("Async mode is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
||||
|
||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||
{
|
||||
nsamp = _sampleBufferContains;
|
||||
samples = SampleBuffer;
|
||||
}
|
||||
|
||||
public void GetSamplesAsync(short[] samples)
|
||||
{
|
||||
throw new InvalidOperationException("Async mode is not supported.");
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
_sampleBufferContains = 0;
|
||||
}
|
||||
|
||||
// i tried using the left and right buffers and then mixing them together... it was kind of a mess of code, and slow
|
||||
private readonly BlipBuffer[] _linkedBlips;
|
||||
|
||||
private readonly short[] SoundBuffer;
|
||||
|
||||
private readonly short[] ScratchBuffer = new short[1536];
|
||||
|
||||
private readonly short[] SampleBuffer = new short[1536];
|
||||
private int _sampleBufferContains = 0;
|
||||
|
||||
private readonly int[] _linkedLatches;
|
||||
|
||||
private unsafe void PrepSound()
|
||||
{
|
||||
fixed (short* sb = &SoundBuffer[0])
|
||||
{
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
for (uint j = 0; j < SampPerFrame * 2; j += 2)
|
||||
{
|
||||
int s = (sb[(i * MaxSampsPerFrame) + j] + sb[(i * MaxSampsPerFrame) + j + 1]) / 2;
|
||||
if (s != _linkedLatches[i])
|
||||
{
|
||||
_linkedBlips[i].AddDelta(j, s - _linkedLatches[i]);
|
||||
_linkedLatches[i] = s;
|
||||
}
|
||||
}
|
||||
_linkedBlips[i].EndFrame(SampPerFrame * 2);
|
||||
}
|
||||
}
|
||||
|
||||
int count = _linkedBlips[P1].SamplesAvailable();
|
||||
for (int i = 1; i < _numCores; i++)
|
||||
{
|
||||
if (count != _linkedBlips[i].SamplesAvailable())
|
||||
{
|
||||
throw new Exception("Sound problem?");
|
||||
}
|
||||
}
|
||||
|
||||
// calling blip.Clear() causes rounding fractions to be reset,
|
||||
// and if only one channel is muted, in subsequent frames we can be off by a sample or two
|
||||
// not a big deal, but we didn't account for it. so we actually complete the entire
|
||||
// audio read and then stamp it out if muted.
|
||||
|
||||
switch (_numCores)
|
||||
{
|
||||
case 2:
|
||||
{
|
||||
// no need to do any complicated mixing
|
||||
_linkedBlips[P1].ReadSamplesLeft(_settings._linkedSettings[P1].Muted ? ScratchBuffer : SampleBuffer, count);
|
||||
_linkedBlips[P2].ReadSamplesRight(_settings._linkedSettings[P2].Muted ? ScratchBuffer : SampleBuffer, count);
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// since P2 is center, mix its samples with P1 and P3
|
||||
_linkedBlips[P1].ReadSamplesLeft(_settings._linkedSettings[P1].Muted ? ScratchBuffer : SampleBuffer, count);
|
||||
_linkedBlips[P3].ReadSamplesRight(_settings._linkedSettings[P3].Muted ? ScratchBuffer : SampleBuffer, count);
|
||||
_linkedBlips[P2].ReadSamples(ScratchBuffer, count, false);
|
||||
if (!_settings._linkedSettings[P2].Muted)
|
||||
{
|
||||
fixed (short* p = SampleBuffer, q = ScratchBuffer)
|
||||
{
|
||||
for (int i = 0; i < SampleBuffer.Length; i++)
|
||||
{
|
||||
int s = (p[i] + q[i]) / 2;
|
||||
p[i] = (short)s;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// since P1 and P2 are left side and P3 and P4 are right side, mix their samples accordingly
|
||||
_linkedBlips[P1].ReadSamplesLeft(_settings._linkedSettings[P1].Muted ? ScratchBuffer : SampleBuffer, count);
|
||||
_linkedBlips[P3].ReadSamplesRight(_settings._linkedSettings[P3].Muted ? ScratchBuffer : SampleBuffer, count);
|
||||
_linkedBlips[P2].ReadSamplesLeft(ScratchBuffer, count);
|
||||
_linkedBlips[P4].ReadSamplesRight(ScratchBuffer, count);
|
||||
if (_settings._linkedSettings[P2].Muted)
|
||||
{
|
||||
fixed (short* p = ScratchBuffer)
|
||||
{
|
||||
for (int i = 0; i < ScratchBuffer.Length; i += 2)
|
||||
{
|
||||
p[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_settings._linkedSettings[P4].Muted)
|
||||
{
|
||||
fixed (short* p = ScratchBuffer)
|
||||
{
|
||||
for (int i = 1; i < ScratchBuffer.Length; i += 2)
|
||||
{
|
||||
p[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
fixed (short* p = SampleBuffer, q = ScratchBuffer)
|
||||
{
|
||||
for (int i = 0; i < SampleBuffer.Length; i++)
|
||||
{
|
||||
int s = (p[i] + q[i]) / 2;
|
||||
p[i] = (short)s;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
_sampleBufferContains = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
@ -10,87 +11,119 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
{
|
||||
public void SaveStateText(TextWriter writer)
|
||||
{
|
||||
var s = new DGBSerialized
|
||||
{
|
||||
L = L.SaveState(),
|
||||
R = R.SaveState(),
|
||||
IsLagFrame = IsLagFrame,
|
||||
LagCount = LagCount,
|
||||
Frame = Frame,
|
||||
overflowL = _overflowL,
|
||||
overflowR = _overflowR,
|
||||
LatchL = _latchLeft,
|
||||
LatchR = _latchRight,
|
||||
cableconnected = _cableconnected,
|
||||
cablediscosignal = _cablediscosignal
|
||||
};
|
||||
ser.Serialize(writer, s);
|
||||
ser.Serialize(writer, new DGBSerialized(this));
|
||||
}
|
||||
|
||||
public void LoadStateText(TextReader reader)
|
||||
{
|
||||
var s = (DGBSerialized)ser.Deserialize(reader, typeof(DGBSerialized));
|
||||
L.LoadState(s.L);
|
||||
R.LoadState(s.R);
|
||||
if (s.NumCores != _numCores)
|
||||
{
|
||||
throw new InvalidOperationException("Core number mismatch!");
|
||||
}
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedCores[i].LoadState(s.LinkedStates[i]);
|
||||
_linkedOverflow[i] = s.LinkedOverflow[i];
|
||||
_linkedLatches[i] = s.LinkedLatches[i];
|
||||
}
|
||||
IsLagFrame = s.IsLagFrame;
|
||||
LagCount = s.LagCount;
|
||||
Frame = s.Frame;
|
||||
_overflowL = s.overflowL;
|
||||
_overflowR = s.overflowR;
|
||||
_latchLeft = s.LatchL;
|
||||
_latchRight = s.LatchR;
|
||||
_cableconnected = s.cableconnected;
|
||||
_cablediscosignal = s.cablediscosignal;
|
||||
_cableConnected = s.CableConnected;
|
||||
_cableDiscoSignal = s.CableDiscoSignal;
|
||||
_cableShifted = s.CableShifted;
|
||||
_cableShiftSignal = s.CableShiftSignal;
|
||||
_cableSpaced = s.CableSpaced;
|
||||
_cableSpaceSignal = s.CableSpaceSignal;
|
||||
}
|
||||
|
||||
public void SaveStateBinary(BinaryWriter writer)
|
||||
{
|
||||
L.SaveStateBinary(writer);
|
||||
R.SaveStateBinary(writer);
|
||||
writer.Write(_numCores);
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedCores[i].SaveStateBinary(writer);
|
||||
writer.Write(_linkedOverflow[i]);
|
||||
writer.Write(_linkedLatches[i]);
|
||||
}
|
||||
// other variables
|
||||
writer.Write(IsLagFrame);
|
||||
writer.Write(LagCount);
|
||||
writer.Write(Frame);
|
||||
writer.Write(_overflowL);
|
||||
writer.Write(_overflowR);
|
||||
writer.Write(_latchLeft);
|
||||
writer.Write(_latchRight);
|
||||
writer.Write(_cableconnected);
|
||||
writer.Write(_cablediscosignal);
|
||||
writer.Write(_cableConnected);
|
||||
writer.Write(_cableDiscoSignal);
|
||||
writer.Write(_cableShifted);
|
||||
writer.Write(_cableShiftSignal);
|
||||
writer.Write(_cableSpaced);
|
||||
writer.Write(_cableSpaceSignal);
|
||||
}
|
||||
|
||||
public void LoadStateBinary(BinaryReader reader)
|
||||
{
|
||||
L.LoadStateBinary(reader);
|
||||
R.LoadStateBinary(reader);
|
||||
if (_numCores != reader.ReadInt32())
|
||||
{
|
||||
throw new InvalidOperationException("Core number mismatch!");
|
||||
}
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedCores[i].LoadStateBinary(reader);
|
||||
_linkedOverflow[i] = reader.ReadInt32();
|
||||
_linkedLatches[i] = reader.ReadInt32();
|
||||
}
|
||||
// other variables
|
||||
IsLagFrame = reader.ReadBoolean();
|
||||
LagCount = reader.ReadInt32();
|
||||
Frame = reader.ReadInt32();
|
||||
_overflowL = reader.ReadInt32();
|
||||
_overflowR = reader.ReadInt32();
|
||||
_latchLeft = reader.ReadInt32();
|
||||
_latchRight = reader.ReadInt32();
|
||||
_cableconnected = reader.ReadBoolean();
|
||||
_cablediscosignal = reader.ReadBoolean();
|
||||
_cableConnected = reader.ReadBoolean();
|
||||
_cableDiscoSignal = reader.ReadBoolean();
|
||||
_cableShifted = reader.ReadBoolean();
|
||||
_cableShiftSignal = reader.ReadBoolean();
|
||||
_cableSpaced = reader.ReadBoolean();
|
||||
_cableSpaceSignal = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
private readonly JsonSerializer ser = new JsonSerializer { Formatting = Formatting.Indented };
|
||||
|
||||
private class DGBSerialized
|
||||
{
|
||||
public TextState<Gameboy.TextStateData> L;
|
||||
public TextState<Gameboy.TextStateData> R;
|
||||
public int NumCores;
|
||||
public TextState<Gameboy.TextStateData>[] LinkedStates;
|
||||
// other data
|
||||
public bool IsLagFrame;
|
||||
public int LagCount;
|
||||
public int Frame;
|
||||
public int overflowL;
|
||||
public int overflowR;
|
||||
public int LatchL;
|
||||
public int LatchR;
|
||||
public bool cableconnected;
|
||||
public bool cablediscosignal;
|
||||
public int[] LinkedOverflow;
|
||||
public int[] LinkedLatches;
|
||||
public bool CableConnected;
|
||||
public bool CableDiscoSignal;
|
||||
public bool CableShifted;
|
||||
public bool CableShiftSignal;
|
||||
public bool CableSpaced;
|
||||
public bool CableSpaceSignal;
|
||||
|
||||
public DGBSerialized(GambatteLink linkcore)
|
||||
{
|
||||
NumCores = linkcore._numCores;
|
||||
LinkedStates = new TextState<Gameboy.TextStateData>[NumCores];
|
||||
LinkedOverflow = new int[NumCores];
|
||||
LinkedLatches = new int[NumCores];
|
||||
for (int i = 0; i < NumCores; i++)
|
||||
{
|
||||
LinkedStates[i] = linkcore._linkedCores[i].SaveState();
|
||||
LinkedOverflow[i] = linkcore._linkedOverflow[i];
|
||||
LinkedLatches[i] = linkcore._linkedLatches[i];
|
||||
}
|
||||
IsLagFrame = linkcore.IsLagFrame;
|
||||
LagCount = linkcore.LagCount;
|
||||
Frame = linkcore.Frame;
|
||||
CableConnected = linkcore._cableConnected;
|
||||
CableDiscoSignal = linkcore._cableDiscoSignal;
|
||||
CableShifted = linkcore._cableShifted;
|
||||
CableShiftSignal = linkcore._cableShiftSignal;
|
||||
CableSpaced = linkcore._cableSpaced;
|
||||
CableSpaceSignal = linkcore._cableSpaceSignal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,32 @@
|
|||
using BizHawk.Emulation.Common;
|
||||
using System;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||
{
|
||||
public partial class GambatteLink : IVideoProvider
|
||||
{
|
||||
public int VirtualWidth => 320;
|
||||
public int VirtualWidth => 160 * _numCores;
|
||||
public int VirtualHeight => 144;
|
||||
public int BufferWidth => 320;
|
||||
public int BufferWidth => 160 * _numCores;
|
||||
public int BufferHeight => 144;
|
||||
|
||||
public int VsyncNumerator => L.VsyncNumerator;
|
||||
public int VsyncNumerator => _linkedCores[P1].VsyncNumerator;
|
||||
|
||||
public int VsyncDenominator => L.VsyncDenominator;
|
||||
public int VsyncDenominator => _linkedCores[P1].VsyncDenominator;
|
||||
|
||||
public int BackgroundColor => unchecked((int)0xff000000);
|
||||
|
||||
private readonly int[] FrameBuffer = CreateVideoBuffer();
|
||||
private readonly int[] FrameBuffer;
|
||||
|
||||
public int[] GetVideoBuffer() => VideoBuffer;
|
||||
|
||||
private readonly int[] VideoBuffer = CreateVideoBuffer();
|
||||
private readonly int[] VideoBuffer;
|
||||
|
||||
private static int[] CreateVideoBuffer()
|
||||
private int[] CreateVideoBuffer()
|
||||
{
|
||||
var b = new int[160 * 2 * 144];
|
||||
for (int i = 0; i < (160 * 2 * 144); i++)
|
||||
var b = new int[BufferWidth * BufferHeight];
|
||||
for (int i = 0; i < b.Length; i++)
|
||||
{
|
||||
b[i] = -1; // GB/C screen is disabled on bootup, so it always starts as white, not black
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
|
@ -6,32 +7,42 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
{
|
||||
[PortedCore(CoreNames.DualGambatte, "sinamas/natt")]
|
||||
[ServiceNotApplicable(new[] { typeof(IDriveLight) })]
|
||||
public partial class GambatteLink : IEmulator, IVideoProvider, ISoundProvider, IInputPollable, ISaveRam, IStatable, ILinkable,
|
||||
IBoardInfo, IRomInfo, IDebuggable, ISettable<GambatteLink.GambatteLinkSettings, GambatteLink.GambatteLinkSyncSettings>, ICodeDataLogger
|
||||
public partial class GambatteLink : ILinkable, IRomInfo
|
||||
{
|
||||
[CoreConstructor(VSystemID.Raw.DGB)]
|
||||
public GambatteLink(CoreLoadParameters<GambatteLinkSettings, GambatteLinkSyncSettings> lp)
|
||||
{
|
||||
if (lp.Roms.Count != 2)
|
||||
if (lp.Roms.Count < MIN_PLAYERS || lp.Roms.Count > MAX_PLAYERS)
|
||||
throw new InvalidOperationException("Wrong number of roms");
|
||||
|
||||
ServiceProvider = new BasicServiceProvider(this);
|
||||
GambatteLinkSettings linkSettings = lp.Settings ?? new GambatteLinkSettings();
|
||||
GambatteLinkSyncSettings linkSyncSettings = lp.SyncSettings ?? new GambatteLinkSyncSettings();
|
||||
_numCores = lp.Roms.Count;
|
||||
|
||||
L = new Gameboy(lp.Comm, lp.Roms[0].Game, lp.Roms[0].RomData, linkSettings.L, linkSyncSettings.L, lp.DeterministicEmulationRequested);
|
||||
R = new Gameboy(lp.Comm, lp.Roms[1].Game, lp.Roms[1].RomData, linkSettings.R, linkSyncSettings.R, lp.DeterministicEmulationRequested);
|
||||
_serviceProvider = new BasicServiceProvider(this);
|
||||
_settings = lp.Settings ?? new GambatteLinkSettings();
|
||||
_syncSettings = lp.SyncSettings ?? new GambatteLinkSyncSettings();
|
||||
|
||||
// connect link cable
|
||||
LibGambatte.gambatte_linkstatus(L.GambatteState, 259);
|
||||
LibGambatte.gambatte_linkstatus(R.GambatteState, 259);
|
||||
_linkedCores = new Gameboy[_numCores];
|
||||
_linkedConts = new SaveController[_numCores];
|
||||
_linkedBlips = new BlipBuffer[_numCores];
|
||||
_linkedLatches = new int[_numCores];
|
||||
_linkedOverflow = new int[_numCores];
|
||||
|
||||
L.ConnectInputCallbackSystem(_inputCallbacks);
|
||||
R.ConnectInputCallbackSystem(_inputCallbacks);
|
||||
L.ConnectMemoryCallbackSystem(_memorycallbacks);
|
||||
R.ConnectMemoryCallbackSystem(_memorycallbacks);
|
||||
RomDetails = "";
|
||||
_memoryCallbacks = new MemoryCallbackSystem(new[] { "System Bus" });
|
||||
|
||||
RomDetails = "LEFT:\r\n" + L.RomDetails + "RIGHT:\r\n" + R.RomDetails;
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
_linkedCores[i] = new Gameboy(lp.Comm, lp.Roms[i].Game, lp.Roms[i].RomData, _settings._linkedSettings[i], _syncSettings._linkedSyncSettings[i], lp.DeterministicEmulationRequested);
|
||||
LibGambatte.gambatte_linkstatus(_linkedCores[i].GambatteState, 259); // connect link cable
|
||||
_linkedCores[i].ConnectInputCallbackSystem(_inputCallbacks);
|
||||
_linkedCores[i].ConnectMemoryCallbackSystem(_memoryCallbacks);
|
||||
_linkedConts[i] = new SaveController(Gameboy.CreateControllerDefinition(false, false));
|
||||
_linkedBlips[i] = new BlipBuffer(1024);
|
||||
_linkedBlips[i].SetRates(2097152 * 2, 44100);
|
||||
_linkedOverflow[i] = 0;
|
||||
_linkedLatches[i] = 0;
|
||||
RomDetails += $"P{i + 1}:\r\n" + _linkedCores[i].RomDetails;
|
||||
}
|
||||
|
||||
LinkConnected = true;
|
||||
|
||||
|
@ -39,56 +50,99 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
LagCount = 0;
|
||||
IsLagFrame = false;
|
||||
|
||||
_blipLeft = new BlipBuffer(1024);
|
||||
_blipRight = new BlipBuffer(1024);
|
||||
_blipLeft.SetRates(2097152 * 2, 44100);
|
||||
_blipRight.SetRates(2097152 * 2, 44100);
|
||||
SoundBuffer = new short[MaxSampsPerFrame * _numCores];
|
||||
|
||||
SetMemoryDomains();
|
||||
FrameBuffer = CreateVideoBuffer();
|
||||
VideoBuffer = CreateVideoBuffer();
|
||||
|
||||
GBLinkController = CreateControllerDefinition();
|
||||
|
||||
_linkedSaveRam = new LinkedSaveRam(_linkedCores, _numCores);
|
||||
_serviceProvider.Register<ISaveRam>(_linkedSaveRam);
|
||||
|
||||
_linkedMemoryDomains = new LinkedMemoryDomains(_linkedCores, _numCores);
|
||||
_serviceProvider.Register<IMemoryDomains>(_linkedMemoryDomains);
|
||||
|
||||
_linkedDebuggable = new LinkedDebuggable(_linkedCores, _numCores, _memoryCallbacks);
|
||||
_serviceProvider.Register<IDebuggable>(_linkedDebuggable);
|
||||
}
|
||||
|
||||
private readonly BasicServiceProvider _serviceProvider;
|
||||
|
||||
private readonly MemoryCallbackSystem _memoryCallbacks;
|
||||
|
||||
public string RomDetails { get; }
|
||||
|
||||
public bool LinkConnected
|
||||
{
|
||||
get => _cableconnected;
|
||||
set => _cableconnected = value;
|
||||
get => _cableConnected;
|
||||
set => _cableConnected = value;
|
||||
}
|
||||
|
||||
private bool _disposed = false;
|
||||
private int _numCores = 0;
|
||||
private readonly Gameboy[] _linkedCores;
|
||||
|
||||
private Gameboy L;
|
||||
private Gameboy R;
|
||||
private readonly LinkedSaveRam _linkedSaveRam;
|
||||
private readonly LinkedMemoryDomains _linkedMemoryDomains;
|
||||
private readonly LinkedDebuggable _linkedDebuggable;
|
||||
|
||||
// counter to ensure we do 35112 samples per frame
|
||||
private int _overflowL = 0;
|
||||
private int _overflowR = 0;
|
||||
// counters to ensure we do 35112 samples per frame
|
||||
private readonly int[] _linkedOverflow;
|
||||
|
||||
// if true, the link cable is currently connected
|
||||
private bool _cableconnected = true;
|
||||
private bool _cableConnected = true;
|
||||
|
||||
// if true, the link cable is currently shifted (3x/4x only)
|
||||
private bool _cableShifted = false;
|
||||
|
||||
// if true, the link cable is currently spaced outwards (3x/4x only)
|
||||
private bool _cableSpaced = false;
|
||||
|
||||
// if true, the link cable toggle signal is currently asserted
|
||||
private bool _cablediscosignal = false;
|
||||
private bool _cableDiscoSignal = false;
|
||||
|
||||
// if true, the link cable shift signal is currently asserted
|
||||
private bool _cableShiftSignal = false;
|
||||
|
||||
// if true, the link cable spacing signal is currently asserted
|
||||
private bool _cableSpaceSignal = false;
|
||||
|
||||
private const int SampPerFrame = 35112;
|
||||
private const int MaxSampsPerFrame = (SampPerFrame + 2064) * 2;
|
||||
|
||||
private readonly SaveController LCont = new SaveController(Gameboy.CreateControllerDefinition(false, false));
|
||||
private readonly SaveController RCont = new SaveController(Gameboy.CreateControllerDefinition(false, false));
|
||||
private readonly SaveController[] _linkedConts;
|
||||
|
||||
public bool IsCGBMode(bool right)
|
||||
public bool IsCGBMode(int which)
|
||||
{
|
||||
return right ? R.IsCGBMode() : L.IsCGBMode();
|
||||
return which < _numCores && _linkedCores[which].IsCGBMode();
|
||||
}
|
||||
|
||||
private static readonly ControllerDefinition DualGbController = new ControllerDefinition
|
||||
private ControllerDefinition GBLinkController { get; }
|
||||
|
||||
private ControllerDefinition CreateControllerDefinition()
|
||||
{
|
||||
Name = "Dual Gameboy Controller",
|
||||
BoolButtons =
|
||||
var ret = new ControllerDefinition { Name = $"GB Link {_numCores}x Controller" };
|
||||
for (int i = 0; i < _numCores; i++)
|
||||
{
|
||||
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 A", "P1 B", "P1 Select", "P1 Start", "P1 Power",
|
||||
"P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 A", "P2 B", "P2 Select", "P2 Start", "P2 Power",
|
||||
"Toggle Cable"
|
||||
ret.BoolButtons.AddRange(
|
||||
new[] { "Up", "Down", "Left", "Right", "A", "B", "Select", "Start", "Power" }
|
||||
.Select(s => $"P{i + 1} {s}"));
|
||||
}
|
||||
};
|
||||
ret.BoolButtons.Add("Toggle Cable Connection");
|
||||
if (_numCores > 2)
|
||||
{
|
||||
ret.BoolButtons.Add("Toggle Cable Shift");
|
||||
ret.BoolButtons.Add("Toggle Cable Spacing");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private const int P1 = 0;
|
||||
private const int P2 = 1;
|
||||
private const int P3 = 2;
|
||||
private const int P4 = 3;
|
||||
|
||||
private const int MIN_PLAYERS = 2;
|
||||
private const int MAX_PLAYERS = 4;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue