2012-12-29 01:25:06 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
2013-11-12 16:30:35 +00:00
|
|
|
|
using System.Linq;
|
2012-12-29 01:25:06 +00:00
|
|
|
|
|
2014-07-03 18:54:53 +00:00
|
|
|
|
using BizHawk.Common.BufferExtensions;
|
2013-11-04 01:06:36 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2013-11-13 23:36:21 +00:00
|
|
|
|
using BizHawk.Emulation.Cores.Nintendo.SNES;
|
2013-10-27 22:07:40 +00:00
|
|
|
|
|
2013-11-13 03:32:25 +00:00
|
|
|
|
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
2012-12-29 01:25:06 +00:00
|
|
|
|
{
|
2014-04-25 01:19:57 +00:00
|
|
|
|
[CoreAttributes(
|
|
|
|
|
"DualGambatte",
|
|
|
|
|
"sinamas/natt",
|
|
|
|
|
isPorted: true,
|
|
|
|
|
isReleased: true
|
|
|
|
|
)]
|
2012-12-29 01:25:06 +00:00
|
|
|
|
public class GambatteLink : IEmulator, IVideoProvider, ISyncSoundProvider
|
|
|
|
|
{
|
2012-12-29 17:11:19 +00:00
|
|
|
|
bool disposed = false;
|
|
|
|
|
|
2012-12-29 01:25:06 +00:00
|
|
|
|
Gameboy L;
|
|
|
|
|
Gameboy R;
|
2012-12-29 17:11:19 +00:00
|
|
|
|
// counter to ensure we do 35112 samples per frame
|
|
|
|
|
int overflowL = 0;
|
|
|
|
|
int overflowR = 0;
|
2014-08-18 02:31:42 +00:00
|
|
|
|
/// <summary>if true, the link cable is currently connected</summary>
|
|
|
|
|
bool cableconnected = true;
|
|
|
|
|
/// <summary>if true, the link cable toggle signal is currently asserted</summary>
|
|
|
|
|
bool cablediscosignal = false;
|
2012-12-29 17:11:19 +00:00
|
|
|
|
|
|
|
|
|
const int SampPerFrame = 35112;
|
2012-12-29 01:25:06 +00:00
|
|
|
|
|
2013-11-13 23:36:21 +00:00
|
|
|
|
LibsnesCore.SnesSaveController LCont = new LibsnesCore.SnesSaveController(Gameboy.GbController);
|
|
|
|
|
LibsnesCore.SnesSaveController RCont = new LibsnesCore.SnesSaveController(Gameboy.GbController);
|
2012-12-29 01:25:06 +00:00
|
|
|
|
|
2013-12-23 16:58:20 +00:00
|
|
|
|
public bool IsCGBMode(bool right)
|
|
|
|
|
{
|
|
|
|
|
return right ? R.IsCGBMode() : L.IsCGBMode();
|
|
|
|
|
}
|
2014-08-18 02:31:42 +00:00
|
|
|
|
public bool LinkCableConnected { get { return cableconnected; } }
|
2013-12-23 16:58:20 +00:00
|
|
|
|
|
2014-05-12 17:24:43 +00:00
|
|
|
|
public GambatteLink(CoreComm comm, GameInfo leftinfo, byte[] leftrom, GameInfo rightinfo, byte[] rightrom, object Settings, object SyncSettings, bool deterministic)
|
2012-12-29 01:25:06 +00:00
|
|
|
|
{
|
2014-07-14 16:56:23 +00:00
|
|
|
|
GambatteLinkSettings _Settings = (GambatteLinkSettings)Settings ?? new GambatteLinkSettings();
|
2014-07-14 16:10:45 +00:00
|
|
|
|
GambatteLinkSyncSettings _SyncSettings = (GambatteLinkSyncSettings)SyncSettings ?? new GambatteLinkSyncSettings();
|
2013-12-23 02:51:41 +00:00
|
|
|
|
|
2012-12-29 01:25:06 +00:00
|
|
|
|
CoreComm = comm;
|
2014-05-12 17:24:43 +00:00
|
|
|
|
L = new Gameboy(new CoreComm(comm.ShowMessage, comm.Notify), leftinfo, leftrom, _Settings.L, _SyncSettings.L, deterministic);
|
|
|
|
|
R = new Gameboy(new CoreComm(comm.ShowMessage, comm.Notify), rightinfo, rightrom, _Settings.R, _SyncSettings.R, deterministic);
|
2012-12-29 01:25:06 +00:00
|
|
|
|
|
2012-12-29 15:48:30 +00:00
|
|
|
|
// connect link cable
|
|
|
|
|
LibGambatte.gambatte_linkstatus(L.GambatteState, 259);
|
|
|
|
|
LibGambatte.gambatte_linkstatus(R.GambatteState, 259);
|
|
|
|
|
|
2012-12-29 01:25:06 +00:00
|
|
|
|
L.Controller = LCont;
|
|
|
|
|
R.Controller = RCont;
|
|
|
|
|
|
|
|
|
|
comm.VsyncNum = L.CoreComm.VsyncNum;
|
|
|
|
|
comm.VsyncDen = L.CoreComm.VsyncDen;
|
|
|
|
|
comm.RomStatusAnnotation = null;
|
|
|
|
|
comm.RomStatusDetails = "LEFT:\r\n" + L.CoreComm.RomStatusDetails + "RIGHT:\r\n" + R.CoreComm.RomStatusDetails;
|
|
|
|
|
comm.CpuTraceAvailable = false; // TODO
|
|
|
|
|
comm.NominalWidth = L.CoreComm.NominalWidth + R.CoreComm.NominalWidth;
|
|
|
|
|
comm.NominalHeight = L.CoreComm.NominalHeight;
|
|
|
|
|
|
|
|
|
|
Frame = 0;
|
|
|
|
|
LagCount = 0;
|
|
|
|
|
IsLagFrame = false;
|
2012-12-29 17:11:19 +00:00
|
|
|
|
|
2013-11-14 19:33:13 +00:00
|
|
|
|
blip_left = new BlipBuffer(1024);
|
|
|
|
|
blip_right = new BlipBuffer(1024);
|
2012-12-29 17:11:19 +00:00
|
|
|
|
blip_left.SetRates(2097152 * 2, 44100);
|
|
|
|
|
blip_right.SetRates(2097152 * 2, 44100);
|
2012-12-29 18:06:39 +00:00
|
|
|
|
|
|
|
|
|
SetMemoryDomains();
|
2013-05-11 23:46:23 +00:00
|
|
|
|
|
2013-11-10 18:15:32 +00:00
|
|
|
|
L.CoreComm.InputCallback = CoreComm.InputCallback;
|
|
|
|
|
R.CoreComm.InputCallback = CoreComm.InputCallback;
|
2012-12-29 01:25:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IVideoProvider VideoProvider { get { return this; } }
|
|
|
|
|
public ISoundProvider SoundProvider { get { return null; } }
|
|
|
|
|
public ISyncSoundProvider SyncSoundProvider { get { return this; } }
|
|
|
|
|
public bool StartAsyncSound() { return false; }
|
|
|
|
|
public void EndAsyncSound() { }
|
|
|
|
|
|
|
|
|
|
public static readonly ControllerDefinition DualGbController = new ControllerDefinition
|
|
|
|
|
{
|
|
|
|
|
Name = "Dual Gameboy Controller",
|
|
|
|
|
BoolButtons =
|
|
|
|
|
{
|
|
|
|
|
"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",
|
2014-08-18 02:31:42 +00:00
|
|
|
|
"Toggle Cable"
|
2012-12-29 01:25:06 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public ControllerDefinition ControllerDefinition { get { return DualGbController; } }
|
|
|
|
|
public IController Controller { get; set; }
|
|
|
|
|
|
|
|
|
|
public void FrameAdvance(bool render, bool rendersound = true)
|
|
|
|
|
{
|
|
|
|
|
LCont.Clear();
|
|
|
|
|
RCont.Clear();
|
|
|
|
|
|
|
|
|
|
foreach (var s in DualGbController.BoolButtons)
|
|
|
|
|
{
|
|
|
|
|
if (Controller[s])
|
|
|
|
|
{
|
|
|
|
|
if (s.Contains("P1 "))
|
|
|
|
|
LCont.Set(s.Replace("P1 ", ""));
|
|
|
|
|
else if (s.Contains("P2 "))
|
|
|
|
|
RCont.Set(s.Replace("P2 ", ""));
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-08-18 02:31:42 +00:00
|
|
|
|
bool cablediscosignal_new = Controller["Toggle Cable"];
|
|
|
|
|
if (cablediscosignal_new && !cablediscosignal)
|
|
|
|
|
{
|
|
|
|
|
cableconnected ^= true;
|
|
|
|
|
Console.WriteLine("Cable connect status to {0}", cableconnected);
|
|
|
|
|
}
|
|
|
|
|
cablediscosignal = cablediscosignal_new;
|
2012-12-29 01:25:06 +00:00
|
|
|
|
|
|
|
|
|
Frame++;
|
2012-12-29 15:48:30 +00:00
|
|
|
|
L.FrameAdvancePrep();
|
|
|
|
|
R.FrameAdvancePrep();
|
|
|
|
|
|
|
|
|
|
unsafe
|
|
|
|
|
{
|
|
|
|
|
fixed (int* leftvbuff = &VideoBuffer[0])
|
|
|
|
|
{
|
2012-12-29 17:11:19 +00:00
|
|
|
|
// use pitch to have both cores write to the same video buffer, interleaved
|
2012-12-29 15:48:30 +00:00
|
|
|
|
int* rightvbuff = leftvbuff + 160;
|
|
|
|
|
const int pitch = 160 * 2;
|
|
|
|
|
|
2012-12-29 17:11:19 +00:00
|
|
|
|
fixed (short* leftsbuff = LeftBuffer, rightsbuff = RightBuffer)
|
2012-12-29 15:48:30 +00:00
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
const int step = 32; // could be 1024 for GB
|
|
|
|
|
|
2012-12-29 17:11:19 +00:00
|
|
|
|
int nL = overflowL;
|
|
|
|
|
int nR = overflowR;
|
2012-12-29 15:48:30 +00:00
|
|
|
|
|
2012-12-29 17:11:19 +00:00
|
|
|
|
// slowly step our way through the frame, while continually checking and resolving link cable status
|
|
|
|
|
for (int target = 0; target < SampPerFrame;)
|
2012-12-29 15:48:30 +00:00
|
|
|
|
{
|
2012-12-29 17:11:19 +00:00
|
|
|
|
target += step;
|
2012-12-30 17:38:44 +00:00
|
|
|
|
if (target > SampPerFrame)
|
|
|
|
|
target = SampPerFrame; // don't run for slightly too long depending on step
|
2012-12-29 15:48:30 +00:00
|
|
|
|
|
2012-12-29 18:55:10 +00:00
|
|
|
|
// gambatte_runfor() aborts early when a frame is produced, but we don't want that, hence the while()
|
|
|
|
|
while (nL < target)
|
2012-12-29 15:48:30 +00:00
|
|
|
|
{
|
|
|
|
|
uint nsamp = (uint)(target - nL);
|
2014-05-03 03:05:34 +00:00
|
|
|
|
if (LibGambatte.gambatte_runfor(L.GambatteState, leftsbuff + nL * 2, ref nsamp) > 0)
|
|
|
|
|
LibGambatte.gambatte_blitto(L.GambatteState, leftvbuff, pitch);
|
2012-12-29 15:48:30 +00:00
|
|
|
|
nL += (int)nsamp;
|
|
|
|
|
}
|
2012-12-29 18:55:10 +00:00
|
|
|
|
while (nR < target)
|
2012-12-29 15:48:30 +00:00
|
|
|
|
{
|
|
|
|
|
uint nsamp = (uint)(target - nR);
|
2014-05-03 03:05:34 +00:00
|
|
|
|
if (LibGambatte.gambatte_runfor(R.GambatteState, rightsbuff + nR * 2, ref nsamp) > 0)
|
|
|
|
|
LibGambatte.gambatte_blitto(R.GambatteState, rightvbuff, pitch);
|
2012-12-29 15:48:30 +00:00
|
|
|
|
nR += (int)nsamp;
|
|
|
|
|
}
|
2014-08-18 02:31:42 +00:00
|
|
|
|
|
|
|
|
|
// poll link cable statuses, but not when the cable is disconnected
|
|
|
|
|
if (!cableconnected)
|
|
|
|
|
continue;
|
2012-12-29 15:48:30 +00:00
|
|
|
|
|
|
|
|
|
if (LibGambatte.gambatte_linkstatus(L.GambatteState, 256) != 0) // ClockTrigger
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-12-29 17:11:19 +00:00
|
|
|
|
overflowL = nL - SampPerFrame;
|
|
|
|
|
overflowR = nR - SampPerFrame;
|
|
|
|
|
if (overflowL < 0 || overflowR < 0)
|
2012-12-30 17:38:44 +00:00
|
|
|
|
throw new Exception("Timing problem?");
|
2012-12-29 15:48:30 +00:00
|
|
|
|
|
|
|
|
|
if (rendersound)
|
|
|
|
|
{
|
2012-12-29 17:11:19 +00:00
|
|
|
|
PrepSound();
|
2012-12-29 15:48:30 +00:00
|
|
|
|
}
|
2012-12-29 17:11:19 +00:00
|
|
|
|
// copy extra samples back to beginning
|
|
|
|
|
for (int i = 0; i < overflowL * 2; i++)
|
|
|
|
|
LeftBuffer[i] = LeftBuffer[i + SampPerFrame * 2];
|
|
|
|
|
for (int i = 0; i < overflowR * 2; i++)
|
|
|
|
|
RightBuffer[i] = RightBuffer[i + SampPerFrame * 2];
|
|
|
|
|
|
2012-12-29 15:48:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
L.FrameAdvancePost();
|
|
|
|
|
R.FrameAdvancePost();
|
2012-12-29 01:25:06 +00:00
|
|
|
|
IsLagFrame = L.IsLagFrame && R.IsLagFrame;
|
|
|
|
|
if (IsLagFrame)
|
|
|
|
|
LagCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int Frame { get; private set; }
|
|
|
|
|
public int LagCount { get; set; }
|
|
|
|
|
public bool IsLagFrame { get; private set; }
|
|
|
|
|
public string SystemId { get { return "DGB"; } }
|
2014-05-12 17:24:43 +00:00
|
|
|
|
public bool DeterministicEmulation { get { return L.DeterministicEmulation && R.DeterministicEmulation; } }
|
2012-12-29 01:25:06 +00:00
|
|
|
|
|
2013-08-24 16:54:22 +00:00
|
|
|
|
public string BoardName { get { return L.BoardName + '|' + R.BoardName; } }
|
|
|
|
|
|
2014-08-18 02:31:42 +00:00
|
|
|
|
public void ResetCounters()
|
|
|
|
|
{
|
|
|
|
|
Frame = 0;
|
|
|
|
|
LagCount = 0;
|
|
|
|
|
IsLagFrame = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public CoreComm CoreComm { get; private set; }
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
if (!disposed)
|
|
|
|
|
{
|
|
|
|
|
L.Dispose();
|
|
|
|
|
L = null;
|
|
|
|
|
R.Dispose();
|
|
|
|
|
R = null;
|
|
|
|
|
blip_left.Dispose();
|
|
|
|
|
blip_left = null;
|
|
|
|
|
blip_right.Dispose();
|
|
|
|
|
blip_right = null;
|
|
|
|
|
|
|
|
|
|
disposed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-29 01:25:06 +00:00
|
|
|
|
#region saveram
|
|
|
|
|
|
2014-08-13 17:52:13 +00:00
|
|
|
|
public byte[] CloneSaveRam()
|
2012-12-29 01:25:06 +00:00
|
|
|
|
{
|
2014-08-13 17:52:13 +00:00
|
|
|
|
byte[] lb = L.CloneSaveRam();
|
|
|
|
|
byte[] rb = R.CloneSaveRam();
|
2012-12-29 01:25:06 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2014-08-13 17:52:13 +00:00
|
|
|
|
byte[] lb = new byte[L.CloneSaveRam().Length];
|
|
|
|
|
byte[] rb = new byte[R.CloneSaveRam().Length];
|
2012-12-29 01:25:06 +00:00
|
|
|
|
Buffer.BlockCopy(data, 0, lb, 0, lb.Length);
|
|
|
|
|
Buffer.BlockCopy(data, lb.Length, rb, 0, rb.Length);
|
|
|
|
|
L.StoreSaveRam(lb);
|
|
|
|
|
R.StoreSaveRam(rb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ClearSaveRam()
|
|
|
|
|
{
|
|
|
|
|
L.ClearSaveRam();
|
|
|
|
|
R.ClearSaveRam();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool SaveRamModified
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return L.SaveRamModified || R.SaveRamModified;
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region savestates
|
|
|
|
|
|
|
|
|
|
public void SaveStateText(TextWriter writer)
|
|
|
|
|
{
|
|
|
|
|
var temp = SaveStateBinary();
|
|
|
|
|
temp.SaveAsHex(writer);
|
|
|
|
|
// write extra copy of stuff we don't use
|
|
|
|
|
writer.WriteLine("Frame {0}", Frame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadStateText(TextReader reader)
|
|
|
|
|
{
|
|
|
|
|
string hex = reader.ReadLine();
|
|
|
|
|
byte[] state = new byte[hex.Length / 2];
|
|
|
|
|
state.ReadFromHex(hex);
|
|
|
|
|
LoadStateBinary(new BinaryReader(new MemoryStream(state)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SaveStateBinary(BinaryWriter writer)
|
|
|
|
|
{
|
|
|
|
|
L.SaveStateBinary(writer);
|
|
|
|
|
R.SaveStateBinary(writer);
|
|
|
|
|
// other variables
|
|
|
|
|
writer.Write(IsLagFrame);
|
|
|
|
|
writer.Write(LagCount);
|
|
|
|
|
writer.Write(Frame);
|
2012-12-29 17:11:19 +00:00
|
|
|
|
writer.Write(overflowL);
|
|
|
|
|
writer.Write(overflowR);
|
|
|
|
|
writer.Write(LatchL);
|
|
|
|
|
writer.Write(LatchR);
|
2014-08-18 02:31:42 +00:00
|
|
|
|
writer.Write(cableconnected);
|
|
|
|
|
writer.Write(cablediscosignal);
|
2012-12-29 01:25:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadStateBinary(BinaryReader reader)
|
|
|
|
|
{
|
|
|
|
|
L.LoadStateBinary(reader);
|
|
|
|
|
R.LoadStateBinary(reader);
|
|
|
|
|
// other variables
|
|
|
|
|
IsLagFrame = reader.ReadBoolean();
|
|
|
|
|
LagCount = reader.ReadInt32();
|
|
|
|
|
Frame = reader.ReadInt32();
|
2012-12-29 17:11:19 +00:00
|
|
|
|
overflowL = reader.ReadInt32();
|
|
|
|
|
overflowR = reader.ReadInt32();
|
|
|
|
|
LatchL = reader.ReadInt32();
|
|
|
|
|
LatchR = reader.ReadInt32();
|
2014-08-18 02:31:42 +00:00
|
|
|
|
cableconnected = reader.ReadBoolean();
|
|
|
|
|
cablediscosignal = reader.ReadBoolean();
|
2012-12-29 01:25:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] SaveStateBinary()
|
|
|
|
|
{
|
|
|
|
|
MemoryStream ms = new MemoryStream();
|
|
|
|
|
BinaryWriter bw = new BinaryWriter(ms);
|
|
|
|
|
SaveStateBinary(bw);
|
|
|
|
|
bw.Flush();
|
|
|
|
|
return ms.ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-06 20:51:28 +00:00
|
|
|
|
public bool BinarySaveStatesPreferred { get { return true; } }
|
|
|
|
|
|
2012-12-29 01:25:06 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2014-08-18 02:31:42 +00:00
|
|
|
|
#region debugging
|
2012-12-29 01:25:06 +00:00
|
|
|
|
|
2013-11-06 02:15:29 +00:00
|
|
|
|
public MemoryDomainList MemoryDomains { get; private set; }
|
2012-12-29 01:25:06 +00:00
|
|
|
|
|
2014-08-18 02:31:42 +00:00
|
|
|
|
public Dictionary<string, int> GetCpuFlagsAndRegisters()
|
|
|
|
|
{
|
|
|
|
|
var left = L.GetCpuFlagsAndRegisters()
|
|
|
|
|
.Select(reg => new KeyValuePair<string, int>("Left " + reg.Key, reg.Value));
|
|
|
|
|
|
|
|
|
|
var right = R.GetCpuFlagsAndRegisters()
|
|
|
|
|
.Select(reg => new KeyValuePair<string, int>("Right " + reg.Key, reg.Value));
|
|
|
|
|
|
|
|
|
|
return left.Union(right).ToList().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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-29 18:06:39 +00:00
|
|
|
|
void SetMemoryDomains()
|
2012-12-29 01:25:06 +00:00
|
|
|
|
{
|
2012-12-29 18:06:39 +00:00
|
|
|
|
var mm = new List<MemoryDomain>();
|
|
|
|
|
|
|
|
|
|
foreach (var md in L.MemoryDomains)
|
2013-11-04 02:11:40 +00:00
|
|
|
|
mm.Add(new MemoryDomain("L " + md.Name, md.Size, md.EndianType, md.PeekByte, md.PokeByte));
|
2012-12-29 18:06:39 +00:00
|
|
|
|
foreach (var md in R.MemoryDomains)
|
2013-11-04 02:11:40 +00:00
|
|
|
|
mm.Add(new MemoryDomain("R " + md.Name, md.Size, md.EndianType, md.PeekByte, md.PokeByte));
|
2012-12-29 18:06:39 +00:00
|
|
|
|
|
2013-11-06 02:15:29 +00:00
|
|
|
|
MemoryDomains = new MemoryDomainList(mm);
|
2012-12-29 01:25:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-08-18 02:31:42 +00:00
|
|
|
|
#endregion
|
2012-12-29 01:25:06 +00:00
|
|
|
|
|
2012-12-29 18:06:39 +00:00
|
|
|
|
#region VideoProvider
|
|
|
|
|
|
2012-12-29 01:25:06 +00:00
|
|
|
|
int[] VideoBuffer = new int[160 * 2 * 144];
|
|
|
|
|
public int[] GetVideoBuffer() { return VideoBuffer; }
|
|
|
|
|
public int VirtualWidth { get { return 320; } }
|
2014-04-30 23:48:37 +00:00
|
|
|
|
public int VirtualHeight { get { return 144; } }
|
2012-12-29 01:25:06 +00:00
|
|
|
|
public int BufferWidth { get { return 320; } }
|
|
|
|
|
public int BufferHeight { get { return 144; } }
|
|
|
|
|
public int BackgroundColor { get { return unchecked((int)0xff000000); } }
|
|
|
|
|
|
2012-12-29 18:06:39 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region SoundProvider
|
|
|
|
|
|
|
|
|
|
// i tried using the left and right buffers and then mixing them together... it was kind of a mess of code, and slow
|
2012-12-29 17:11:19 +00:00
|
|
|
|
|
2013-11-14 19:33:13 +00:00
|
|
|
|
BlipBuffer blip_left;
|
|
|
|
|
BlipBuffer blip_right;
|
2012-12-29 17:11:19 +00:00
|
|
|
|
|
|
|
|
|
short[] LeftBuffer = new short[(35112 + 2064) * 2];
|
|
|
|
|
short[] RightBuffer = new short[(35112 + 2064) * 2];
|
|
|
|
|
|
|
|
|
|
short[] SampleBuffer = new short[1536];
|
|
|
|
|
int SampleBufferContains = 0;
|
|
|
|
|
|
|
|
|
|
int LatchL;
|
|
|
|
|
int LatchR;
|
|
|
|
|
|
|
|
|
|
void PrepSound()
|
|
|
|
|
{
|
|
|
|
|
unsafe
|
|
|
|
|
{
|
|
|
|
|
fixed (short* sl = LeftBuffer, sr = RightBuffer)
|
|
|
|
|
{
|
|
|
|
|
for (uint i = 0; i < SampPerFrame * 2; i += 2)
|
|
|
|
|
{
|
2014-03-05 05:06:34 +00:00
|
|
|
|
int s = (sl[i] + sl[i + 1]) / 2;
|
2012-12-29 17:11:19 +00:00
|
|
|
|
if (s != LatchL)
|
|
|
|
|
{
|
|
|
|
|
blip_left.AddDelta(i, s - LatchL);
|
|
|
|
|
LatchL = s;
|
|
|
|
|
}
|
2014-03-05 05:06:34 +00:00
|
|
|
|
s = (sr[i] + sr[i + 1]) / 2;
|
2012-12-29 17:11:19 +00:00
|
|
|
|
if (s != LatchR)
|
|
|
|
|
{
|
|
|
|
|
blip_right.AddDelta(i, s - LatchR);
|
|
|
|
|
LatchR = s;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
blip_left.EndFrame(SampPerFrame * 2);
|
|
|
|
|
blip_right.EndFrame(SampPerFrame * 2);
|
|
|
|
|
int count = blip_left.SamplesAvailable();
|
|
|
|
|
if (count != blip_right.SamplesAvailable())
|
|
|
|
|
throw new Exception("Sound problem?");
|
|
|
|
|
|
|
|
|
|
blip_left.ReadSamplesLeft(SampleBuffer, count);
|
|
|
|
|
blip_right.ReadSamplesRight(SampleBuffer, count);
|
|
|
|
|
SampleBufferContains = count;
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-29 01:25:06 +00:00
|
|
|
|
public void GetSamples(out short[] samples, out int nsamp)
|
|
|
|
|
{
|
2012-12-29 17:11:19 +00:00
|
|
|
|
nsamp = SampleBufferContains;
|
|
|
|
|
samples = SampleBuffer;
|
2012-12-29 01:25:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void DiscardSamples()
|
|
|
|
|
{
|
2012-12-29 17:11:19 +00:00
|
|
|
|
SampleBufferContains = 0;
|
2012-12-29 01:25:06 +00:00
|
|
|
|
}
|
2012-12-29 18:06:39 +00:00
|
|
|
|
|
|
|
|
|
#endregion
|
2013-12-22 00:44:39 +00:00
|
|
|
|
|
2014-08-18 02:31:42 +00:00
|
|
|
|
#region settings
|
|
|
|
|
|
2013-12-23 02:51:41 +00:00
|
|
|
|
public object GetSettings()
|
|
|
|
|
{
|
|
|
|
|
return new GambatteLinkSettings
|
2014-07-14 16:56:23 +00:00
|
|
|
|
(
|
|
|
|
|
(Gameboy.GambatteSettings)L.GetSettings(),
|
|
|
|
|
(Gameboy.GambatteSettings)R.GetSettings()
|
|
|
|
|
);
|
2013-12-23 02:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
public object GetSyncSettings()
|
|
|
|
|
{
|
|
|
|
|
return new GambatteLinkSyncSettings
|
2014-07-14 16:56:23 +00:00
|
|
|
|
(
|
|
|
|
|
(Gameboy.GambatteSyncSettings)L.GetSyncSettings(),
|
|
|
|
|
(Gameboy.GambatteSyncSettings)R.GetSyncSettings()
|
|
|
|
|
);
|
2013-12-23 02:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
public bool PutSettings(object o)
|
|
|
|
|
{
|
|
|
|
|
var s = (GambatteLinkSettings)o;
|
|
|
|
|
return L.PutSettings(s.L) || R.PutSettings(s.R);
|
|
|
|
|
}
|
|
|
|
|
public bool PutSyncSettings(object o)
|
|
|
|
|
{
|
|
|
|
|
var s = (GambatteLinkSyncSettings)o;
|
|
|
|
|
return L.PutSyncSettings(s.L) || R.PutSyncSettings(s.R);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class GambatteLinkSettings
|
|
|
|
|
{
|
|
|
|
|
public Gameboy.GambatteSettings L;
|
|
|
|
|
public Gameboy.GambatteSettings R;
|
|
|
|
|
|
2014-07-14 16:56:23 +00:00
|
|
|
|
public GambatteLinkSettings()
|
2013-12-23 02:51:41 +00:00
|
|
|
|
{
|
2014-07-14 16:56:23 +00:00
|
|
|
|
L = new Gameboy.GambatteSettings();
|
|
|
|
|
R = new Gameboy.GambatteSettings();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public GambatteLinkSettings(Gameboy.GambatteSettings L, Gameboy.GambatteSettings R)
|
|
|
|
|
{
|
|
|
|
|
this.L = L;
|
|
|
|
|
this.R = R;
|
2013-12-23 02:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public GambatteLinkSettings Clone()
|
|
|
|
|
{
|
2014-07-14 16:56:23 +00:00
|
|
|
|
return new GambatteLinkSettings(L.Clone(), R.Clone());
|
2013-12-23 02:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-07-14 16:56:23 +00:00
|
|
|
|
|
2013-12-23 02:51:41 +00:00
|
|
|
|
public class GambatteLinkSyncSettings
|
|
|
|
|
{
|
|
|
|
|
public Gameboy.GambatteSyncSettings L;
|
|
|
|
|
public Gameboy.GambatteSyncSettings R;
|
|
|
|
|
|
2014-07-14 16:10:45 +00:00
|
|
|
|
public GambatteLinkSyncSettings()
|
2013-12-23 02:51:41 +00:00
|
|
|
|
{
|
2014-07-14 16:10:45 +00:00
|
|
|
|
L = new Gameboy.GambatteSyncSettings();
|
|
|
|
|
R = new Gameboy.GambatteSyncSettings();
|
2013-12-23 02:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-14 16:56:23 +00:00
|
|
|
|
public GambatteLinkSyncSettings(Gameboy.GambatteSyncSettings L, Gameboy.GambatteSyncSettings R)
|
|
|
|
|
{
|
|
|
|
|
this.L = L;
|
|
|
|
|
this.R = R;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-23 02:51:41 +00:00
|
|
|
|
public GambatteLinkSyncSettings Clone()
|
|
|
|
|
{
|
2014-07-14 16:56:23 +00:00
|
|
|
|
return new GambatteLinkSyncSettings(L.Clone(), R.Clone());
|
2013-12-23 02:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-18 02:31:42 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2012-12-29 01:25:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|