2012-09-08 21:36:04 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
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>
|
2012-09-09 02:06:07 +00:00
|
|
|
|
public class Gameboy : IEmulator, IVideoProvider, ISoundProvider
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// internal gambatte state
|
|
|
|
|
/// </summary>
|
|
|
|
|
IntPtr GambatteState = IntPtr.Zero;
|
|
|
|
|
|
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
|
|
|
|
|
2012-09-15 16:14:03 +00:00
|
|
|
|
public Gameboy(GameInfo game, byte[] romdata)
|
2012-09-08 21:36:04 +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;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (LibGambatte.gambatte_load(GambatteState, romdata, (uint)romdata.Length, 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-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-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 =
|
|
|
|
|
{
|
2012-09-16 16:25:54 +00:00
|
|
|
|
"Up", "Down", "Left", "Right", "A", "B", "Select", "Start", "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
|
|
|
|
// can when this is called (or not called) be used to give information about lagged frames?
|
|
|
|
|
LibGambatte.Buttons ControllerCallback()
|
|
|
|
|
{
|
|
|
|
|
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-09-20 19:52:47 +00:00
|
|
|
|
public void FrameAdvance(bool render, bool rendersound)
|
2012-09-08 21:36:04 +00:00
|
|
|
|
{
|
2012-09-09 21:57:15 +00:00
|
|
|
|
uint nsamp = 35112; // according to gambatte docs, this is the nominal length of a frame in 2mhz clocks
|
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"])
|
2012-09-15 02:36:19 +00:00
|
|
|
|
LibGambatte.gambatte_reset(GambatteState);
|
|
|
|
|
|
2012-09-09 13:35:58 +00:00
|
|
|
|
LibGambatte.gambatte_runfor(GambatteState, VideoBuffer, 160, soundbuff, ref nsamp);
|
2012-09-09 00:41:11 +00:00
|
|
|
|
|
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-20 19:52:47 +00:00
|
|
|
|
if (rendersound)
|
|
|
|
|
soundbuffcontains = (int)nsamp;
|
|
|
|
|
else
|
|
|
|
|
soundbuffcontains = 0;
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
2012-09-09 21:57:15 +00:00
|
|
|
|
if (IsLagFrame)
|
|
|
|
|
LagCount++;
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-09 14:17:57 +00:00
|
|
|
|
|
|
|
|
|
|
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"; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool DeterministicEmulation { get; set; }
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ResetFrameCounter()
|
|
|
|
|
{
|
2012-09-09 21:57:15 +00:00
|
|
|
|
// is this right?
|
|
|
|
|
Frame = 0;
|
|
|
|
|
LagCount = 0;
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-09 21:57:15 +00:00
|
|
|
|
#region savestates
|
|
|
|
|
|
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-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();
|
|
|
|
|
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-09 18:47:00 +00:00
|
|
|
|
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);
|
2012-09-08 21:48:46 +00:00
|
|
|
|
|
2012-09-09 18:47:00 +00:00
|
|
|
|
writer.Write((int)nlen);
|
|
|
|
|
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-09 18:47:00 +00:00
|
|
|
|
if (!LibGambatte.gambatte_loadstate(GambatteState, data, (uint)length))
|
|
|
|
|
throw new Exception("Gambatte failed to load the savestate!");
|
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
|
|
|
|
}
|
|
|
|
|
|
2012-09-09 21:57:15 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
2012-09-08 21:48:46 +00:00
|
|
|
|
public CoreInputComm CoreInputComm { get; set; }
|
2012-09-08 21:36:04 +00:00
|
|
|
|
|
|
|
|
|
CoreOutputComm GbOutputComm = new CoreOutputComm
|
|
|
|
|
{
|
2012-09-09 00:41:11 +00:00
|
|
|
|
VsyncNum = 262144,
|
|
|
|
|
VsyncDen = 4389,
|
2012-09-14 15:50:35 +00:00
|
|
|
|
RomStatusAnnotation = null, //"Bizwhackin it up",
|
|
|
|
|
RomStatusDetails = null, //"LEVAR BURTON",
|
2012-09-08 21:36:04 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public CoreOutputComm CoreOutputComm
|
|
|
|
|
{
|
|
|
|
|
get { return GbOutputComm; }
|
|
|
|
|
}
|
|
|
|
|
|
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-09-14 21:29:35 +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;
|
|
|
|
|
}
|
|
|
|
|
return CachedMemory[addr];
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
CachedMemory[addr] = val;
|
|
|
|
|
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
|
|
|
|
|
2012-09-13 21:19:26 +00:00
|
|
|
|
MemoryDomains.Add(new MemoryDomain(name, length, 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
|
|
|
|
MemoryDomains = new List<MemoryDomain>();
|
|
|
|
|
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
|
|
|
|
|
|
2012-09-13 21:19:26 +00:00
|
|
|
|
MemoryDomains.Add(new MemoryDomain("System Bus", 65536, 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);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// this is the wram area and matches the bizhawk convention for what MainMemory means
|
2012-09-11 17:37:17 +00:00
|
|
|
|
MainMemory = MemoryDomains[0];
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-11 15:28:38 +00:00
|
|
|
|
public IList<MemoryDomain> MemoryDomains { get; private set; }
|
|
|
|
|
|
|
|
|
|
public MemoryDomain MainMemory { get; private set; }
|
|
|
|
|
|
2012-09-13 21:03:34 +00:00
|
|
|
|
List <MemoryRefresher> MemoryRefreshers;
|
2012-09-11 17:37:17 +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-09-08 21:36:04 +00:00
|
|
|
|
#endregion
|
2012-09-09 00:41:11 +00:00
|
|
|
|
|
|
|
|
|
#region ISoundProvider
|
|
|
|
|
|
2012-09-09 21:57:15 +00:00
|
|
|
|
public ISoundProvider SoundProvider
|
|
|
|
|
{
|
|
|
|
|
get { return this; }
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-09 00:41:11 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// sample pairs before resampling
|
|
|
|
|
/// </summary>
|
|
|
|
|
short[] soundbuff = new short[(35112 + 2064) * 2];
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// how many sample pairs are in soundbuff
|
|
|
|
|
/// </summary>
|
|
|
|
|
int soundbuffcontains = 0;
|
|
|
|
|
|
|
|
|
|
Sound.Utilities.SpeexResampler resampler;
|
2012-09-11 21:30:50 +00:00
|
|
|
|
ISoundProvider metaspu;
|
2012-09-09 00:41:11 +00:00
|
|
|
|
|
|
|
|
|
void InitSound()
|
|
|
|
|
{
|
2012-09-11 21:30:50 +00:00
|
|
|
|
var metaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_V);
|
2012-09-09 12:23:40 +00:00
|
|
|
|
resampler = new Sound.Utilities.SpeexResampler(2, 2097152, 44100, 2097152, 44100, metaspu.buffer.enqueue_samples);
|
2012-09-11 21:30:50 +00:00
|
|
|
|
this.metaspu = new Sound.Utilities.DCFilter(metaspu);// metaspu;
|
2012-09-09 00:41:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-09 13:35:58 +00:00
|
|
|
|
void DisposeSound()
|
|
|
|
|
{
|
|
|
|
|
resampler.Dispose();
|
|
|
|
|
resampler = null;
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-09 00:41:11 +00:00
|
|
|
|
public void GetSamples(short[] samples)
|
|
|
|
|
{
|
2012-09-20 19:52:47 +00:00
|
|
|
|
if (soundbuffcontains > 0)
|
|
|
|
|
{
|
|
|
|
|
resampler.EnqueueSamples(soundbuff, soundbuffcontains);
|
|
|
|
|
soundbuffcontains = 0;
|
|
|
|
|
resampler.Flush();
|
|
|
|
|
metaspu.GetSamples(samples);
|
|
|
|
|
}
|
2012-09-09 00:41:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void DiscardSamples()
|
|
|
|
|
{
|
|
|
|
|
metaspu.DiscardSamples();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int MaxVolume { get; set; }
|
|
|
|
|
#endregion
|
2012-09-09 21:57:15 +00:00
|
|
|
|
|
2012-09-08 21:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|