2012-09-08 21:36:04 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
2013-10-27 22:07:40 +00:00
|
|
|
|
using BizHawk.Common;
|
2013-11-04 01:06:36 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2013-10-27 22:07:40 +00:00
|
|
|
|
|
2012-09-09 02:06:07 +00:00
|
|
|
|
namespace BizHawk.Emulation.Consoles.GB
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
2012-09-08 22:01:47 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// a gameboy/gameboy color emulator wrapped around native C++ libgambatte
|
|
|
|
|
/// </summary>
|
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
|
|
|
|
public class Gameboy : IEmulator, IVideoProvider, ISyncSoundProvider
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// internal gambatte state
|
|
|
|
|
/// </summary>
|
2012-12-29 01:25:06 +00:00
|
|
|
|
internal IntPtr GambatteState = IntPtr.Zero;
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
2012-09-09 14:17:57 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// keep a copy of the input callback delegate so it doesn't get GCed
|
|
|
|
|
/// </summary>
|
|
|
|
|
LibGambatte.InputGetter InputCallback;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// whatever keys are currently depressed
|
|
|
|
|
/// </summary>
|
|
|
|
|
LibGambatte.Buttons CurrentButtons = 0;
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
2013-05-09 23:15:59 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// RTC time when emulation begins.
|
|
|
|
|
/// </summary>
|
2013-10-27 17:07:37 +00:00
|
|
|
|
long zerotime = 0;
|
2013-05-09 23:15:59 +00:00
|
|
|
|
|
|
|
|
|
LibGambatte.RTCCallback TimeCallback;
|
|
|
|
|
|
|
|
|
|
long GetCurrentTime()
|
|
|
|
|
{
|
|
|
|
|
long fn = Frame;
|
|
|
|
|
fn /= 60; // exactly 60 fps. in case you feel bad about it, remember that we're not exactly tracking cpu cycles either.
|
|
|
|
|
fn += zerotime;
|
|
|
|
|
return fn;
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-10 00:43:43 +00:00
|
|
|
|
public Gameboy(CoreComm comm, GameInfo game, byte[] romdata)
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm = comm;
|
|
|
|
|
|
2012-12-10 01:33:09 +00:00
|
|
|
|
comm.VsyncNum = 262144;
|
|
|
|
|
comm.VsyncDen = 4389;
|
|
|
|
|
comm.RomStatusAnnotation = null;
|
|
|
|
|
comm.RomStatusDetails = null;
|
|
|
|
|
comm.CpuTraceAvailable = true;
|
|
|
|
|
comm.NominalWidth = 160;
|
|
|
|
|
comm.NominalHeight = 144;
|
|
|
|
|
|
2012-09-24 20:20:21 +00:00
|
|
|
|
ThrowExceptionForBadRom(romdata);
|
2013-08-24 16:54:22 +00:00
|
|
|
|
BoardName = MapperName(romdata);
|
2012-09-24 20:20:21 +00:00
|
|
|
|
|
2012-09-09 21:15:54 +00:00
|
|
|
|
GambatteState = LibGambatte.gambatte_create();
|
|
|
|
|
|
|
|
|
|
if (GambatteState == IntPtr.Zero)
|
|
|
|
|
throw new Exception("gambatte_create() returned null???");
|
|
|
|
|
|
2012-09-15 16:14:03 +00:00
|
|
|
|
LibGambatte.LoadFlags flags = 0;
|
|
|
|
|
|
|
|
|
|
if (game["ForceDMG"])
|
|
|
|
|
flags |= LibGambatte.LoadFlags.FORCE_DMG;
|
|
|
|
|
if (game["GBACGB"])
|
|
|
|
|
flags |= LibGambatte.LoadFlags.GBA_CGB;
|
|
|
|
|
if (game["MulitcartCompat"])
|
|
|
|
|
flags |= LibGambatte.LoadFlags.MULTICART_COMPAT;
|
|
|
|
|
|
|
|
|
|
|
2013-05-09 23:15:59 +00:00
|
|
|
|
if (LibGambatte.gambatte_load(GambatteState, romdata, (uint)romdata.Length, GetCurrentTime(), flags) != 0)
|
2012-09-09 21:15:54 +00:00
|
|
|
|
throw new Exception("gambatte_load() returned non-zero (is this not a gb or gbc rom?)");
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
2012-09-16 17:09:52 +00:00
|
|
|
|
// set real default colors (before anyone mucks with them at all)
|
|
|
|
|
ChangeDMGColors(new int[] { 10798341, 8956165, 1922333, 337157, 10798341, 8956165, 1922333, 337157, 10798341, 8956165, 1922333, 337157 });
|
2012-11-18 18:46:57 +00:00
|
|
|
|
SetCGBColors(GBColors.ColorType.gambatte);
|
2012-11-18 17:02:55 +00:00
|
|
|
|
|
2012-09-09 00:41:11 +00:00
|
|
|
|
InitSound();
|
2012-09-09 14:17:57 +00:00
|
|
|
|
|
|
|
|
|
Frame = 0;
|
|
|
|
|
LagCount = 0;
|
2012-09-09 21:57:15 +00:00
|
|
|
|
IsLagFrame = false;
|
2012-09-09 14:17:57 +00:00
|
|
|
|
|
|
|
|
|
InputCallback = new LibGambatte.InputGetter(ControllerCallback);
|
|
|
|
|
|
|
|
|
|
LibGambatte.gambatte_setinputgetter(GambatteState, InputCallback);
|
2012-09-11 15:28:38 +00:00
|
|
|
|
|
|
|
|
|
InitMemoryDomains();
|
2012-09-26 03:24:00 +00:00
|
|
|
|
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm.RomStatusDetails = string.Format("{0}\r\nSHA1:{1}\r\nMD5:{2}\r\n",
|
2012-09-26 03:24:00 +00:00
|
|
|
|
game.Name,
|
|
|
|
|
Util.BytesToHexString(System.Security.Cryptography.SHA1.Create().ComputeHash(romdata)),
|
|
|
|
|
Util.BytesToHexString(System.Security.Cryptography.MD5.Create().ComputeHash(romdata))
|
|
|
|
|
);
|
2013-05-09 23:15:59 +00:00
|
|
|
|
|
|
|
|
|
TimeCallback = new LibGambatte.RTCCallback(GetCurrentTime);
|
|
|
|
|
LibGambatte.gambatte_setrtccallback(GambatteState, TimeCallback);
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-09 02:06:07 +00:00
|
|
|
|
public static readonly ControllerDefinition GbController = new ControllerDefinition
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
|
|
|
|
Name = "Gameboy Controller",
|
|
|
|
|
BoolButtons =
|
|
|
|
|
{
|
2013-07-29 02:11:00 +00:00
|
|
|
|
"Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "Power"
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public ControllerDefinition ControllerDefinition
|
|
|
|
|
{
|
|
|
|
|
get { return GbController; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IController Controller { get; set; }
|
|
|
|
|
|
2012-09-09 21:57:15 +00:00
|
|
|
|
LibGambatte.Buttons ControllerCallback()
|
|
|
|
|
{
|
2013-11-10 18:15:32 +00:00
|
|
|
|
CoreComm.InputCallback.Call();
|
2012-09-09 21:57:15 +00:00
|
|
|
|
IsLagFrame = false;
|
|
|
|
|
return CurrentButtons;
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-15 18:30:11 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// true if the emulator is currently emulating CGB
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public bool IsCGBMode()
|
|
|
|
|
{
|
|
|
|
|
return (LibGambatte.gambatte_iscgb(GambatteState));
|
|
|
|
|
}
|
2012-09-09 14:17:57 +00:00
|
|
|
|
|
2012-12-29 15:48:30 +00:00
|
|
|
|
internal void FrameAdvancePrep()
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
2012-09-09 14:17:57 +00:00
|
|
|
|
Controller.UpdateControls(Frame++);
|
|
|
|
|
|
|
|
|
|
// update our local copy of the controller data
|
|
|
|
|
CurrentButtons = 0;
|
|
|
|
|
|
|
|
|
|
if (Controller["Up"])
|
|
|
|
|
CurrentButtons |= LibGambatte.Buttons.UP;
|
|
|
|
|
if (Controller["Down"])
|
|
|
|
|
CurrentButtons |= LibGambatte.Buttons.DOWN;
|
|
|
|
|
if (Controller["Left"])
|
|
|
|
|
CurrentButtons |= LibGambatte.Buttons.LEFT;
|
|
|
|
|
if (Controller["Right"])
|
|
|
|
|
CurrentButtons |= LibGambatte.Buttons.RIGHT;
|
|
|
|
|
if (Controller["A"])
|
|
|
|
|
CurrentButtons |= LibGambatte.Buttons.A;
|
|
|
|
|
if (Controller["B"])
|
|
|
|
|
CurrentButtons |= LibGambatte.Buttons.B;
|
|
|
|
|
if (Controller["Select"])
|
|
|
|
|
CurrentButtons |= LibGambatte.Buttons.SELECT;
|
|
|
|
|
if (Controller["Start"])
|
|
|
|
|
CurrentButtons |= LibGambatte.Buttons.START;
|
|
|
|
|
|
2012-09-09 21:57:15 +00:00
|
|
|
|
// the controller callback will set this to false if it actually gets called during the frame
|
|
|
|
|
IsLagFrame = true;
|
|
|
|
|
|
2012-09-11 17:37:17 +00:00
|
|
|
|
// download any modified data to the core
|
|
|
|
|
foreach (var r in MemoryRefreshers)
|
|
|
|
|
r.RefreshWrite();
|
|
|
|
|
|
2012-09-16 16:25:54 +00:00
|
|
|
|
if (Controller["Power"])
|
2013-05-09 23:15:59 +00:00
|
|
|
|
LibGambatte.gambatte_reset(GambatteState, GetCurrentTime());
|
2012-09-15 02:36:19 +00:00
|
|
|
|
|
2012-10-14 15:10:33 +00:00
|
|
|
|
RefreshMemoryCallbacks();
|
2012-12-10 00:43:43 +00:00
|
|
|
|
if (CoreComm.Tracer.Enabled)
|
2012-11-02 19:44:31 +00:00
|
|
|
|
tracecb = MakeTrace;
|
|
|
|
|
else
|
|
|
|
|
tracecb = null;
|
|
|
|
|
LibGambatte.gambatte_settracecallback(GambatteState, tracecb);
|
2012-12-29 15:48:30 +00:00
|
|
|
|
}
|
2012-10-14 15:10:33 +00:00
|
|
|
|
|
2012-12-29 15:48:30 +00:00
|
|
|
|
internal void FrameAdvancePost()
|
|
|
|
|
{
|
2012-09-11 17:37:17 +00:00
|
|
|
|
// upload any modified data to the memory domains
|
|
|
|
|
foreach (var r in MemoryRefreshers)
|
|
|
|
|
r.RefreshRead();
|
|
|
|
|
|
2012-09-09 21:57:15 +00:00
|
|
|
|
if (IsLagFrame)
|
|
|
|
|
LagCount++;
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
2012-11-06 17:54:04 +00:00
|
|
|
|
if (endofframecallback != null)
|
|
|
|
|
endofframecallback(LibGambatte.gambatte_cpuread(GambatteState, 0xff40));
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-12-29 15:48:30 +00:00
|
|
|
|
public void FrameAdvance(bool render, bool rendersound)
|
|
|
|
|
{
|
|
|
|
|
FrameAdvancePrep();
|
|
|
|
|
|
|
|
|
|
uint nsamp = 35112; // according to gambatte docs, this is the nominal length of a frame in 2mhz clocks
|
|
|
|
|
|
|
|
|
|
LibGambatte.gambatte_runfor(GambatteState, VideoBuffer, 160, soundbuff, ref nsamp);
|
|
|
|
|
|
|
|
|
|
if (rendersound)
|
2013-10-25 01:00:31 +00:00
|
|
|
|
{
|
2012-12-29 15:48:30 +00:00
|
|
|
|
soundbuffcontains = (int)nsamp;
|
2013-10-25 01:00:31 +00:00
|
|
|
|
ProcessSound();
|
|
|
|
|
}
|
2012-12-29 15:48:30 +00:00
|
|
|
|
else
|
2013-10-25 01:00:31 +00:00
|
|
|
|
{
|
2012-12-29 15:48:30 +00:00
|
|
|
|
soundbuffcontains = 0;
|
2013-10-25 01:00:31 +00:00
|
|
|
|
}
|
2012-12-29 15:48:30 +00:00
|
|
|
|
|
|
|
|
|
FrameAdvancePost();
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-24 16:54:22 +00:00
|
|
|
|
static string MapperName(byte[] romdata)
|
|
|
|
|
{
|
|
|
|
|
switch (romdata[0x147])
|
|
|
|
|
{
|
|
|
|
|
case 0x00: return "Plain ROM"; // = PLAIN; break;
|
|
|
|
|
case 0x01: return "MBC1 ROM"; // = MBC1; break;
|
|
|
|
|
case 0x02: return "MBC1 ROM+RAM"; // = MBC1; break;
|
|
|
|
|
case 0x03: return "MBC1 ROM+RAM+BATTERY"; // = MBC1; break;
|
|
|
|
|
case 0x05: return "MBC2 ROM"; // = MBC2; break;
|
|
|
|
|
case 0x06: return "MBC2 ROM+BATTERY"; // = MBC2; break;
|
|
|
|
|
case 0x08: return "Plain ROM+RAM"; // = PLAIN; break;
|
|
|
|
|
case 0x09: return "Plain ROM+RAM+BATTERY"; // = PLAIN; break;
|
|
|
|
|
case 0x0F: return "MBC3 ROM+TIMER+BATTERY"; // = MBC3; break;
|
|
|
|
|
case 0x10: return "MBC3 ROM+TIMER+RAM+BATTERY"; // = MBC3; break;
|
|
|
|
|
case 0x11: return "MBC3 ROM"; // = MBC3; break;
|
|
|
|
|
case 0x12: return "MBC3 ROM+RAM"; // = MBC3; break;
|
|
|
|
|
case 0x13: return "MBC3 ROM+RAM+BATTERY"; // = MBC3; break;
|
|
|
|
|
case 0x19: return "MBC5 ROM"; // = MBC5; break;
|
|
|
|
|
case 0x1A: return "MBC5 ROM+RAM"; // = MBC5; break;
|
|
|
|
|
case 0x1B: return "MBC5 ROM+RAM+BATTERY"; // = MBC5; break;
|
|
|
|
|
case 0x1C: return "MBC5 ROM+RUMBLE"; // = MBC5; break;
|
|
|
|
|
case 0x1D: return "MBC5 ROM+RUMBLE+RAM"; // = MBC5; break;
|
|
|
|
|
case 0x1E: return "MBC5 ROM+RUMBLE+RAM+BATTERY"; // = MBC5; break;
|
|
|
|
|
case 0xFF: return "HuC1 ROM+RAM+BATTERY"; // = HUC1; break;
|
|
|
|
|
default: return "UNKNOWN";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-24 20:20:21 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// throw exception with intelligible message on some kinds of bad rom
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="romdata"></param>
|
|
|
|
|
static void ThrowExceptionForBadRom(byte[] romdata)
|
|
|
|
|
{
|
|
|
|
|
if (romdata.Length < 0x148)
|
|
|
|
|
throw new Exception("ROM is far too small to be a valid GB\\GBC rom!");
|
2012-09-09 14:17:57 +00:00
|
|
|
|
|
2012-09-24 20:20:21 +00:00
|
|
|
|
switch (romdata[0x147])
|
|
|
|
|
{
|
|
|
|
|
case 0x00: break;
|
|
|
|
|
case 0x01: break;
|
|
|
|
|
case 0x02: break;
|
|
|
|
|
case 0x03: break;
|
|
|
|
|
case 0x05: break;
|
|
|
|
|
case 0x06: break;
|
|
|
|
|
case 0x08: break;
|
|
|
|
|
case 0x09: break;
|
|
|
|
|
|
|
|
|
|
case 0x0b: throw new Exception("\"MM01\" Mapper not supported!");
|
|
|
|
|
case 0x0c: throw new Exception("\"MM01\" Mapper not supported!");
|
|
|
|
|
case 0x0d: throw new Exception("\"MM01\" Mapper not supported!");
|
|
|
|
|
|
|
|
|
|
case 0x0f: break;
|
|
|
|
|
case 0x10: break;
|
|
|
|
|
case 0x11: break;
|
|
|
|
|
case 0x12: break;
|
|
|
|
|
case 0x13: break;
|
|
|
|
|
|
|
|
|
|
case 0x15: throw new Exception("\"MBC4\" Mapper not supported!");
|
|
|
|
|
case 0x16: throw new Exception("\"MBC4\" Mapper not supported!");
|
|
|
|
|
case 0x17: throw new Exception("\"MBC4\" Mapper not supported!");
|
|
|
|
|
|
|
|
|
|
case 0x19: break;
|
|
|
|
|
case 0x1a: break;
|
|
|
|
|
case 0x1b: break;
|
|
|
|
|
case 0x1c: break; // rumble
|
|
|
|
|
case 0x1d: break; // rumble
|
|
|
|
|
case 0x1e: break; // rumble
|
|
|
|
|
|
|
|
|
|
case 0x20: throw new Exception("\"MBC6\" Mapper not supported!");
|
|
|
|
|
case 0x22: throw new Exception("\"MBC7\" Mapper not supported!");
|
|
|
|
|
|
|
|
|
|
case 0xfc: throw new Exception("\"Pocket Camera\" Mapper not supported!");
|
|
|
|
|
case 0xfd: throw new Exception("\"Bandai TAMA5\" Mapper not supported!");
|
|
|
|
|
case 0xfe: throw new Exception("\"HuC3\" Mapper not supported!");
|
|
|
|
|
case 0xff: break;
|
|
|
|
|
default: throw new Exception(string.Format("Unknown mapper: {0:x2}", romdata[0x147]));
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2012-09-09 21:57:15 +00:00
|
|
|
|
|
2012-09-09 14:17:57 +00:00
|
|
|
|
public int Frame { get; set; }
|
|
|
|
|
|
2012-09-08 21:36:04 +00:00
|
|
|
|
public int LagCount { get; set; }
|
|
|
|
|
|
2012-09-09 21:57:15 +00:00
|
|
|
|
public bool IsLagFrame { get; private set; }
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
|
|
|
|
public string SystemId
|
|
|
|
|
{
|
|
|
|
|
get { return "GB"; }
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-24 16:54:22 +00:00
|
|
|
|
public string BoardName { get; private set; }
|
|
|
|
|
|
2012-10-03 15:31:04 +00:00
|
|
|
|
public bool DeterministicEmulation { get { return true; } }
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
2012-10-14 15:10:33 +00:00
|
|
|
|
#region saveram
|
|
|
|
|
|
2012-09-14 22:28:38 +00:00
|
|
|
|
public byte[] ReadSaveRam()
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
2012-09-14 22:28:38 +00:00
|
|
|
|
int length = LibGambatte.gambatte_savesavedatalength(GambatteState);
|
|
|
|
|
|
|
|
|
|
if (length > 0)
|
2012-09-10 23:40:53 +00:00
|
|
|
|
{
|
2012-09-14 22:28:38 +00:00
|
|
|
|
byte[] ret = new byte[length];
|
|
|
|
|
LibGambatte.gambatte_savesavedata(GambatteState, ret);
|
|
|
|
|
return ret;
|
2012-09-10 23:40:53 +00:00
|
|
|
|
}
|
2012-09-14 22:28:38 +00:00
|
|
|
|
else
|
|
|
|
|
return new byte[0];
|
2012-09-10 23:40:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void StoreSaveRam(byte[] data)
|
|
|
|
|
{
|
|
|
|
|
if (data.Length != LibGambatte.gambatte_savesavedatalength(GambatteState))
|
|
|
|
|
throw new ArgumentException("Size of saveram data does not match expected!");
|
|
|
|
|
LibGambatte.gambatte_loadsavedata(GambatteState, data);
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-14 21:29:35 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// reset cart save ram, if any, to initial state
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void ClearSaveRam()
|
|
|
|
|
{
|
|
|
|
|
int length = LibGambatte.gambatte_savesavedatalength(GambatteState);
|
|
|
|
|
if (length == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
byte[] clear = new byte[length];
|
|
|
|
|
for (int i = 0; i < clear.Length; i++)
|
|
|
|
|
clear[i] = 0xff; // this exactly matches what gambatte core does
|
|
|
|
|
|
|
|
|
|
StoreSaveRam(clear);
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-10 23:40:53 +00:00
|
|
|
|
|
2012-09-08 21:36:04 +00:00
|
|
|
|
public bool SaveRamModified
|
|
|
|
|
{
|
2012-09-10 23:40:53 +00:00
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (LibGambatte.gambatte_savesavedatalength(GambatteState) == 0)
|
|
|
|
|
return false;
|
|
|
|
|
else
|
|
|
|
|
return true; // need to wire more stuff into the core to actually know this
|
|
|
|
|
}
|
|
|
|
|
set { }
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-10-14 15:10:33 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2013-11-03 16:29:51 +00:00
|
|
|
|
public void ResetCounters()
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
2012-09-09 21:57:15 +00:00
|
|
|
|
Frame = 0;
|
|
|
|
|
LagCount = 0;
|
2012-11-25 15:41:40 +00:00
|
|
|
|
IsLagFrame = false;
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-09 21:57:15 +00:00
|
|
|
|
#region savestates
|
|
|
|
|
|
2012-09-23 16:56:11 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// handles the core-portion of savestating
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>private binary data corresponding to a savestate</returns>
|
|
|
|
|
byte[] SaveCoreBinary()
|
|
|
|
|
{
|
|
|
|
|
uint nlen = 0;
|
|
|
|
|
IntPtr ndata = IntPtr.Zero;
|
|
|
|
|
|
|
|
|
|
if (!LibGambatte.gambatte_savestate(GambatteState, VideoBuffer, 160, ref ndata, ref nlen))
|
|
|
|
|
throw new Exception("Gambatte failed to save the savestate!");
|
|
|
|
|
|
|
|
|
|
if (nlen == 0)
|
|
|
|
|
throw new Exception("Gambatte returned a 0-length savestate?");
|
|
|
|
|
|
|
|
|
|
byte[] data = new byte[nlen];
|
|
|
|
|
System.Runtime.InteropServices.Marshal.Copy(ndata, data, 0, (int)nlen);
|
|
|
|
|
LibGambatte.gambatte_savestate_destroy(ndata);
|
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// handles the core portion of loadstating
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="data">private binary data previously returned from SaveCoreBinary()</param>
|
|
|
|
|
void LoadCoreBinary(byte[] data)
|
|
|
|
|
{
|
|
|
|
|
if (!LibGambatte.gambatte_loadstate(GambatteState, data, (uint)data.Length))
|
|
|
|
|
throw new Exception("Gambatte failed to load the savestate!");
|
2012-09-29 13:03:14 +00:00
|
|
|
|
// since a savestate has been loaded, all memory domain data is now dirty
|
|
|
|
|
foreach (var r in MemoryRefreshers)
|
|
|
|
|
r.RefreshRead();
|
2012-09-23 16:56:11 +00:00
|
|
|
|
}
|
2012-11-19 17:59:57 +00:00
|
|
|
|
|
2012-09-08 21:36:04 +00:00
|
|
|
|
public void SaveStateText(System.IO.TextWriter writer)
|
|
|
|
|
{
|
2012-09-09 18:47:00 +00:00
|
|
|
|
var temp = SaveStateBinary();
|
|
|
|
|
temp.SaveAsHex(writer);
|
2012-09-23 16:56:11 +00:00
|
|
|
|
// write extra copy of stuff we don't use
|
|
|
|
|
writer.WriteLine("Frame {0}", Frame);
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadStateText(System.IO.TextReader reader)
|
|
|
|
|
{
|
2012-09-09 18:47:00 +00:00
|
|
|
|
string hex = reader.ReadLine();
|
2013-03-17 17:15:33 +00:00
|
|
|
|
if (hex.StartsWith("emuVersion")) // movie save
|
|
|
|
|
{
|
|
|
|
|
do // theoretically, our portion should start right after StartsFromSavestate, maybe...
|
|
|
|
|
{
|
|
|
|
|
hex = reader.ReadLine();
|
|
|
|
|
} while (!hex.StartsWith("StartsFromSavestate"));
|
|
|
|
|
hex = reader.ReadLine();
|
|
|
|
|
}
|
2012-09-09 18:47:00 +00:00
|
|
|
|
byte[] state = new byte[hex.Length / 2];
|
|
|
|
|
state.ReadFromHex(hex);
|
|
|
|
|
LoadStateBinary(new BinaryReader(new MemoryStream(state)));
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SaveStateBinary(System.IO.BinaryWriter writer)
|
|
|
|
|
{
|
2012-09-23 16:56:11 +00:00
|
|
|
|
byte[] data = SaveCoreBinary();
|
2012-09-09 18:47:00 +00:00
|
|
|
|
|
2012-09-23 16:56:11 +00:00
|
|
|
|
writer.Write(data.Length);
|
2012-09-09 18:47:00 +00:00
|
|
|
|
writer.Write(data);
|
2012-09-11 01:46:57 +00:00
|
|
|
|
|
|
|
|
|
// other variables
|
|
|
|
|
writer.Write(IsLagFrame);
|
|
|
|
|
writer.Write(LagCount);
|
|
|
|
|
writer.Write(Frame);
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadStateBinary(System.IO.BinaryReader reader)
|
|
|
|
|
{
|
2012-09-09 18:47:00 +00:00
|
|
|
|
int length = reader.ReadInt32();
|
|
|
|
|
byte[] data = reader.ReadBytes(length);
|
2012-09-08 21:48:46 +00:00
|
|
|
|
|
2012-09-23 16:56:11 +00:00
|
|
|
|
LoadCoreBinary(data);
|
2012-09-11 01:46:57 +00:00
|
|
|
|
|
|
|
|
|
// other variables
|
|
|
|
|
IsLagFrame = reader.ReadBoolean();
|
|
|
|
|
LagCount = reader.ReadInt32();
|
|
|
|
|
Frame = reader.ReadInt32();
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] SaveStateBinary()
|
|
|
|
|
{
|
2012-09-09 18:47:00 +00:00
|
|
|
|
MemoryStream ms = new MemoryStream();
|
|
|
|
|
BinaryWriter bw = new BinaryWriter(ms);
|
|
|
|
|
SaveStateBinary(bw);
|
|
|
|
|
bw.Flush();
|
|
|
|
|
return ms.ToArray();
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-05-06 20:51:28 +00:00
|
|
|
|
public bool BinarySaveStatesPreferred { get { return true; } }
|
|
|
|
|
|
2012-09-09 21:57:15 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2012-10-14 15:10:33 +00:00
|
|
|
|
#region memorycallback
|
|
|
|
|
|
|
|
|
|
LibGambatte.MemoryCallback readcb;
|
|
|
|
|
LibGambatte.MemoryCallback writecb;
|
|
|
|
|
|
|
|
|
|
void RefreshMemoryCallbacks()
|
|
|
|
|
{
|
2012-12-10 00:43:43 +00:00
|
|
|
|
var mcs = CoreComm.MemoryCallbackSystem;
|
2012-10-14 15:10:33 +00:00
|
|
|
|
|
|
|
|
|
// we RefreshMemoryCallbacks() after the triggers in case the trigger turns itself off at that point
|
|
|
|
|
|
2013-11-10 21:20:55 +00:00
|
|
|
|
if (mcs.HasReads)
|
|
|
|
|
readcb = delegate(uint addr) { mcs.CallRead(addr); RefreshMemoryCallbacks(); };
|
2012-10-14 15:10:33 +00:00
|
|
|
|
else
|
|
|
|
|
readcb = null;
|
2013-11-10 21:20:55 +00:00
|
|
|
|
if (mcs.HasWrites)
|
|
|
|
|
writecb = delegate(uint addr) { mcs.CallWrite(addr); RefreshMemoryCallbacks(); };
|
2012-10-14 15:10:33 +00:00
|
|
|
|
else
|
|
|
|
|
writecb = null;
|
|
|
|
|
|
|
|
|
|
LibGambatte.gambatte_setreadcallback(GambatteState, readcb);
|
|
|
|
|
LibGambatte.gambatte_setwritecallback(GambatteState, writecb);
|
|
|
|
|
}
|
2012-11-19 17:59:57 +00:00
|
|
|
|
|
2012-10-14 15:10:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
2012-12-10 00:43:43 +00:00
|
|
|
|
public CoreComm CoreComm { get; set; }
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
2012-11-02 19:44:31 +00:00
|
|
|
|
LibGambatte.TraceCallback tracecb;
|
|
|
|
|
|
|
|
|
|
void MakeTrace(IntPtr _s)
|
|
|
|
|
{
|
|
|
|
|
int[] s = new int[13];
|
|
|
|
|
System.Runtime.InteropServices.Marshal.Copy(_s, s, 0, 13);
|
2012-11-02 23:19:16 +00:00
|
|
|
|
ushort unused;
|
2012-11-02 19:44:31 +00:00
|
|
|
|
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm.Tracer.Put(string.Format(
|
2012-11-02 23:19:16 +00:00
|
|
|
|
"{13} SP:{2:x2} A:{3:x2} B:{4:x2} C:{5:x2} D:{6:x2} E:{7:x2} F:{8:x2} H:{9:x2} L:{10:x2} {11} Cy:{0}",
|
2012-11-02 19:44:31 +00:00
|
|
|
|
s[0],
|
|
|
|
|
s[1] & 0xffff,
|
|
|
|
|
s[2] & 0xffff,
|
|
|
|
|
s[3] & 0xff,
|
|
|
|
|
s[4] & 0xff,
|
|
|
|
|
s[5] & 0xff,
|
|
|
|
|
s[6] & 0xff,
|
|
|
|
|
s[7] & 0xff,
|
|
|
|
|
s[8] & 0xff,
|
|
|
|
|
s[9] & 0xff,
|
|
|
|
|
s[10] & 0xff,
|
|
|
|
|
s[11] != 0 ? "skip" : "",
|
2012-11-02 23:19:16 +00:00
|
|
|
|
s[12] & 0xff,
|
2012-11-03 18:17:55 +00:00
|
|
|
|
CPUs.Z80GB.NewDisassembler.Disassemble((ushort)s[1], (addr) => LibGambatte.gambatte_cpuread(GambatteState, addr), out unused).PadRight(30)
|
2012-11-02 19:44:31 +00:00
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-11 17:37:17 +00:00
|
|
|
|
#region MemoryDomains
|
|
|
|
|
|
|
|
|
|
class MemoryRefresher
|
|
|
|
|
{
|
|
|
|
|
IntPtr data;
|
|
|
|
|
int length;
|
|
|
|
|
|
|
|
|
|
byte[] CachedMemory;
|
|
|
|
|
|
|
|
|
|
public MemoryRefresher(IntPtr data, int length)
|
|
|
|
|
{
|
|
|
|
|
this.data = data;
|
|
|
|
|
this.length = length;
|
|
|
|
|
CachedMemory = new byte[length];
|
|
|
|
|
|
|
|
|
|
writeneeded = false;
|
2012-09-14 21:29:35 +00:00
|
|
|
|
// needs to be true in case a read is attempted before the first frame advance
|
|
|
|
|
readneeded = true;
|
2012-09-11 17:37:17 +00:00
|
|
|
|
}
|
2012-11-19 17:59:57 +00:00
|
|
|
|
|
2012-09-11 17:37:17 +00:00
|
|
|
|
bool readneeded;
|
|
|
|
|
bool writeneeded;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// reads data from native core to managed buffer
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void RefreshRead()
|
|
|
|
|
{
|
|
|
|
|
readneeded = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// writes data from managed buffer back to core
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void RefreshWrite()
|
|
|
|
|
{
|
|
|
|
|
if (writeneeded)
|
|
|
|
|
{
|
|
|
|
|
System.Runtime.InteropServices.Marshal.Copy(CachedMemory, 0, data, length);
|
|
|
|
|
writeneeded = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte Peek(int addr)
|
|
|
|
|
{
|
|
|
|
|
if (readneeded)
|
|
|
|
|
{
|
|
|
|
|
System.Runtime.InteropServices.Marshal.Copy(data, CachedMemory, 0, length);
|
|
|
|
|
readneeded = false;
|
|
|
|
|
}
|
2012-12-27 22:50:34 +00:00
|
|
|
|
return CachedMemory[addr % CachedMemory.Length];
|
2012-09-11 17:37:17 +00:00
|
|
|
|
}
|
|
|
|
|
public void Poke(int addr, byte val)
|
|
|
|
|
{
|
|
|
|
|
// a poke without any peek is certainly legal. we need to update read, because writeneeded = true means that
|
|
|
|
|
// all of this data will be downloaded before the next frame. so everything but that which was poked needs to
|
|
|
|
|
// be up to date.
|
|
|
|
|
if (readneeded)
|
|
|
|
|
{
|
|
|
|
|
System.Runtime.InteropServices.Marshal.Copy(data, CachedMemory, 0, length);
|
|
|
|
|
readneeded = false;
|
|
|
|
|
}
|
2012-12-27 22:50:34 +00:00
|
|
|
|
CachedMemory[addr % CachedMemory.Length] = val;
|
2012-09-11 17:37:17 +00:00
|
|
|
|
writeneeded = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-11 15:28:38 +00:00
|
|
|
|
|
2012-09-13 21:19:26 +00:00
|
|
|
|
void CreateMemoryDomain(LibGambatte.MemoryAreas which, string name)
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
2012-09-11 15:28:38 +00:00
|
|
|
|
IntPtr data = IntPtr.Zero;
|
|
|
|
|
int length = 0;
|
|
|
|
|
|
|
|
|
|
if (!LibGambatte.gambatte_getmemoryarea(GambatteState, which, ref data, ref length))
|
|
|
|
|
throw new Exception("gambatte_getmemoryarea() failed!");
|
|
|
|
|
|
2012-09-11 19:05:44 +00:00
|
|
|
|
// if length == 0, it's an empty block; (usually rambank on some carts); that's ok
|
2012-09-13 21:03:34 +00:00
|
|
|
|
// TODO: when length == 0, should we simply not add the memory domain at all?
|
2012-09-11 19:05:44 +00:00
|
|
|
|
if (data == IntPtr.Zero && length > 0)
|
2012-09-11 15:28:38 +00:00
|
|
|
|
throw new Exception("bad return from gambatte_getmemoryarea()");
|
|
|
|
|
|
2012-09-13 21:03:34 +00:00
|
|
|
|
var refresher = new MemoryRefresher(data, length);
|
|
|
|
|
|
|
|
|
|
MemoryRefreshers.Add(refresher);
|
2012-09-11 17:37:17 +00:00
|
|
|
|
|
2013-11-06 02:15:29 +00:00
|
|
|
|
_MemoryDomains.Add(new MemoryDomain(name, length, MemoryDomain.Endian.Little, refresher.Peek, refresher.Poke));
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-11 15:28:38 +00:00
|
|
|
|
void InitMemoryDomains()
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
2012-09-13 21:03:34 +00:00
|
|
|
|
MemoryRefreshers = new List<MemoryRefresher>();
|
2012-09-11 15:28:38 +00:00
|
|
|
|
|
2012-09-13 21:19:26 +00:00
|
|
|
|
CreateMemoryDomain(LibGambatte.MemoryAreas.wram, "WRAM");
|
|
|
|
|
CreateMemoryDomain(LibGambatte.MemoryAreas.rom, "ROM");
|
|
|
|
|
CreateMemoryDomain(LibGambatte.MemoryAreas.vram, "VRAM");
|
|
|
|
|
CreateMemoryDomain(LibGambatte.MemoryAreas.cartram, "Cart RAM");
|
|
|
|
|
CreateMemoryDomain(LibGambatte.MemoryAreas.oam, "OAM");
|
|
|
|
|
CreateMemoryDomain(LibGambatte.MemoryAreas.hram, "HRAM");
|
2012-09-11 15:28:38 +00:00
|
|
|
|
|
2012-09-13 21:03:34 +00:00
|
|
|
|
// also add a special memory domain for the system bus, where calls get sent directly to the core each time
|
|
|
|
|
|
2013-11-06 02:15:29 +00:00
|
|
|
|
_MemoryDomains.Add(new MemoryDomain("System Bus", 65536, MemoryDomain.Endian.Little,
|
2012-09-13 21:03:34 +00:00
|
|
|
|
delegate(int addr)
|
|
|
|
|
{
|
|
|
|
|
return LibGambatte.gambatte_cpuread(GambatteState, (ushort)addr);
|
|
|
|
|
},
|
|
|
|
|
delegate(int addr, byte val)
|
|
|
|
|
{
|
|
|
|
|
LibGambatte.gambatte_cpuwrite(GambatteState, (ushort)addr, val);
|
|
|
|
|
}));
|
|
|
|
|
|
2013-11-06 02:15:29 +00:00
|
|
|
|
MemoryDomains = new MemoryDomainList(_MemoryDomains);
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-11-06 02:15:29 +00:00
|
|
|
|
private List<MemoryDomain> _MemoryDomains = new List<MemoryDomain>();
|
|
|
|
|
public MemoryDomainList MemoryDomains { get; private set; }
|
2012-09-11 15:28:38 +00:00
|
|
|
|
|
|
|
|
|
|
2012-11-19 17:59:57 +00:00
|
|
|
|
List<MemoryRefresher> MemoryRefreshers;
|
2012-09-11 17:37:17 +00:00
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2012-11-05 01:34:11 +00:00
|
|
|
|
#region ppudebug
|
2012-11-05 20:15:53 +00:00
|
|
|
|
public bool GetGPUMemoryAreas(out IntPtr vram, out IntPtr bgpal, out IntPtr sppal, out IntPtr oam)
|
|
|
|
|
{
|
|
|
|
|
IntPtr _vram = IntPtr.Zero;
|
|
|
|
|
IntPtr _bgpal = IntPtr.Zero;
|
|
|
|
|
IntPtr _sppal = IntPtr.Zero;
|
|
|
|
|
IntPtr _oam = IntPtr.Zero;
|
|
|
|
|
int unused = 0;
|
|
|
|
|
if (!LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.vram, ref _vram, ref unused)
|
|
|
|
|
|| !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.bgpal, ref _bgpal, ref unused)
|
|
|
|
|
|| !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.sppal, ref _sppal, ref unused)
|
|
|
|
|
|| !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.oam, ref _oam, ref unused))
|
|
|
|
|
{
|
|
|
|
|
vram = IntPtr.Zero;
|
|
|
|
|
bgpal = IntPtr.Zero;
|
|
|
|
|
sppal = IntPtr.Zero;
|
|
|
|
|
oam = IntPtr.Zero;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
vram = _vram;
|
|
|
|
|
bgpal = _bgpal;
|
|
|
|
|
sppal = _sppal;
|
|
|
|
|
oam = _oam;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-05 01:34:11 +00:00
|
|
|
|
/// <summary>
|
2012-11-05 04:09:04 +00:00
|
|
|
|
///
|
2012-11-05 01:34:11 +00:00
|
|
|
|
/// </summary>
|
2012-11-05 20:15:53 +00:00
|
|
|
|
/// <param name="lcdc">current value of register $ff40 (LCDC)</param>
|
|
|
|
|
public delegate void ScanlineCallback(int lcdc);
|
2012-11-05 01:34:11 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// set up callback
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="callback"></param>
|
2012-11-06 17:54:04 +00:00
|
|
|
|
/// <param name="line">scanline. -1 = end of frame, -2 = RIGHT NOW</param>
|
2012-11-05 01:34:11 +00:00
|
|
|
|
public void SetScanlineCallback(ScanlineCallback callback, int line)
|
|
|
|
|
{
|
2012-11-05 20:15:53 +00:00
|
|
|
|
if (GambatteState == IntPtr.Zero)
|
|
|
|
|
// not sure how this is being reached. tried the debugger...
|
|
|
|
|
return;
|
2012-11-06 17:54:04 +00:00
|
|
|
|
endofframecallback = null;
|
|
|
|
|
if (callback == null || line == -1 || line == -2)
|
|
|
|
|
{
|
2012-11-05 20:15:53 +00:00
|
|
|
|
scanlinecb = null;
|
2012-11-06 17:54:04 +00:00
|
|
|
|
LibGambatte.gambatte_setscanlinecallback(GambatteState, null, 0);
|
|
|
|
|
if (line == -1)
|
|
|
|
|
endofframecallback = callback;
|
|
|
|
|
else if (line == -2)
|
|
|
|
|
callback(LibGambatte.gambatte_cpuread(GambatteState, 0xff40));
|
|
|
|
|
}
|
|
|
|
|
else if (line >= 0 && line <= 153)
|
|
|
|
|
{
|
2012-11-05 20:15:53 +00:00
|
|
|
|
scanlinecb = delegate()
|
|
|
|
|
{
|
|
|
|
|
callback(LibGambatte.gambatte_cpuread(GambatteState, 0xff40));
|
|
|
|
|
};
|
2012-11-06 17:54:04 +00:00
|
|
|
|
LibGambatte.gambatte_setscanlinecallback(GambatteState, scanlinecb, line);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
throw new ArgumentOutOfRangeException("line must be in [0, 153]");
|
2012-11-05 01:34:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-05 20:15:53 +00:00
|
|
|
|
LibGambatte.ScanlineCallback scanlinecb;
|
2012-11-06 17:54:04 +00:00
|
|
|
|
ScanlineCallback endofframecallback;
|
2012-11-05 20:15:53 +00:00
|
|
|
|
|
2012-11-05 01:34:11 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2012-09-08 21:36:04 +00:00
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
LibGambatte.gambatte_destroy(GambatteState);
|
|
|
|
|
GambatteState = IntPtr.Zero;
|
2012-09-09 13:35:58 +00:00
|
|
|
|
DisposeSound();
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region IVideoProvider
|
|
|
|
|
|
2012-09-09 21:57:15 +00:00
|
|
|
|
public IVideoProvider VideoProvider
|
|
|
|
|
{
|
|
|
|
|
get { return this; }
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-08 22:01:47 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// stored image of most recent frame
|
|
|
|
|
/// </summary>
|
2012-09-08 21:36:04 +00:00
|
|
|
|
int[] VideoBuffer = new int[160 * 144];
|
|
|
|
|
|
|
|
|
|
public int[] GetVideoBuffer()
|
|
|
|
|
{
|
|
|
|
|
return VideoBuffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int VirtualWidth
|
|
|
|
|
{
|
2012-09-09 21:57:15 +00:00
|
|
|
|
// only sgb changes this, which we don't emulate here
|
2012-09-08 21:36:04 +00:00
|
|
|
|
get { return 160; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int BufferWidth
|
|
|
|
|
{
|
|
|
|
|
get { return 160; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int BufferHeight
|
|
|
|
|
{
|
|
|
|
|
get { return 144; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int BackgroundColor
|
|
|
|
|
{
|
|
|
|
|
get { return 0; }
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-14 00:02:37 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region palette
|
|
|
|
|
|
2012-09-12 22:18:51 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// update gambatte core's internal colors
|
|
|
|
|
/// </summary>
|
2012-09-15 18:30:11 +00:00
|
|
|
|
public void ChangeDMGColors(int[] colors)
|
2012-09-12 22:18:51 +00:00
|
|
|
|
{
|
2012-09-15 18:30:11 +00:00
|
|
|
|
for (int i = 0; i < 12; i++)
|
|
|
|
|
LibGambatte.gambatte_setdmgpalettecolor(GambatteState, (LibGambatte.PalType)(i / 4), (uint)i % 4, (uint)colors[i]);
|
2012-09-12 22:18:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-18 18:46:57 +00:00
|
|
|
|
public void SetCGBColors(GBColors.ColorType type)
|
2012-11-18 17:02:55 +00:00
|
|
|
|
{
|
2012-11-18 18:46:57 +00:00
|
|
|
|
int[] lut = GBColors.GetLut(type);
|
2012-11-19 17:59:57 +00:00
|
|
|
|
LibGambatte.gambatte_setcgbpalette(GambatteState, lut);
|
2012-11-18 17:02:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-08 21:36:04 +00:00
|
|
|
|
#endregion
|
2012-09-09 00:41:11 +00:00
|
|
|
|
|
|
|
|
|
#region ISoundProvider
|
|
|
|
|
|
2012-11-19 17:59:57 +00:00
|
|
|
|
public ISoundProvider SoundProvider { get { return null; } }
|
2013-10-25 01:00:31 +00:00
|
|
|
|
public ISyncSoundProvider SyncSoundProvider { get { return this; } }
|
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
|
|
|
|
public bool StartAsyncSound() { return false; }
|
|
|
|
|
public void EndAsyncSound() { }
|
2012-09-09 21:57:15 +00:00
|
|
|
|
|
2012-09-09 00:41:11 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// sample pairs before resampling
|
|
|
|
|
/// </summary>
|
2012-12-29 17:11:19 +00:00
|
|
|
|
short[] soundbuff = new short[(35112 + 2064) * 2];
|
2012-09-09 00:41:11 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// how many sample pairs are in soundbuff
|
|
|
|
|
/// </summary>
|
2012-12-29 17:11:19 +00:00
|
|
|
|
int soundbuffcontains = 0;
|
2012-09-09 00:41:11 +00:00
|
|
|
|
|
2013-10-25 01:00:31 +00:00
|
|
|
|
int soundoutbuffcontains = 0;
|
|
|
|
|
|
|
|
|
|
short[] soundoutbuff = new short[2048];
|
|
|
|
|
|
|
|
|
|
int latchaudio = 0;
|
|
|
|
|
|
|
|
|
|
//Sound.Utilities.SpeexResampler resampler;
|
|
|
|
|
//Sound.Utilities.DCFilter dcfilter;
|
|
|
|
|
|
|
|
|
|
Sound.Utilities.BlipBuffer blip;
|
|
|
|
|
|
|
|
|
|
void ProcessSound()
|
|
|
|
|
{
|
|
|
|
|
for (uint i = 0; i < soundbuffcontains; i++)
|
|
|
|
|
{
|
|
|
|
|
int curr = soundbuff[i * 2];
|
|
|
|
|
|
|
|
|
|
if (curr != latchaudio)
|
|
|
|
|
{
|
|
|
|
|
int diff = latchaudio - curr;
|
|
|
|
|
latchaudio = curr;
|
|
|
|
|
blip.AddDelta(i, diff);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
blip.EndFrame((uint)soundbuffcontains);
|
|
|
|
|
|
|
|
|
|
soundoutbuffcontains = blip.SamplesAvailable();
|
|
|
|
|
|
|
|
|
|
blip.ReadSamples(soundoutbuff, soundoutbuffcontains, true);
|
|
|
|
|
for (int i = 0; i < soundoutbuffcontains * 2; i += 2)
|
|
|
|
|
soundoutbuff[i + 1] = soundoutbuff[i];
|
|
|
|
|
|
|
|
|
|
soundbuffcontains = 0;
|
|
|
|
|
|
|
|
|
|
}
|
2012-09-09 00:41:11 +00:00
|
|
|
|
|
|
|
|
|
void InitSound()
|
|
|
|
|
{
|
2013-10-25 01:00:31 +00:00
|
|
|
|
//resampler = new Sound.Utilities.SpeexResampler(2, 2097152, 44100, 2097152, 44100, null, this);
|
2012-12-09 20:02:43 +00:00
|
|
|
|
//dcfilter = Sound.Utilities.DCFilter.AsISyncSoundProvider(resampler, 65536);
|
|
|
|
|
// lowpass filtering on an actual GB was probably pretty aggressive?
|
2013-10-25 01:00:31 +00:00
|
|
|
|
//dcfilter = Sound.Utilities.DCFilter.AsISyncSoundProvider(resampler, 2048);
|
|
|
|
|
blip = new Sound.Utilities.BlipBuffer(1024);
|
|
|
|
|
blip.SetRates(2097152, 44100);
|
2012-09-09 00:41:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-09 13:35:58 +00:00
|
|
|
|
void DisposeSound()
|
|
|
|
|
{
|
2013-10-25 01:00:31 +00:00
|
|
|
|
blip.Dispose();
|
|
|
|
|
blip = null;
|
|
|
|
|
//resampler.Dispose();
|
|
|
|
|
//resampler = null;
|
2012-09-09 13:35:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
|
|
|
|
public void DiscardSamples()
|
2012-09-29 22:38:47 +00:00
|
|
|
|
{
|
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
|
|
|
|
soundbuffcontains = 0;
|
2012-09-29 22:38:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
|
|
|
|
public void GetSamples(out short[] samples, out int nsamp)
|
2012-09-09 00:41:11 +00:00
|
|
|
|
{
|
2013-10-25 01:00:31 +00:00
|
|
|
|
samples = soundoutbuff;
|
|
|
|
|
nsamp = soundoutbuffcontains;
|
2012-09-09 00:41:11 +00:00
|
|
|
|
}
|
|
|
|
|
#endregion
|
2012-09-09 21:57:15 +00:00
|
|
|
|
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|