BizHawk/BizHawk.Client.Common/Api/Classes/EmuApi.cs

306 lines
8.8 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES;
using BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
using BizHawk.Emulation.Cores.Nintendo.NES;
using BizHawk.Emulation.Cores.Nintendo.SNES;
using BizHawk.Emulation.Cores.PCEngine;
using BizHawk.Emulation.Cores.Sega.MasterSystem;
using BizHawk.Emulation.Cores.WonderSwan;
namespace BizHawk.Client.Common
{
[Description("A library for interacting with the currently loaded emulator core")]
public sealed class EmuApi : IEmu
{
[RequiredService]
private IEmulator Emulator { get; set; }
[OptionalService]
private IBoardInfo BoardInfo { get; set; }
[OptionalService]
private IDebuggable DebuggableCore { get; set; }
[OptionalService]
private IDisassemblable DisassemblableCore { get; set; }
[OptionalService]
private IInputPollable InputPollableCore { get; set; }
[OptionalService]
private IMemoryDomains MemoryDomains { get; set; }
[OptionalService]
private IRegionable RegionableCore { get; set; }
public EmuApi(Action<string> logCallback)
{
LogCallback = logCallback;
}
public EmuApi() : this(Console.WriteLine) {}
private readonly Action<string> LogCallback;
/// <summary>Using this property to get a reference to <see cref="Global.Config">Global.Config</see> is a terrible, horrible, no good, very bad idea. That's why it's not in the <see cref="IEmu">interface</see>.</summary>
public Config ForbiddenConfigReference
{
get
{
ForbiddenConfigReferenceUsed = true;
return Global.Config;
}
}
public Action FrameAdvanceCallback { get; set; }
public bool ForbiddenConfigReferenceUsed { get; private set; }
public Action YieldCallback { get; set; }
public void DisplayVsync(bool enabled) => Global.Config.VSync = enabled;
public void FrameAdvance() => FrameAdvanceCallback();
public int FrameCount() => Emulator.Frame;
public object Disassemble(uint pc, string name = "")
{
try
{
if (DisassemblableCore != null)
{
return new {
disasm = DisassemblableCore.Disassemble(
string.IsNullOrEmpty(name) ? MemoryDomains.SystemBus : MemoryDomains[name],
pc,
out var l
),
length = l
};
}
}
catch (NotImplementedException) {}
LogCallback($"Error: {Emulator.Attributes().CoreName} does not yet implement {nameof(IDisassemblable.Disassemble)}()");
return null;
}
public ulong? GetRegister(string name)
{
try
{
if (DebuggableCore != null)
{
var registers = DebuggableCore.GetCpuFlagsAndRegisters();
return registers.ContainsKey(name) ? registers[name].Value : (ulong?) null;
}
}
catch (NotImplementedException) {}
LogCallback($"Error: {Emulator.Attributes().CoreName} does not yet implement {nameof(IDebuggable.GetCpuFlagsAndRegisters)}()");
return null;
}
public Dictionary<string, ulong> GetRegisters()
{
try
{
if (DebuggableCore != null)
{
var table = new Dictionary<string, ulong>();
foreach (var kvp in DebuggableCore.GetCpuFlagsAndRegisters()) table[kvp.Key] = kvp.Value.Value;
return table;
}
}
catch (NotImplementedException) {}
LogCallback($"Error: {Emulator.Attributes().CoreName} does not yet implement {nameof(IDebuggable.GetCpuFlagsAndRegisters)}()");
return new Dictionary<string, ulong>();
}
public void SetRegister(string register, int value)
{
try
{
if (DebuggableCore != null)
{
DebuggableCore.SetCpuRegister(register, value);
return;
}
}
catch (NotImplementedException) {}
LogCallback($"Error: {Emulator.Attributes().CoreName} does not yet implement {nameof(IDebuggable.SetCpuRegister)}()");
}
public long TotalExecutedCycles()
{
try
{
if (DebuggableCore != null) return DebuggableCore.TotalExecutedCycles;
}
catch (NotImplementedException) {}
LogCallback($"Error: {Emulator.Attributes().CoreName} does not yet implement {nameof(IDebuggable.TotalExecutedCycles)}()");
return default;
}
public string GetSystemId() => Global.Game.System;
public bool IsLagged()
{
if (InputPollableCore != null) return InputPollableCore.IsLagFrame;
LogCallback($"Can not get lag information, {Emulator.Attributes().CoreName} does not implement {nameof(IInputPollable)}");
return false;
}
public void SetIsLagged(bool value = true)
{
if (InputPollableCore != null) InputPollableCore.IsLagFrame = value;
else LogCallback($"Can not set lag information, {Emulator.Attributes().CoreName} does not implement {nameof(IInputPollable)}");
}
public int LagCount()
{
if (InputPollableCore != null) return InputPollableCore.LagCount;
LogCallback($"Can not get lag information, {Emulator.Attributes().CoreName} does not implement {nameof(IInputPollable)}");
return default;
}
public void SetLagCount(int count)
{
if (InputPollableCore != null) InputPollableCore.LagCount = count;
else LogCallback($"Can not set lag information, {Emulator.Attributes().CoreName} does not implement {nameof(IInputPollable)}");
}
public void LimitFramerate(bool enabled) => Global.Config.ClockThrottle = enabled;
public void MinimizeFrameskip(bool enabled) => Global.Config.AutoMinimizeSkipping = enabled;
public void Yield() => YieldCallback();
public string GetDisplayType() => (RegionableCore?.Region)?.ToString() ?? "";
public string GetBoardName() => BoardInfo?.BoardName ?? "";
public object GetSettings() => Emulator switch
{
GPGX gpgx => gpgx.GetSettings(),
LibsnesCore snes => snes.GetSettings(),
NES nes => nes.GetSettings(),
PCEngine pce => pce.GetSettings(),
QuickNES quickNes => quickNes.GetSettings(),
SMS sms => sms.GetSettings(),
WonderSwan ws => ws.GetSettings(),
_ => (object) null
};
public bool PutSettings(object settings) => Emulator switch
{
GPGX gpgx => gpgx.PutSettings((GPGX.GPGXSettings) settings),
LibsnesCore snes => snes.PutSettings((LibsnesCore.SnesSettings) settings),
NES nes => nes.PutSettings((NES.NESSettings) settings),
PCEngine pce => pce.PutSettings((PCEngine.PCESettings) settings),
QuickNES quickNes => quickNes.PutSettings((QuickNES.QuickNESSettings) settings),
SMS sms => sms.PutSettings((SMS.SmsSettings) settings),
WonderSwan ws => ws.PutSettings((WonderSwan.Settings) settings),
_ => false
};
public void SetRenderPlanes(params bool[] args)
{
static bool GetSetting(bool[] settings, int index) => index >= settings.Length || settings[index];
void SetBSNES(LibsnesCore core)
{
var s = core.GetSettings();
s.ShowBG1_0 = s.ShowBG1_1 = GetSetting(args, 0);
s.ShowBG2_0 = s.ShowBG2_1 = GetSetting(args, 1);
s.ShowBG3_0 = s.ShowBG3_1 = GetSetting(args, 2);
s.ShowBG4_0 = s.ShowBG4_1 = GetSetting(args, 3);
s.ShowOBJ_0 = GetSetting(args, 4);
s.ShowOBJ_1 = GetSetting(args, 5);
s.ShowOBJ_2 = GetSetting(args, 6);
s.ShowOBJ_3 = GetSetting(args, 7);
core.PutSettings(s);
}
void SetCygne(WonderSwan ws)
{
var s = ws.GetSettings();
s.EnableSprites = GetSetting(args, 0);
s.EnableFG = GetSetting(args, 1);
s.EnableBG = GetSetting(args, 2);
ws.PutSettings(s);
}
void SetGPGX(GPGX core)
{
var s = core.GetSettings();
s.DrawBGA = GetSetting(args, 0);
s.DrawBGB = GetSetting(args, 1);
s.DrawBGW = GetSetting(args, 2);
s.DrawObj = GetSetting(args, 3);
core.PutSettings(s);
}
void SetNesHawk(NES core)
{
var s = core.GetSettings();
// in the future, we could do something more arbitrary here, but this isn't any worse than the old system
s.DispSprites = GetSetting(args, 0);
s.DispBackground = GetSetting(args, 1);
core.PutSettings(s);
}
void SetPCEHawk(PCEngine pce)
{
var s = pce.GetSettings();
s.ShowOBJ1 = GetSetting(args, 0);
s.ShowBG1 = GetSetting(args, 1);
if (args.Length > 2)
{
s.ShowOBJ2 = GetSetting(args, 2);
s.ShowBG2 = GetSetting(args, 3);
}
pce.PutSettings(s);
}
void SetQuickNES(QuickNES quicknes)
{
var s = quicknes.GetSettings();
// this core doesn't support disabling BG
var showSp = GetSetting(args, 0);
if (showSp && s.NumSprites == 0) s.NumSprites = 8;
else if (!showSp && s.NumSprites > 0) s.NumSprites = 0;
quicknes.PutSettings(s);
}
void SetSMSHawk(SMS sms)
{
var s = sms.GetSettings();
s.DispOBJ = GetSetting(args, 0);
s.DispBG = GetSetting(args, 1);
sms.PutSettings(s);
}
switch (Emulator)
{
case GPGX gpgx:
SetGPGX(gpgx);
break;
case LibsnesCore snes:
SetBSNES(snes);
break;
case NES nes:
SetNesHawk(nes);
break;
case PCEngine pce:
SetPCEHawk(pce);
break;
case QuickNES quicknes:
SetQuickNES(quicknes);
break;
case SMS sms:
SetSMSHawk(sms);
break;
case WonderSwan ws:
SetCygne(ws);
break;
}
}
}
}