2012-09-08 21:36:04 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2014-07-03 19:20:34 +00:00
|
|
|
|
using System.ComponentModel;
|
2012-09-08 21:36:04 +00:00
|
|
|
|
using System.IO;
|
|
|
|
|
|
2014-07-03 19:20:34 +00:00
|
|
|
|
using BizHawk.Common.BufferExtensions;
|
2013-11-04 01:06:36 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2014-07-14 16:10:45 +00:00
|
|
|
|
using BizHawk.Common;
|
2014-07-03 19:20:34 +00:00
|
|
|
|
|
2014-05-11 20:48:19 +00:00
|
|
|
|
using Newtonsoft.Json;
|
2013-10-27 22:07:40 +00:00
|
|
|
|
|
2013-11-13 03:32:25 +00:00
|
|
|
|
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
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>
|
2014-04-25 01:19:57 +00:00
|
|
|
|
[CoreAttributes(
|
|
|
|
|
"Gambatte",
|
2014-06-01 01:57:22 +00:00
|
|
|
|
"",
|
2014-04-25 01:19:57 +00:00
|
|
|
|
isPorted: true,
|
2014-06-01 01:57:22 +00:00
|
|
|
|
isReleased: true,
|
|
|
|
|
portedVersion: "SVN 344",
|
|
|
|
|
portedUrl: "http://gambatte.sourceforge.net/"
|
2014-04-25 01:19:57 +00:00
|
|
|
|
)]
|
2015-08-06 00:12:09 +00:00
|
|
|
|
[ServiceNotApplicable(typeof(IDriveLight), typeof(IDriveLight))]
|
2015-01-16 18:37:42 +00:00
|
|
|
|
public partial class Gameboy : IEmulator, IVideoProvider, ISyncSoundProvider, ISaveRam, IStatable, IInputPollable,
|
2015-01-14 21:55:48 +00:00
|
|
|
|
IDebuggable, ISettable<Gameboy.GambatteSettings, Gameboy.GambatteSyncSettings>
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
2014-08-04 22:25:07 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// the nominal length of one frame
|
|
|
|
|
/// </summary>
|
|
|
|
|
private const uint TICKSINFRAME = 35112;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// number of ticks per second
|
|
|
|
|
/// </summary>
|
|
|
|
|
private const uint TICKSPERSECOND = 2097152;
|
|
|
|
|
|
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
|
|
|
|
|
2014-05-12 17:24:43 +00:00
|
|
|
|
#region RTC
|
|
|
|
|
|
2013-05-09 23:15:59 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// RTC time when emulation begins.
|
|
|
|
|
/// </summary>
|
2013-11-24 17:32:46 +00:00
|
|
|
|
uint zerotime = 0;
|
2013-05-09 23:15:59 +00:00
|
|
|
|
|
2014-05-12 17:24:43 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// if true, RTC will run off of real elapsed time
|
|
|
|
|
/// </summary>
|
|
|
|
|
bool real_rtc_time = false;
|
|
|
|
|
|
2013-05-09 23:15:59 +00:00
|
|
|
|
LibGambatte.RTCCallback TimeCallback;
|
|
|
|
|
|
2014-05-12 17:24:43 +00:00
|
|
|
|
static long GetUnixNow()
|
|
|
|
|
{
|
|
|
|
|
// because internally the RTC works off of relative time, we don't need to base
|
|
|
|
|
// this off of any particular canonical epoch.
|
|
|
|
|
return DateTime.UtcNow.Ticks / 10000000L - 60000000000L;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-24 17:32:46 +00:00
|
|
|
|
uint GetCurrentTime()
|
2013-05-09 23:15:59 +00:00
|
|
|
|
{
|
2014-05-12 17:24:43 +00:00
|
|
|
|
if (real_rtc_time)
|
|
|
|
|
{
|
|
|
|
|
return (uint)GetUnixNow();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ulong fn = (ulong)Frame;
|
|
|
|
|
// as we're exactly tracking cpu cycles, this can be pretty accurate
|
|
|
|
|
fn *= 4389;
|
|
|
|
|
fn /= 262144;
|
|
|
|
|
fn += zerotime;
|
|
|
|
|
return (uint)fn;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint GetInitialTime()
|
|
|
|
|
{
|
|
|
|
|
if (real_rtc_time)
|
|
|
|
|
return (uint)GetUnixNow();
|
|
|
|
|
else
|
|
|
|
|
// setting the initial boot time to 0 will cause our zerotime
|
|
|
|
|
// to function as an initial offset, which is what we want
|
|
|
|
|
return 0;
|
2013-05-09 23:15:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-05-12 17:24:43 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2014-08-23 19:06:37 +00:00
|
|
|
|
[CoreConstructor("GB", "GBC")]
|
2014-09-12 15:39:04 +00:00
|
|
|
|
public Gameboy(CoreComm comm, GameInfo game, byte[] file, object Settings, object SyncSettings, bool deterministic)
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
2014-12-14 18:24:10 +00:00
|
|
|
|
var ser = new BasicServiceProvider(this);
|
|
|
|
|
ser.Register<IDisassemblable>(new GBDisassembler());
|
|
|
|
|
ServiceProvider = ser;
|
2014-12-05 00:05:40 +00:00
|
|
|
|
Tracer = new TraceBuffer();
|
2014-12-23 01:58:12 +00:00
|
|
|
|
ser.Register<ITraceable>(Tracer);
|
2014-12-05 02:39:42 +00:00
|
|
|
|
InitMemoryCallbacks();
|
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.NominalWidth = 160;
|
|
|
|
|
comm.NominalHeight = 144;
|
|
|
|
|
|
2014-09-12 15:39:04 +00:00
|
|
|
|
ThrowExceptionForBadRom(file);
|
|
|
|
|
BoardName = MapperName(file);
|
2012-09-24 20:20:21 +00:00
|
|
|
|
|
2014-05-12 17:24:43 +00:00
|
|
|
|
DeterministicEmulation = deterministic;
|
|
|
|
|
|
2012-09-09 21:15:54 +00:00
|
|
|
|
GambatteState = LibGambatte.gambatte_create();
|
|
|
|
|
|
|
|
|
|
if (GambatteState == IntPtr.Zero)
|
2014-08-01 14:56:23 +00:00
|
|
|
|
throw new InvalidOperationException("gambatte_create() returned null???");
|
2012-09-09 21:15:54 +00:00
|
|
|
|
|
2013-12-09 20:50:21 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
2015-01-16 18:37:42 +00:00
|
|
|
|
this._syncSettings = (GambatteSyncSettings)SyncSettings ?? new GambatteSyncSettings();
|
2014-05-12 17:24:43 +00:00
|
|
|
|
// copy over non-loadflag syncsettings now; they won't take effect if changed later
|
2015-01-16 18:37:42 +00:00
|
|
|
|
zerotime = (uint)this._syncSettings.RTCInitialTime;
|
|
|
|
|
real_rtc_time = DeterministicEmulation ? false : this._syncSettings.RealTimeRTC;
|
2013-12-23 02:51:41 +00:00
|
|
|
|
|
2013-12-09 20:50:21 +00:00
|
|
|
|
LibGambatte.LoadFlags flags = 0;
|
2012-09-15 16:14:03 +00:00
|
|
|
|
|
2015-01-16 18:37:42 +00:00
|
|
|
|
if (this._syncSettings.ForceDMG)
|
2013-12-09 20:50:21 +00:00
|
|
|
|
flags |= LibGambatte.LoadFlags.FORCE_DMG;
|
2015-01-16 18:37:42 +00:00
|
|
|
|
if (this._syncSettings.GBACGB)
|
2013-12-09 20:50:21 +00:00
|
|
|
|
flags |= LibGambatte.LoadFlags.GBA_CGB;
|
2015-01-16 18:37:42 +00:00
|
|
|
|
if (this._syncSettings.MulticartCompat)
|
2013-12-09 20:50:21 +00:00
|
|
|
|
flags |= LibGambatte.LoadFlags.MULTICART_COMPAT;
|
2012-09-15 16:14:03 +00:00
|
|
|
|
|
2014-09-12 15:39:04 +00:00
|
|
|
|
if (LibGambatte.gambatte_load(GambatteState, file, (uint)file.Length, GetCurrentTime(), flags) != 0)
|
2014-08-01 14:56:23 +00:00
|
|
|
|
throw new InvalidOperationException("gambatte_load() returned non-zero (is this not a gb or gbc rom?)");
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
2013-12-09 20:50:21 +00:00
|
|
|
|
// set real default colors (before anyone mucks with them at all)
|
2014-10-19 01:22:47 +00:00
|
|
|
|
PutSettings((GambatteSettings)Settings ?? new GambatteSettings());
|
2012-11-18 17:02:55 +00:00
|
|
|
|
|
2013-12-09 20:50:21 +00:00
|
|
|
|
InitSound();
|
2012-09-09 14:17:57 +00:00
|
|
|
|
|
2013-12-09 20:50:21 +00:00
|
|
|
|
Frame = 0;
|
|
|
|
|
LagCount = 0;
|
|
|
|
|
IsLagFrame = false;
|
2012-09-09 14:17:57 +00:00
|
|
|
|
|
2013-12-09 20:50:21 +00:00
|
|
|
|
InputCallback = new LibGambatte.InputGetter(ControllerCallback);
|
2012-09-09 14:17:57 +00:00
|
|
|
|
|
2013-12-09 20:50:21 +00:00
|
|
|
|
LibGambatte.gambatte_setinputgetter(GambatteState, InputCallback);
|
2012-09-11 15:28:38 +00:00
|
|
|
|
|
2013-12-09 20:50:21 +00:00
|
|
|
|
InitMemoryDomains();
|
2012-09-26 03:24:00 +00:00
|
|
|
|
|
2013-12-09 20:50:21 +00:00
|
|
|
|
CoreComm.RomStatusDetails = string.Format("{0}\r\nSHA1:{1}\r\nMD5:{2}\r\n",
|
|
|
|
|
game.Name,
|
2014-09-12 15:39:04 +00:00
|
|
|
|
file.HashSHA1(),
|
|
|
|
|
file.HashMD5());
|
2013-05-09 23:15:59 +00:00
|
|
|
|
|
2014-06-16 15:59:39 +00:00
|
|
|
|
{
|
|
|
|
|
byte[] buff = new byte[32];
|
|
|
|
|
LibGambatte.gambatte_romtitle(GambatteState, buff);
|
|
|
|
|
string romname = System.Text.Encoding.ASCII.GetString(buff);
|
|
|
|
|
Console.WriteLine("Core reported rom name: {0}", romname);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-09 20:50:21 +00:00
|
|
|
|
TimeCallback = new LibGambatte.RTCCallback(GetCurrentTime);
|
|
|
|
|
LibGambatte.gambatte_setrtccallback(GambatteState, TimeCallback);
|
2014-05-10 04:22:12 +00:00
|
|
|
|
|
|
|
|
|
NewSaveCoreSetBuff();
|
2013-12-09 20:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
Dispose();
|
|
|
|
|
throw;
|
|
|
|
|
}
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-12-04 03:38:30 +00:00
|
|
|
|
public IEmulatorServiceProvider ServiceProvider { get; private set; }
|
|
|
|
|
|
2015-01-16 18:37:42 +00:00
|
|
|
|
#region ALL SAVESTATEABLE STATE GOES HERE
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// internal gambatte state
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal IntPtr GambatteState = IntPtr.Zero;
|
|
|
|
|
|
|
|
|
|
public int Frame { get; set; }
|
2015-07-09 17:05:30 +00:00
|
|
|
|
public int LagCount { get; set; }
|
2015-01-16 18:37:42 +00:00
|
|
|
|
public bool IsLagFrame { get; private set; }
|
|
|
|
|
|
|
|
|
|
// all cycle counts are relative to a 2*1024*1024 mhz refclock
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// total cycles actually executed
|
|
|
|
|
/// </summary>
|
|
|
|
|
private ulong _cycleCount = 0;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// number of extra cycles we overran in the last frame
|
|
|
|
|
/// </summary>
|
|
|
|
|
private uint frameOverflow = 0;
|
|
|
|
|
public ulong CycleCount { get { return _cycleCount; } }
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2014-08-04 22:25:07 +00:00
|
|
|
|
#region controller
|
|
|
|
|
|
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()
|
|
|
|
|
{
|
2014-12-04 00:43:12 +00:00
|
|
|
|
InputCallbacks.Call();
|
2012-09-09 21:57:15 +00:00
|
|
|
|
IsLagFrame = false;
|
|
|
|
|
return CurrentButtons;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-04 22:25:07 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
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
|
|
|
|
|
2014-12-05 02:39:42 +00:00
|
|
|
|
private InputCallbackSystem _inputCallbacks = new InputCallbackSystem();
|
|
|
|
|
// low priority TODO: due to certain aspects of the core implementation,
|
|
|
|
|
// we don't smartly use the ActiveChanged event here.
|
|
|
|
|
public IInputCallbackSystem InputCallbacks { get { return _inputCallbacks; } }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// for use in dual core
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="ics"></param>
|
|
|
|
|
public void ConnectInputCallbackSystem(InputCallbackSystem ics)
|
|
|
|
|
{
|
|
|
|
|
_inputCallbacks = ics;
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-29 15:48:30 +00:00
|
|
|
|
internal void FrameAdvancePrep()
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
2013-12-07 00:53:06 +00:00
|
|
|
|
Frame++;
|
2012-09-09 14:17:57 +00:00
|
|
|
|
|
|
|
|
|
// 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-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
|
|
|
|
|
2014-12-05 00:05:40 +00:00
|
|
|
|
if (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-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();
|
2015-01-16 18:37:42 +00:00
|
|
|
|
if (_syncSettings.EqualLengthFrames)
|
2014-04-29 23:31:25 +00:00
|
|
|
|
{
|
2014-08-04 22:25:07 +00:00
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
// target number of samples to emit: length of 1 frame minus whatever overflow
|
|
|
|
|
uint samplesEmitted = TICKSINFRAME - frameOverflow;
|
|
|
|
|
System.Diagnostics.Debug.Assert(samplesEmitted * 2 <= soundbuff.Length);
|
|
|
|
|
if (LibGambatte.gambatte_runfor(GambatteState, soundbuff, ref samplesEmitted) > 0)
|
|
|
|
|
LibGambatte.gambatte_blitto(GambatteState, VideoBuffer, 160);
|
|
|
|
|
|
|
|
|
|
// account for actual number of samples emitted
|
|
|
|
|
_cycleCount += (ulong)samplesEmitted;
|
|
|
|
|
frameOverflow += samplesEmitted;
|
|
|
|
|
|
2014-11-16 21:56:32 +00:00
|
|
|
|
if (rendersound && !Muted)
|
2014-08-04 22:25:07 +00:00
|
|
|
|
{
|
|
|
|
|
ProcessSound((int)samplesEmitted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (frameOverflow >= TICKSINFRAME)
|
|
|
|
|
{
|
|
|
|
|
frameOverflow -= TICKSINFRAME;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// target number of samples to emit: always 59.7fps
|
|
|
|
|
// runfor() always ends after creating a video frame, so sync-up is guaranteed
|
|
|
|
|
// when the display has been off, some frames can be markedly shorter than expected
|
|
|
|
|
uint samplesEmitted = TICKSINFRAME;
|
2014-05-03 03:05:34 +00:00
|
|
|
|
if (LibGambatte.gambatte_runfor(GambatteState, soundbuff, ref samplesEmitted) > 0)
|
|
|
|
|
LibGambatte.gambatte_blitto(GambatteState, VideoBuffer, 160);
|
2012-12-29 15:48:30 +00:00
|
|
|
|
|
2014-04-29 23:31:25 +00:00
|
|
|
|
_cycleCount += (ulong)samplesEmitted;
|
2014-08-04 22:25:07 +00:00
|
|
|
|
frameOverflow = 0;
|
2014-11-16 21:56:32 +00:00
|
|
|
|
if (rendersound && !Muted)
|
2014-04-29 23:31:25 +00:00
|
|
|
|
{
|
2014-08-04 22:25:07 +00:00
|
|
|
|
ProcessSound((int)samplesEmitted);
|
2014-04-29 23:31:25 +00:00
|
|
|
|
}
|
2013-10-25 01:00:31 +00:00
|
|
|
|
}
|
2012-12-29 15:48:30 +00:00
|
|
|
|
|
2014-11-16 21:56:32 +00:00
|
|
|
|
if (rendersound && !Muted)
|
2014-04-30 03:46:37 +00:00
|
|
|
|
ProcessSoundEnd();
|
|
|
|
|
|
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)
|
2014-08-01 14:56:23 +00:00
|
|
|
|
throw new ArgumentException("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;
|
|
|
|
|
|
2014-10-29 18:58:43 +00:00
|
|
|
|
case 0x0b: throw new UnsupportedGameException("\"MM01\" Mapper not supported!");
|
|
|
|
|
case 0x0c: throw new UnsupportedGameException("\"MM01\" Mapper not supported!");
|
|
|
|
|
case 0x0d: throw new UnsupportedGameException("\"MM01\" Mapper not supported!");
|
2012-09-24 20:20:21 +00:00
|
|
|
|
|
|
|
|
|
case 0x0f: break;
|
|
|
|
|
case 0x10: break;
|
|
|
|
|
case 0x11: break;
|
|
|
|
|
case 0x12: break;
|
|
|
|
|
case 0x13: break;
|
|
|
|
|
|
2014-10-29 18:58:43 +00:00
|
|
|
|
case 0x15: throw new UnsupportedGameException("\"MBC4\" Mapper not supported!");
|
|
|
|
|
case 0x16: throw new UnsupportedGameException("\"MBC4\" Mapper not supported!");
|
|
|
|
|
case 0x17: throw new UnsupportedGameException("\"MBC4\" Mapper not supported!");
|
2012-09-24 20:20:21 +00:00
|
|
|
|
|
|
|
|
|
case 0x19: break;
|
|
|
|
|
case 0x1a: break;
|
|
|
|
|
case 0x1b: break;
|
|
|
|
|
case 0x1c: break; // rumble
|
|
|
|
|
case 0x1d: break; // rumble
|
|
|
|
|
case 0x1e: break; // rumble
|
|
|
|
|
|
2014-10-29 18:58:43 +00:00
|
|
|
|
case 0x20: throw new UnsupportedGameException("\"MBC6\" Mapper not supported!");
|
|
|
|
|
case 0x22: throw new UnsupportedGameException("\"MBC7\" Mapper not supported!");
|
2012-09-24 20:20:21 +00:00
|
|
|
|
|
2014-10-29 18:58:43 +00:00
|
|
|
|
case 0xfc: throw new UnsupportedGameException("\"Pocket Camera\" Mapper not supported!");
|
|
|
|
|
case 0xfd: throw new UnsupportedGameException("\"Bandai TAMA5\" Mapper not supported!");
|
|
|
|
|
case 0xfe: throw new UnsupportedGameException("\"HuC3\" Mapper not supported!");
|
2012-09-24 20:20:21 +00:00
|
|
|
|
case 0xff: break;
|
2014-10-29 18:58:43 +00:00
|
|
|
|
default: throw new UnsupportedGameException(string.Format("Unknown mapper: {0:x2}", romdata[0x147]));
|
2012-09-24 20:20:21 +00:00
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2012-09-09 21:57:15 +00:00
|
|
|
|
|
2014-08-04 22:25:07 +00:00
|
|
|
|
public string SystemId { get { return "GB"; } }
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
2013-08-24 16:54:22 +00:00
|
|
|
|
public string BoardName { get; private set; }
|
|
|
|
|
|
2014-05-12 17:24:43 +00:00
|
|
|
|
public bool DeterministicEmulation { get; private set; }
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
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;
|
2014-08-04 22:25:07 +00:00
|
|
|
|
// reset frame counters is meant to "re-zero" emulation time wherever it was
|
|
|
|
|
// so these should be reset as well
|
|
|
|
|
_cycleCount = 0;
|
|
|
|
|
frameOverflow = 0;
|
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-05 01:34:11 +00:00
|
|
|
|
#region ppudebug
|
2014-08-04 22:25:07 +00:00
|
|
|
|
|
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
|
2015-02-10 04:19:34 +00:00
|
|
|
|
{
|
|
|
|
|
throw new ArgumentOutOfRangeException("line", "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()
|
|
|
|
|
{
|
2013-12-09 20:50:21 +00:00
|
|
|
|
if (GambatteState != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
LibGambatte.gambatte_destroy(GambatteState);
|
|
|
|
|
GambatteState = IntPtr.Zero;
|
|
|
|
|
}
|
2012-09-09 13:35:58 +00:00
|
|
|
|
DisposeSound();
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-14 00:02:37 +00:00
|
|
|
|
#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>
|
2014-08-04 22:25:07 +00:00
|
|
|
|
short[] soundbuff = new short[(35112 + 2064) * 2];
|
2012-09-09 00:41:11 +00:00
|
|
|
|
|
2013-10-25 01:00:31 +00:00
|
|
|
|
int soundoutbuffcontains = 0;
|
|
|
|
|
|
|
|
|
|
short[] soundoutbuff = new short[2048];
|
|
|
|
|
|
2014-03-05 05:03:13 +00:00
|
|
|
|
int latchL = 0;
|
|
|
|
|
int latchR = 0;
|
2013-10-25 01:00:31 +00:00
|
|
|
|
|
2014-03-05 05:03:13 +00:00
|
|
|
|
BlipBuffer blipL, blipR;
|
2014-04-30 03:46:37 +00:00
|
|
|
|
uint blipAccumulate;
|
2013-10-25 01:00:31 +00:00
|
|
|
|
|
2014-08-04 22:25:07 +00:00
|
|
|
|
private void ProcessSound(int nsamp)
|
2013-10-25 01:00:31 +00:00
|
|
|
|
{
|
2014-08-04 22:25:07 +00:00
|
|
|
|
for (uint i = 0; i < nsamp; i++)
|
2013-10-25 01:00:31 +00:00
|
|
|
|
{
|
|
|
|
|
int curr = soundbuff[i * 2];
|
|
|
|
|
|
2014-03-05 05:03:13 +00:00
|
|
|
|
if (curr != latchL)
|
2013-10-25 01:00:31 +00:00
|
|
|
|
{
|
2014-03-05 05:03:13 +00:00
|
|
|
|
int diff = latchL - curr;
|
|
|
|
|
latchL = curr;
|
2014-04-30 03:46:37 +00:00
|
|
|
|
blipL.AddDelta(blipAccumulate, diff);
|
2014-03-05 05:03:13 +00:00
|
|
|
|
}
|
|
|
|
|
curr = soundbuff[i * 2 + 1];
|
|
|
|
|
|
|
|
|
|
if (curr != latchR)
|
|
|
|
|
{
|
|
|
|
|
int diff = latchR - curr;
|
|
|
|
|
latchR = curr;
|
2014-04-30 03:46:37 +00:00
|
|
|
|
blipR.AddDelta(blipAccumulate, diff);
|
2013-10-25 01:00:31 +00:00
|
|
|
|
}
|
2014-04-30 03:46:37 +00:00
|
|
|
|
|
|
|
|
|
blipAccumulate++;
|
2013-10-25 01:00:31 +00:00
|
|
|
|
}
|
2014-04-30 03:46:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ProcessSoundEnd()
|
|
|
|
|
{
|
2014-08-04 22:25:07 +00:00
|
|
|
|
blipL.EndFrame(blipAccumulate);
|
|
|
|
|
blipR.EndFrame(blipAccumulate);
|
2014-04-30 03:46:37 +00:00
|
|
|
|
blipAccumulate = 0;
|
2013-10-25 01:00:31 +00:00
|
|
|
|
|
2014-03-05 05:03:13 +00:00
|
|
|
|
soundoutbuffcontains = blipL.SamplesAvailable();
|
|
|
|
|
if (soundoutbuffcontains != blipR.SamplesAvailable())
|
2014-08-04 22:25:07 +00:00
|
|
|
|
throw new InvalidOperationException("Audio processing error");
|
2013-10-25 01:00:31 +00:00
|
|
|
|
|
2014-03-05 05:03:13 +00:00
|
|
|
|
blipL.ReadSamplesLeft(soundoutbuff, soundoutbuffcontains);
|
|
|
|
|
blipR.ReadSamplesRight(soundoutbuff, soundoutbuffcontains);
|
2013-10-25 01:00:31 +00:00
|
|
|
|
}
|
2012-09-09 00:41:11 +00:00
|
|
|
|
|
|
|
|
|
void InitSound()
|
|
|
|
|
{
|
2014-03-05 05:03:13 +00:00
|
|
|
|
blipL = new BlipBuffer(1024);
|
2014-08-04 22:25:07 +00:00
|
|
|
|
blipL.SetRates(TICKSPERSECOND, 44100);
|
2014-03-05 05:03:13 +00:00
|
|
|
|
blipR = new BlipBuffer(1024);
|
2014-08-04 22:25:07 +00:00
|
|
|
|
blipR.SetRates(TICKSPERSECOND, 44100);
|
2012-09-09 00:41:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-09 13:35:58 +00:00
|
|
|
|
void DisposeSound()
|
|
|
|
|
{
|
2014-03-05 05:03:13 +00:00
|
|
|
|
if (blipL != null)
|
|
|
|
|
{
|
|
|
|
|
blipL.Dispose();
|
|
|
|
|
blipL = null;
|
|
|
|
|
}
|
|
|
|
|
if (blipR != null)
|
2013-12-09 20:50:21 +00:00
|
|
|
|
{
|
2014-03-05 05:03:13 +00:00
|
|
|
|
blipR.Dispose();
|
|
|
|
|
blipR = null;
|
2013-12-09 20:50:21 +00:00
|
|
|
|
}
|
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
|
|
|
|
{
|
2014-08-04 22:25:07 +00:00
|
|
|
|
soundoutbuffcontains = 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
|
|
|
|
}
|
2014-04-29 23:31:25 +00:00
|
|
|
|
|
2015-01-16 18:37:42 +00:00
|
|
|
|
public bool Muted { get { return _settings.Muted; } }
|
2014-04-29 23:31:25 +00:00
|
|
|
|
|
|
|
|
|
#endregion
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|