2012-11-19 22:43:34 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
2013-10-27 22:07:40 +00:00
|
|
|
|
using BizHawk.Common;
|
2013-11-04 01:39:19 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2013-10-27 22:07:40 +00:00
|
|
|
|
|
2013-11-13 03:32:25 +00:00
|
|
|
|
namespace BizHawk.Emulation.Cores.Nintendo.GBA
|
2012-11-19 22:43:34 +00:00
|
|
|
|
{
|
|
|
|
|
public class GBA : IEmulator, IVideoProvider, ISyncSoundProvider
|
|
|
|
|
{
|
2013-11-11 03:20:33 +00:00
|
|
|
|
public List<KeyValuePair<string, int>> GetCpuFlagsAndRegisters()
|
|
|
|
|
{
|
2013-11-12 00:40:28 +00:00
|
|
|
|
var ret = new List<KeyValuePair<string, int>>();
|
|
|
|
|
int[] data = new int[LibMeteor.regnames.Length];
|
|
|
|
|
LibMeteor.libmeteor_getregs(data);
|
|
|
|
|
for (int i = 0; i < data.Length; i++)
|
|
|
|
|
ret.Add(new KeyValuePair<string, int>(LibMeteor.regnames[i], data[i]));
|
|
|
|
|
return ret;
|
2013-11-11 03:20:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-19 22:43:34 +00:00
|
|
|
|
public static readonly ControllerDefinition GBAController =
|
|
|
|
|
new ControllerDefinition
|
|
|
|
|
{
|
|
|
|
|
Name = "GBA Controller",
|
|
|
|
|
BoolButtons =
|
|
|
|
|
{
|
2013-07-29 02:11:00 +00:00
|
|
|
|
"Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "L", "R", "Power"
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
public ControllerDefinition ControllerDefinition { get { return GBAController; } }
|
|
|
|
|
public IController Controller { get; set; }
|
|
|
|
|
|
2012-12-10 00:43:43 +00:00
|
|
|
|
public GBA(CoreComm comm)
|
|
|
|
|
{
|
|
|
|
|
CoreComm = comm;
|
|
|
|
|
comm.VsyncNum = 262144;
|
|
|
|
|
comm.VsyncDen = 4389;
|
|
|
|
|
comm.CpuTraceAvailable = true;
|
|
|
|
|
comm.TraceHeader = " -Addr--- -Opcode- -Instruction------------------- -R0----- -R1----- -R2----- -R3----- -R4----- -R5----- -R6----- -R7----- -R8----- -R9----- -R10---- -R11---- -R12---- -R13(SP) -R14(LR) -R15(PC) -CPSR--- -SPSR---";
|
|
|
|
|
comm.NominalWidth = 240;
|
|
|
|
|
comm.NominalHeight = 160;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-20 03:17:53 +00:00
|
|
|
|
public void Load(byte[] rom, byte[] bios)
|
2012-11-19 22:43:34 +00:00
|
|
|
|
{
|
2012-11-20 03:17:53 +00:00
|
|
|
|
if (bios.Length != 16384)
|
|
|
|
|
throw new Exception("GBA bios must be exactly 16384 bytes!");
|
2012-11-30 05:16:37 +00:00
|
|
|
|
if (rom.Length > 32 * 1024 * 1024)
|
|
|
|
|
throw new Exception("Rom is too big!");
|
2012-11-19 22:43:34 +00:00
|
|
|
|
Init();
|
2012-11-23 04:28:38 +00:00
|
|
|
|
LibMeteor.libmeteor_hardreset();
|
2012-11-20 03:17:53 +00:00
|
|
|
|
LibMeteor.libmeteor_loadbios(bios, (uint)bios.Length);
|
2012-11-19 22:43:34 +00:00
|
|
|
|
LibMeteor.libmeteor_loadrom(rom, (uint)rom.Length);
|
2012-11-20 20:22:10 +00:00
|
|
|
|
|
|
|
|
|
SetUpMemoryDomains();
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void FrameAdvance(bool render, bool rendersound = true)
|
|
|
|
|
{
|
2012-11-20 02:58:39 +00:00
|
|
|
|
Controller.UpdateControls(Frame++);
|
|
|
|
|
IsLagFrame = true;
|
2012-11-23 04:28:38 +00:00
|
|
|
|
|
|
|
|
|
if (Controller["Power"])
|
|
|
|
|
LibMeteor.libmeteor_hardreset();
|
2012-11-23 16:55:37 +00:00
|
|
|
|
// due to the design of the tracing api, we have to poll whether it's active each frame
|
2012-12-10 00:43:43 +00:00
|
|
|
|
LibMeteor.libmeteor_settracecallback(CoreComm.Tracer.Enabled ? tracecallback : null);
|
2012-11-21 01:21:50 +00:00
|
|
|
|
if (!coredead)
|
|
|
|
|
LibMeteor.libmeteor_frameadvance();
|
2012-11-20 02:58:39 +00:00
|
|
|
|
if (IsLagFrame)
|
|
|
|
|
LagCount++;
|
2012-11-30 05:16:37 +00:00
|
|
|
|
if (EndOfFrameCallback != null)
|
|
|
|
|
EndOfFrameCallback();
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-20 15:52:36 +00:00
|
|
|
|
public int Frame { get; private set; }
|
|
|
|
|
public int LagCount { get; set; }
|
|
|
|
|
public bool IsLagFrame { get; private set; }
|
|
|
|
|
public string SystemId { get { return "GBA"; } }
|
|
|
|
|
public bool DeterministicEmulation { get { return true; } }
|
2012-11-19 22:43:34 +00:00
|
|
|
|
|
2013-08-24 16:54:22 +00:00
|
|
|
|
// todo: information about the saveram type would be useful here.
|
|
|
|
|
public string BoardName { get { return null; } }
|
|
|
|
|
|
2013-11-03 16:29:51 +00:00
|
|
|
|
public void ResetCounters()
|
2012-11-19 22:43:34 +00:00
|
|
|
|
{
|
2012-11-20 15:52:36 +00:00
|
|
|
|
Frame = 0;
|
|
|
|
|
LagCount = 0;
|
2012-11-25 15:41:40 +00:00
|
|
|
|
IsLagFrame = false;
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-20 15:52:36 +00:00
|
|
|
|
#region saveram
|
2012-11-19 22:43:34 +00:00
|
|
|
|
|
|
|
|
|
public byte[] ReadSaveRam()
|
|
|
|
|
{
|
2013-05-09 00:58:02 +00:00
|
|
|
|
if (disposed)
|
|
|
|
|
throw new ObjectDisposedException(this.GetType().ToString());
|
2012-11-24 02:25:47 +00:00
|
|
|
|
if (!LibMeteor.libmeteor_hassaveram())
|
|
|
|
|
return null;
|
|
|
|
|
IntPtr data = IntPtr.Zero;
|
|
|
|
|
uint size = 0;
|
|
|
|
|
if (!LibMeteor.libmeteor_savesaveram(ref data, ref size))
|
|
|
|
|
throw new Exception("libmeteor_savesaveram() returned false!");
|
|
|
|
|
byte[] ret = new byte[size];
|
|
|
|
|
Marshal.Copy(data, ret, 0, (int)size);
|
|
|
|
|
LibMeteor.libmeteor_savesaveram_destroy(data);
|
|
|
|
|
return ret;
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void StoreSaveRam(byte[] data)
|
|
|
|
|
{
|
2013-05-09 00:58:02 +00:00
|
|
|
|
if (disposed)
|
|
|
|
|
throw new ObjectDisposedException(this.GetType().ToString());
|
2012-11-24 02:25:47 +00:00
|
|
|
|
if (!LibMeteor.libmeteor_loadsaveram(data, (uint)data.Length))
|
|
|
|
|
throw new Exception("libmeteor_loadsaveram() returned false!");
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ClearSaveRam()
|
|
|
|
|
{
|
2013-05-09 00:58:02 +00:00
|
|
|
|
if (disposed)
|
|
|
|
|
throw new ObjectDisposedException(this.GetType().ToString());
|
2012-11-24 02:25:47 +00:00
|
|
|
|
LibMeteor.libmeteor_clearsaveram();
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-24 02:25:47 +00:00
|
|
|
|
public bool SaveRamModified
|
2013-05-09 00:58:02 +00:00
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (disposed)
|
|
|
|
|
throw new ObjectDisposedException(this.GetType().ToString());
|
|
|
|
|
return LibMeteor.libmeteor_hassaveram();
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{ }
|
|
|
|
|
}
|
2012-11-19 22:43:34 +00:00
|
|
|
|
|
2012-11-20 15:52:36 +00:00
|
|
|
|
#endregion
|
2012-11-19 22:43:34 +00:00
|
|
|
|
|
2012-11-20 02:58:39 +00:00
|
|
|
|
#region savestates
|
|
|
|
|
|
2012-11-24 21:23:50 +00:00
|
|
|
|
byte[] SaveCoreBinary()
|
|
|
|
|
{
|
|
|
|
|
IntPtr ndata = IntPtr.Zero;
|
|
|
|
|
uint nsize = 0;
|
|
|
|
|
if (!LibMeteor.libmeteor_savestate(ref ndata, ref nsize))
|
|
|
|
|
throw new Exception("libmeteor_savestate() failed!");
|
|
|
|
|
if (ndata == IntPtr.Zero || nsize == 0)
|
|
|
|
|
throw new Exception("libmeteor_savestate() returned bad!");
|
|
|
|
|
|
|
|
|
|
byte[] ret = new byte[nsize];
|
|
|
|
|
Marshal.Copy(ndata, ret, 0, (int)nsize);
|
|
|
|
|
LibMeteor.libmeteor_savestate_destroy(ndata);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadCoreBinary(byte[] data)
|
|
|
|
|
{
|
|
|
|
|
if (!LibMeteor.libmeteor_loadstate(data, (uint)data.Length))
|
|
|
|
|
throw new Exception("libmeteor_loadstate() failed!");
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-19 22:43:34 +00:00
|
|
|
|
public void SaveStateText(System.IO.TextWriter writer)
|
|
|
|
|
{
|
2012-11-24 21:23:50 +00:00
|
|
|
|
var temp = SaveStateBinary();
|
|
|
|
|
temp.SaveAsHex(writer);
|
|
|
|
|
// write extra copy of stuff we don't use
|
|
|
|
|
writer.WriteLine("Frame {0}", Frame);
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadStateText(System.IO.TextReader reader)
|
|
|
|
|
{
|
2012-11-24 21:23:50 +00:00
|
|
|
|
string hex = reader.ReadLine();
|
|
|
|
|
byte[] state = new byte[hex.Length / 2];
|
|
|
|
|
state.ReadFromHex(hex);
|
|
|
|
|
LoadStateBinary(new BinaryReader(new MemoryStream(state)));
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SaveStateBinary(System.IO.BinaryWriter writer)
|
|
|
|
|
{
|
2012-11-24 21:23:50 +00:00
|
|
|
|
byte[] data = SaveCoreBinary();
|
|
|
|
|
writer.Write(data.Length);
|
|
|
|
|
writer.Write(data);
|
|
|
|
|
// other variables
|
|
|
|
|
writer.Write(IsLagFrame);
|
|
|
|
|
writer.Write(LagCount);
|
|
|
|
|
writer.Write(Frame);
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadStateBinary(System.IO.BinaryReader reader)
|
|
|
|
|
{
|
2012-11-24 21:23:50 +00:00
|
|
|
|
int length = reader.ReadInt32();
|
|
|
|
|
byte[] data = reader.ReadBytes(length);
|
|
|
|
|
LoadCoreBinary(data);
|
|
|
|
|
// other variables
|
|
|
|
|
IsLagFrame = reader.ReadBoolean();
|
|
|
|
|
LagCount = reader.ReadInt32();
|
|
|
|
|
Frame = reader.ReadInt32();
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] SaveStateBinary()
|
|
|
|
|
{
|
|
|
|
|
MemoryStream ms = new MemoryStream();
|
|
|
|
|
BinaryWriter bw = new BinaryWriter(ms);
|
|
|
|
|
SaveStateBinary(bw);
|
|
|
|
|
bw.Flush();
|
|
|
|
|
return ms.ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-06 20:51:28 +00:00
|
|
|
|
public bool BinarySaveStatesPreferred { get { return true; } }
|
|
|
|
|
|
2012-11-20 02:58:39 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2012-12-10 00:43:43 +00:00
|
|
|
|
public CoreComm CoreComm { get; private set; }
|
|
|
|
|
|
2012-11-20 20:22:10 +00:00
|
|
|
|
#region memorydomains
|
2012-11-19 22:43:34 +00:00
|
|
|
|
|
2012-11-20 20:22:10 +00:00
|
|
|
|
List<MemoryDomain> _MemoryDomains = new List<MemoryDomain>();
|
2013-11-06 02:15:29 +00:00
|
|
|
|
public MemoryDomainList MemoryDomains { get; private set; }
|
2012-11-20 20:22:10 +00:00
|
|
|
|
|
|
|
|
|
void AddMemoryDomain(LibMeteor.MemoryArea which, int size, string name)
|
|
|
|
|
{
|
|
|
|
|
IntPtr data = LibMeteor.libmeteor_getmemoryarea(which);
|
|
|
|
|
if (data == IntPtr.Zero)
|
|
|
|
|
throw new Exception("libmeteor_getmemoryarea() returned NULL??");
|
|
|
|
|
|
2013-11-04 02:11:40 +00:00
|
|
|
|
MemoryDomain md = new MemoryDomain(name, size, MemoryDomain.Endian.Little,
|
2012-11-20 20:22:10 +00:00
|
|
|
|
delegate(int addr)
|
|
|
|
|
{
|
|
|
|
|
unsafe
|
|
|
|
|
{
|
|
|
|
|
byte* d = (byte*)data;
|
|
|
|
|
if (addr < 0 || addr >= size)
|
|
|
|
|
throw new IndexOutOfRangeException();
|
|
|
|
|
return d[addr];
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
delegate(int addr, byte val)
|
|
|
|
|
{
|
|
|
|
|
unsafe
|
|
|
|
|
{
|
|
|
|
|
byte* d = (byte*)data;
|
|
|
|
|
if (addr < 0 || addr >= size)
|
|
|
|
|
throw new IndexOutOfRangeException();
|
|
|
|
|
d[addr] = val;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
_MemoryDomains.Add(md);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetUpMemoryDomains()
|
|
|
|
|
{
|
|
|
|
|
_MemoryDomains.Clear();
|
|
|
|
|
// this must be first to coincide with "main memory"
|
|
|
|
|
// note that ewram could also be considered main memory depending on which hairs you split
|
|
|
|
|
AddMemoryDomain(LibMeteor.MemoryArea.iwram, 32 * 1024, "IWRAM");
|
|
|
|
|
AddMemoryDomain(LibMeteor.MemoryArea.ewram, 256 * 1024, "EWRAM");
|
|
|
|
|
AddMemoryDomain(LibMeteor.MemoryArea.bios, 16 * 1024, "BIOS");
|
|
|
|
|
AddMemoryDomain(LibMeteor.MemoryArea.palram, 1024, "PALRAM");
|
|
|
|
|
AddMemoryDomain(LibMeteor.MemoryArea.vram, 96 * 1024, "VRAM");
|
|
|
|
|
AddMemoryDomain(LibMeteor.MemoryArea.oam, 1024, "OAM");
|
|
|
|
|
// even if the rom is less than 32MB, the whole is still valid in meteor
|
|
|
|
|
AddMemoryDomain(LibMeteor.MemoryArea.rom, 32 * 1024 * 1024, "ROM");
|
2012-11-25 19:03:13 +00:00
|
|
|
|
// special domain for system bus
|
2013-09-29 15:43:49 +00:00
|
|
|
|
{
|
2013-11-04 02:11:40 +00:00
|
|
|
|
MemoryDomain sb = new MemoryDomain("BUS", 1 << 28, MemoryDomain.Endian.Little,
|
2013-09-29 15:43:49 +00:00
|
|
|
|
delegate(int addr)
|
|
|
|
|
{
|
|
|
|
|
return LibMeteor.libmeteor_peekbus((uint)addr);
|
|
|
|
|
},
|
|
|
|
|
delegate(int addr, byte val)
|
|
|
|
|
{
|
|
|
|
|
LibMeteor.libmeteor_writebus((uint)addr, val);
|
|
|
|
|
});
|
|
|
|
|
_MemoryDomains.Add(sb);
|
|
|
|
|
}
|
|
|
|
|
// special combined ram memory domain
|
|
|
|
|
{
|
|
|
|
|
var ew = _MemoryDomains[1];
|
|
|
|
|
var iw = _MemoryDomains[0];
|
2013-11-04 02:11:40 +00:00
|
|
|
|
MemoryDomain cr = new MemoryDomain("Combined WRAM", (256 + 32) * 1024, MemoryDomain.Endian.Little,
|
2013-09-29 15:43:49 +00:00
|
|
|
|
delegate(int addr)
|
|
|
|
|
{
|
|
|
|
|
if (addr >= 256 * 1024)
|
|
|
|
|
return iw.PeekByte(addr & 32767);
|
|
|
|
|
else
|
|
|
|
|
return ew.PeekByte(addr);
|
|
|
|
|
},
|
|
|
|
|
delegate(int addr, byte val)
|
|
|
|
|
{
|
|
|
|
|
if (addr >= 256 * 1024)
|
|
|
|
|
iw.PokeByte(addr & 32767, val);
|
|
|
|
|
else
|
|
|
|
|
ew.PokeByte(addr, val);
|
|
|
|
|
});
|
|
|
|
|
_MemoryDomains.Add(cr);
|
|
|
|
|
}
|
2013-11-06 02:15:29 +00:00
|
|
|
|
|
|
|
|
|
MemoryDomains = new MemoryDomainList(_MemoryDomains);
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-27 19:21:46 +00:00
|
|
|
|
public void GetGPUMemoryAreas(out IntPtr vram, out IntPtr palram, out IntPtr oam, out IntPtr mmio)
|
|
|
|
|
{
|
|
|
|
|
IntPtr _vram = LibMeteor.libmeteor_getmemoryarea(LibMeteor.MemoryArea.vram);
|
|
|
|
|
IntPtr _palram = LibMeteor.libmeteor_getmemoryarea(LibMeteor.MemoryArea.palram);
|
|
|
|
|
IntPtr _oam = LibMeteor.libmeteor_getmemoryarea(LibMeteor.MemoryArea.oam);
|
|
|
|
|
IntPtr _mmio = LibMeteor.libmeteor_getmemoryarea(LibMeteor.MemoryArea.io);
|
|
|
|
|
|
|
|
|
|
if (_vram == IntPtr.Zero || _palram == IntPtr.Zero || _oam == IntPtr.Zero || _mmio == IntPtr.Zero)
|
|
|
|
|
throw new Exception("libmeteor_getmemoryarea() failed!");
|
|
|
|
|
|
|
|
|
|
vram = _vram;
|
|
|
|
|
palram = _palram;
|
|
|
|
|
oam = _oam;
|
|
|
|
|
mmio = _mmio;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-20 20:22:10 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2012-11-20 15:52:36 +00:00
|
|
|
|
/// <summary>like libsnes, the library is single-instance</summary>
|
2012-11-19 22:43:34 +00:00
|
|
|
|
static GBA attachedcore;
|
2012-11-20 15:52:36 +00:00
|
|
|
|
/// <summary>hold pointer to message callback so it won't get GCed</summary>
|
2012-11-20 01:28:31 +00:00
|
|
|
|
LibMeteor.MessageCallback messagecallback;
|
2012-11-20 15:52:36 +00:00
|
|
|
|
/// <summary>hold pointer to input callback so it won't get GCed</summary>
|
2012-11-20 02:58:39 +00:00
|
|
|
|
LibMeteor.InputCallback inputcallback;
|
2012-11-21 01:21:50 +00:00
|
|
|
|
/// <summary>true if libmeteor aborted</summary>
|
|
|
|
|
bool coredead = false;
|
2012-11-23 16:55:37 +00:00
|
|
|
|
/// <summary>hold pointer to trace callback so it won't get GCed</summary>
|
|
|
|
|
LibMeteor.TraceCallback tracecallback;
|
2012-11-20 02:58:39 +00:00
|
|
|
|
|
|
|
|
|
LibMeteor.Buttons GetInput()
|
|
|
|
|
{
|
2013-11-13 18:30:57 +00:00
|
|
|
|
CoreComm.InputCallback.Call();
|
2012-11-20 15:52:36 +00:00
|
|
|
|
// libmeteor bitflips everything itself, so 0 == off, 1 == on
|
2012-11-20 02:58:39 +00:00
|
|
|
|
IsLagFrame = false;
|
|
|
|
|
LibMeteor.Buttons ret = 0;
|
|
|
|
|
if (Controller["Up"]) ret |= LibMeteor.Buttons.BTN_UP;
|
|
|
|
|
if (Controller["Down"]) ret |= LibMeteor.Buttons.BTN_DOWN;
|
|
|
|
|
if (Controller["Left"]) ret |= LibMeteor.Buttons.BTN_LEFT;
|
|
|
|
|
if (Controller["Right"]) ret |= LibMeteor.Buttons.BTN_RIGHT;
|
|
|
|
|
if (Controller["Select"]) ret |= LibMeteor.Buttons.BTN_SELECT;
|
|
|
|
|
if (Controller["Start"]) ret |= LibMeteor.Buttons.BTN_START;
|
|
|
|
|
if (Controller["B"]) ret |= LibMeteor.Buttons.BTN_B;
|
|
|
|
|
if (Controller["A"]) ret |= LibMeteor.Buttons.BTN_A;
|
|
|
|
|
if (Controller["L"]) ret |= LibMeteor.Buttons.BTN_L;
|
|
|
|
|
if (Controller["R"]) ret |= LibMeteor.Buttons.BTN_R;
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-21 01:21:50 +00:00
|
|
|
|
#region messagecallbacks
|
|
|
|
|
|
2012-11-20 15:52:36 +00:00
|
|
|
|
void PrintMessage(string msg, bool abort)
|
|
|
|
|
{
|
2012-11-21 01:21:50 +00:00
|
|
|
|
Console.Write(msg.Replace("\n", "\r\n"));
|
|
|
|
|
if (abort)
|
|
|
|
|
StopCore(msg);
|
2012-11-20 15:52:36 +00:00
|
|
|
|
}
|
2012-11-19 22:43:34 +00:00
|
|
|
|
|
2012-11-21 01:21:50 +00:00
|
|
|
|
void StopCore(string msg)
|
|
|
|
|
{
|
|
|
|
|
coredead = true;
|
|
|
|
|
Console.WriteLine("Core stopped.");
|
|
|
|
|
for (int i = 0; i < soundbuffer.Length; i++)
|
|
|
|
|
soundbuffer[i] = 0;
|
|
|
|
|
|
|
|
|
|
var gz = new System.IO.Compression.GZipStream(
|
|
|
|
|
new MemoryStream(Convert.FromBase64String(dispfont), false),
|
|
|
|
|
System.IO.Compression.CompressionMode.Decompress);
|
|
|
|
|
byte[] font = new byte[2048];
|
|
|
|
|
gz.Read(font, 0, 2048);
|
|
|
|
|
gz.Dispose();
|
|
|
|
|
|
|
|
|
|
// cores aren't supposed to have bad dependencies like System.Drawing, right?
|
|
|
|
|
|
|
|
|
|
int scx = 0;
|
|
|
|
|
int scy = 0;
|
|
|
|
|
|
|
|
|
|
foreach (char c in msg)
|
|
|
|
|
{
|
|
|
|
|
if (scx == 240 || c == '\n')
|
|
|
|
|
{
|
|
|
|
|
scy += 8;
|
|
|
|
|
scx = 0;
|
|
|
|
|
}
|
|
|
|
|
if (scy == 160)
|
|
|
|
|
break;
|
|
|
|
|
if (c == '\r' || c == '\n')
|
|
|
|
|
continue;
|
|
|
|
|
if (c < 256 && c != ' ')
|
|
|
|
|
{
|
|
|
|
|
int fpos = c * 8;
|
|
|
|
|
for (int j = 0; j < 8; j++)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
|
|
|
{
|
|
|
|
|
if ((font[fpos] >> i & 1) != 0)
|
|
|
|
|
videobuffer[(scy + j) * 240 + scx + i] = unchecked((int)0xffff0000);
|
|
|
|
|
else
|
|
|
|
|
videobuffer[(scy + j) * 240 + scx + i] = unchecked((int)0xff000000);
|
|
|
|
|
}
|
|
|
|
|
fpos++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
scx += 8;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const string dispfont =
|
|
|
|
|
"H4sICAInrFACAGZvby5yYXcARVU9q9RAFL2gjK8IT+0GDGoh1oGFGHDYQvwL2hoQroXhsdUqGGbxZ/gD" +
|
|
|
|
|
"bKys7BRhIZVYLgurIghvG3ksCPKKJfGcm1nfSTJn750792smWUmIr9++/vjmdYzDZlhuh1guFotpfiRH" +
|
|
|
|
|
"+dQ4n+aLxfOj/MgUR7mID8GLDMN2CftBgj54oEGG5ZuPH98sh93P3afJZHIzqGrw0e+/7LPs+OqVvuu7" +
|
|
|
|
|
"7vTZJb8J223Y+MtZHvLsstwuqlAVt+E1eh+DV0JU+s3mx3q9luCChjoIsVgI7Wg2kAHBQ1mkqPu6EBhk" +
|
|
|
|
|
"feYFcM5F0B0d9A74WtX2QvRtdU0SrBp6kaZpKIJ7XI341oV66sVp4TOtJS/L/IN+k8pnQkCbZb4QPEVB" +
|
|
|
|
|
"nYYhKB16JHZwbsZRDuBEDWsnEnQeTzSIz60CyHWV6cg19LOXjfb1TqKb1pSrzE0VHBUOvIed8ia3dZGb" +
|
|
|
|
|
"c96JM0ZhfgzPBPCbkWEPEs/4j+fO1kd2HM55Q0bf4PdmCW15E/HdFI1M7Dg/Z1xN64InguxqpGn6kkvF" +
|
|
|
|
|
"GaJ0Z32/6jrRkxjntFciMB79mTwPM5NLm0ffWac3iCb7kbx0XbfqzzqhEGBPLe2i9TVKmxGtiGPFIm1N" +
|
|
|
|
|
"tNj+ppMLDDl7Ywh1q62gPEUKlJX1Yw3k1uTo2P9sCseQW3Y80B4QLznrNwaOnbMGUDK9SNOvVgxt9vQH" +
|
|
|
|
|
"gj51IPn7SdlRFDt4MoarIGvKwyoFd6tV34CtAWTLRySiAZF5Oq5DcHvyAvuO8/FtLgZrRNcf9tlp8l/4" +
|
|
|
|
|
"sc64HPuhMnLmR/Z3jA/9cbAzexVj2CU59SPYD+rJyU6VfsiIh5NtL+j+/b7cyzmOu+op1wXrjzHXG2Ow" +
|
|
|
|
|
"Qikba6pbgwL0P7J4y89XDRsY7ZxEXLcmkydP/zz9NVv74P2N4yLVVaT8wIxDNv9NaRtG1pM5kinLVqKY" +
|
|
|
|
|
"ERndzXhOgOicGNe1yPLp5NUXnezAm99//3ymoX0xodQvsMKoE5GH18fr3aPx+v5ivPwFbt1KIx9VffYM" +
|
|
|
|
|
"g30GyUkPbV1zJgGzJpt+sWAxGEWSHwH4izg/hwAeBjEMw0GPweTDfNLyUWzSqdroXN+L9L1z1Gy3tsKe" +
|
|
|
|
|
"7Zbzpj/oOE+9P8iq5j/Nj/HUQK+S4omkuMJIaqD3g5+xQ2KwvIcEKshXE3YJNkfgjbg7/8YNLbV0Lqo6" +
|
|
|
|
|
"AFEaQqJmPlM7n+l9VeDHJTm57wGJPtjRwhg53+LD1DRnMvNFO9q3q9WqFfncnq6+tm7mszbzM4QziERe" +
|
|
|
|
|
"h7+LyO+zz8AYfQGerdf+P27cOBYaeUubt1RNU138q4wg74qiuFeGKjQA5BwOgxABACX8A6+GHm0ACAAA";
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2012-11-23 16:55:37 +00:00
|
|
|
|
void Trace(string msg)
|
|
|
|
|
{
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm.Tracer.Put(msg);
|
2012-11-23 16:55:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-30 05:16:37 +00:00
|
|
|
|
Action EndOfFrameCallback = null;
|
|
|
|
|
LibMeteor.ScanlineCallback scanlinecb = null;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
///
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="callback">null to cancel</param>
|
|
|
|
|
/// <param name="scanline">0-227, null = end of frame</param>
|
|
|
|
|
public void SetScanlineCallback(Action callback, int? scanline)
|
|
|
|
|
{
|
|
|
|
|
if (callback == null)
|
|
|
|
|
{
|
|
|
|
|
LibMeteor.libmeteor_setscanlinecallback(null, 400);
|
|
|
|
|
EndOfFrameCallback = null;
|
|
|
|
|
scanlinecb = null;
|
|
|
|
|
}
|
|
|
|
|
else if (scanline == null)
|
|
|
|
|
{
|
|
|
|
|
LibMeteor.libmeteor_setscanlinecallback(null, 400);
|
|
|
|
|
EndOfFrameCallback = callback;
|
|
|
|
|
scanlinecb = null;
|
|
|
|
|
}
|
|
|
|
|
else if (scanline >= 0 && scanline <= 227)
|
|
|
|
|
{
|
|
|
|
|
scanlinecb = new LibMeteor.ScanlineCallback(callback);
|
|
|
|
|
LibMeteor.libmeteor_setscanlinecallback(scanlinecb, (int)scanline);
|
|
|
|
|
EndOfFrameCallback = null;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentOutOfRangeException("Scanline must be in [0, 227]!");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-19 22:43:34 +00:00
|
|
|
|
void Init()
|
|
|
|
|
{
|
|
|
|
|
if (attachedcore != null)
|
|
|
|
|
attachedcore.Dispose();
|
|
|
|
|
|
2012-11-20 15:52:36 +00:00
|
|
|
|
messagecallback = PrintMessage;
|
2012-11-20 02:58:39 +00:00
|
|
|
|
inputcallback = GetInput;
|
2012-11-23 16:55:37 +00:00
|
|
|
|
tracecallback = Trace; // don't set this callback now, only set if enabled
|
2012-11-20 01:28:31 +00:00
|
|
|
|
LibMeteor.libmeteor_setmessagecallback(messagecallback);
|
2012-11-20 02:58:39 +00:00
|
|
|
|
LibMeteor.libmeteor_setkeycallback(inputcallback);
|
2012-11-20 01:28:31 +00:00
|
|
|
|
|
2012-11-19 22:43:34 +00:00
|
|
|
|
LibMeteor.libmeteor_init();
|
|
|
|
|
videobuffer = new int[240 * 160];
|
|
|
|
|
videohandle = GCHandle.Alloc(videobuffer, GCHandleType.Pinned);
|
2012-11-20 01:28:31 +00:00
|
|
|
|
soundbuffer = new short[2048]; // nominal length of one frame is something like 1480 shorts?
|
2012-11-19 22:43:34 +00:00
|
|
|
|
soundhandle = GCHandle.Alloc(soundbuffer, GCHandleType.Pinned);
|
|
|
|
|
|
|
|
|
|
if (!LibMeteor.libmeteor_setbuffers
|
|
|
|
|
(videohandle.AddrOfPinnedObject(), (uint)(sizeof(int) * videobuffer.Length),
|
|
|
|
|
soundhandle.AddrOfPinnedObject(), (uint)(sizeof(short) * soundbuffer.Length)))
|
2012-11-20 01:28:31 +00:00
|
|
|
|
throw new Exception("libmeteor_setbuffers() returned false??");
|
2012-11-19 22:43:34 +00:00
|
|
|
|
|
|
|
|
|
attachedcore = this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool disposed = false;
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
if (!disposed)
|
|
|
|
|
{
|
|
|
|
|
disposed = true;
|
|
|
|
|
videohandle.Free();
|
|
|
|
|
soundhandle.Free();
|
2012-11-20 15:52:36 +00:00
|
|
|
|
// guarantee crash if it gets accessed
|
|
|
|
|
LibMeteor.libmeteor_setbuffers(IntPtr.Zero, 240 * 160 * 4, IntPtr.Zero, 4);
|
|
|
|
|
messagecallback = null;
|
|
|
|
|
inputcallback = null;
|
2012-11-23 16:55:37 +00:00
|
|
|
|
tracecallback = null;
|
2012-11-20 15:52:36 +00:00
|
|
|
|
LibMeteor.libmeteor_setmessagecallback(messagecallback);
|
|
|
|
|
LibMeteor.libmeteor_setkeycallback(inputcallback);
|
2012-11-23 16:55:37 +00:00
|
|
|
|
LibMeteor.libmeteor_settracecallback(tracecallback);
|
2012-11-20 20:22:10 +00:00
|
|
|
|
_MemoryDomains.Clear();
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region IVideoProvider
|
|
|
|
|
|
|
|
|
|
public IVideoProvider VideoProvider { get { return this; } }
|
|
|
|
|
|
|
|
|
|
int[] videobuffer;
|
|
|
|
|
GCHandle videohandle;
|
|
|
|
|
|
|
|
|
|
public int[] GetVideoBuffer() { return videobuffer; }
|
|
|
|
|
public int VirtualWidth { get { return 240; } }
|
|
|
|
|
public int BufferWidth { get { return 240; } }
|
|
|
|
|
public int BufferHeight { get { return 160; } }
|
|
|
|
|
public int BackgroundColor { get { return unchecked((int)0xff000000); } }
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region ISoundProvider
|
|
|
|
|
|
|
|
|
|
short[] soundbuffer;
|
|
|
|
|
GCHandle soundhandle;
|
|
|
|
|
|
|
|
|
|
public ISoundProvider SoundProvider { get { return null; } }
|
|
|
|
|
public ISyncSoundProvider SyncSoundProvider { get { return this; } }
|
|
|
|
|
public bool StartAsyncSound() { return false; }
|
|
|
|
|
public void EndAsyncSound() { }
|
|
|
|
|
|
|
|
|
|
public void GetSamples(out short[] samples, out int nsamp)
|
|
|
|
|
{
|
|
|
|
|
uint nbytes = LibMeteor.libmeteor_emptysound();
|
|
|
|
|
samples = soundbuffer;
|
2012-11-21 01:21:50 +00:00
|
|
|
|
if (!coredead)
|
|
|
|
|
nsamp = (int)(nbytes / 4);
|
|
|
|
|
else
|
|
|
|
|
nsamp = 738;
|
2012-11-19 22:43:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void DiscardSamples()
|
|
|
|
|
{
|
|
|
|
|
LibMeteor.libmeteor_emptysound();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
}
|