diff --git a/.gitignore b/.gitignore index 2b540361b5..20ffa86c34 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ ExternalCoreProjects/Virtu/bin/*.* libsnes/vs2015/libsnes.VC.db waterbox/**/*.wbx waterbox/**/*.wbx.in +/BizHawkTool_template.zip diff --git a/BizHawk.Client.ApiHawk/BizHawk.Client.ApiHawk.csproj b/BizHawk.Client.ApiHawk/BizHawk.Client.ApiHawk.csproj index 9205707a7e..c99d27ef6f 100644 --- a/BizHawk.Client.ApiHawk/BizHawk.Client.ApiHawk.csproj +++ b/BizHawk.Client.ApiHawk/BizHawk.Client.ApiHawk.csproj @@ -37,6 +37,10 @@ + + False + ..\..\Users\uptho\Documents\BizHawk-2.3\dll\System.Data.SQLite.dll + @@ -48,6 +52,19 @@ + + + + + + + + + + + + + @@ -62,7 +79,25 @@ + + + + + + + + + + + + + + + + + + @@ -95,4 +130,4 @@ --> - + \ No newline at end of file diff --git a/BizHawk.Client.ApiHawk/Classes/Api/EmuApi.cs b/BizHawk.Client.ApiHawk/Classes/Api/EmuApi.cs new file mode 100644 index 0000000000..a3cda9ed76 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/Api/EmuApi.cs @@ -0,0 +1,459 @@ +using System; +using System.ComponentModel; +using System.Collections.Generic; + +using BizHawk.Client.Common; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Common.IEmulatorExtensions; +using BizHawk.Emulation.Cores.Nintendo.NES; +using BizHawk.Emulation.Cores.Nintendo.SNES; +using BizHawk.Emulation.Cores.PCEngine; +using BizHawk.Emulation.Cores.Consoles.Sega.gpgx; +using BizHawk.Emulation.Cores.Sega.MasterSystem; +using BizHawk.Emulation.Cores.WonderSwan; +using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES; + +namespace BizHawk.Client.ApiHawk +{ + [Description("A library for interacting with the currently loaded emulator core")] + public sealed class EmuApi : IEmu + { + private static class EmuStatic + { + public static void DisplayVsync(bool enabled) + { + Global.Config.VSync = enabled; + } + public static string GetSystemId() + { + return Global.Game.System; + } + public static void LimitFramerate(bool enabled) + { + Global.Config.ClockThrottle = enabled; + } + + public static void MinimizeFrameskip(bool enabled) + { + Global.Config.AutoMinimizeSkipping = enabled; + } + + } + [RequiredService] + private IEmulator Emulator { get; set; } + + [OptionalService] + private IDebuggable DebuggableCore { get; set; } + + [OptionalService] + private IDisassemblable DisassemblableCore { get; set; } + + [OptionalService] + private IMemoryDomains MemoryDomains { get; set; } + + [OptionalService] + private IInputPollable InputPollableCore { get; set; } + + [OptionalService] + private IRegionable RegionableCore { get; set; } + + [OptionalService] + private IBoardInfo BoardInfo { get; set; } + + public Action FrameAdvanceCallback { get; set; } + public Action YieldCallback { get; set; } + + public EmuApi() + { } + + public void DisplayVsync(bool enabled) + { + EmuStatic.DisplayVsync(enabled); + } + + public void FrameAdvance() + { + FrameAdvanceCallback(); + } + + public int FrameCount() + { + return Emulator.Frame; + } + + public object Disassemble(uint pc, string name = "") + { + try + { + if (DisassemblableCore == null) + { + throw new NotImplementedException(); + } + + MemoryDomain domain = MemoryDomains.SystemBus; + + if (!string.IsNullOrEmpty(name)) + { + domain = MemoryDomains[name]; + } + + int l; + var d = DisassemblableCore.Disassemble(domain, pc, out l); + return new { disasm = d, length = l }; + } + catch (NotImplementedException) + { + Console.WriteLine($"Error: {Emulator.Attributes().CoreName} does not yet implement disassemble()"); + return null; + } + } + + public ulong? GetRegister(string name) + { + try + { + if (DebuggableCore == null) + { + throw new NotImplementedException(); + } + + var registers = DebuggableCore.GetCpuFlagsAndRegisters(); + ulong? value = null; + if (registers.ContainsKey(name)) value = registers[name].Value; + return value; + } + catch (NotImplementedException) + { + Console.WriteLine($"Error: {Emulator.Attributes().CoreName} does not yet implement getregister()"); + return null; + } + } + + public Dictionary GetRegisters() + { + var table = new Dictionary(); + + try + { + if (DebuggableCore == null) + { + throw new NotImplementedException(); + } + + foreach (var kvp in DebuggableCore.GetCpuFlagsAndRegisters()) + { + table[kvp.Key] = kvp.Value.Value; + } + } + catch (NotImplementedException) + { + Console.WriteLine($"Error: {Emulator.Attributes().CoreName} does not yet implement getregisters()"); + } + + return table; + } + + public void SetRegister(string register, int value) + { + try + { + if (DebuggableCore == null) + { + throw new NotImplementedException(); + } + + DebuggableCore.SetCpuRegister(register, value); + } + catch (NotImplementedException) + { + Console.WriteLine($"Error: {Emulator.Attributes().CoreName} does not yet implement setregister()"); + } + } + + public long TotalExecutedycles() + { + try + { + if (DebuggableCore == null) + { + throw new NotImplementedException(); + } + + return DebuggableCore.TotalExecutedCycles; + } + catch (NotImplementedException) + { + Console.WriteLine($"Error: {Emulator.Attributes().CoreName} does not yet implement totalexecutedcycles()"); + + return 0; + } + } + + public string GetSystemId() + { + return EmuStatic.GetSystemId(); + } + + public bool IsLagged() + { + if (InputPollableCore != null) + { + return InputPollableCore.IsLagFrame; + } + + Console.WriteLine($"Can not get lag information, {Emulator.Attributes().CoreName} does not implement IInputPollable"); + return false; + } + + public void SetIsLagged(bool value = true) + { + if (InputPollableCore != null) + { + InputPollableCore.IsLagFrame = value; + } + else + { + Console.WriteLine($"Can not set lag information, {Emulator.Attributes().CoreName} does not implement IInputPollable"); + } + } + + public int LagCount() + { + if (InputPollableCore != null) + { + return InputPollableCore.LagCount; + } + + Console.WriteLine($"Can not get lag information, {Emulator.Attributes().CoreName} does not implement IInputPollable"); + return 0; + } + + public void SetLagCount(int count) + { + if (InputPollableCore != null) + { + InputPollableCore.LagCount = count; + } + else + { + Console.WriteLine($"Can not set lag information, {Emulator.Attributes().CoreName} does not implement IInputPollable"); + } + } + + public void LimitFramerate(bool enabled) + { + EmuStatic.LimitFramerate(enabled); + } + + public void MinimizeFrameskip(bool enabled) + { + EmuStatic.MinimizeFrameskip(enabled); + } + + public void Yield() + { + YieldCallback(); + } + + public string GetDisplayType() + { + if (RegionableCore != null) + { + return RegionableCore.Region.ToString(); + } + + return ""; + } + + public string GetBoardName() + { + if (BoardInfo != null) + { + return BoardInfo.BoardName; + } + + return ""; + } + public object GetSettings() + { + if (Emulator is GPGX) + { + var gpgx = Emulator as GPGX; + return gpgx.GetSettings(); + } + else if (Emulator is LibsnesCore) + { + var snes = Emulator as LibsnesCore; + return snes.GetSettings(); + } + else if (Emulator is NES) + { + var nes = Emulator as NES; + return nes.GetSettings(); + } + else if (Emulator is QuickNES) + { + var quicknes = Emulator as QuickNES; + return quicknes.GetSettings(); + } + else if (Emulator is PCEngine) + { + var pce = Emulator as PCEngine; + return pce.GetSettings(); + } + else if (Emulator is SMS) + { + var sms = Emulator as SMS; + return sms.GetSettings(); + } + else if (Emulator is WonderSwan) + { + var ws = Emulator as WonderSwan; + return ws.GetSettings(); + } + else + { + return null; + } + } + public bool PutSettings(object settings) + { + if (Emulator is GPGX) + { + var gpgx = Emulator as GPGX; + return gpgx.PutSettings(settings as GPGX.GPGXSettings); + } + else if (Emulator is LibsnesCore) + { + var snes = Emulator as LibsnesCore; + return snes.PutSettings(settings as LibsnesCore.SnesSettings); + } + else if (Emulator is NES) + { + var nes = Emulator as NES; + return nes.PutSettings(settings as NES.NESSettings); + } + else if (Emulator is QuickNES) + { + var quicknes = Emulator as QuickNES; + return quicknes.PutSettings(settings as QuickNES.QuickNESSettings); + } + else if (Emulator is PCEngine) + { + var pce = Emulator as PCEngine; + return pce.PutSettings(settings as PCEngine.PCESettings); + } + else if (Emulator is SMS) + { + var sms = Emulator as SMS; + return sms.PutSettings(settings as SMS.SMSSettings); + } + else if (Emulator is WonderSwan) + { + var ws = Emulator as WonderSwan; + return ws.PutSettings(settings as WonderSwan.Settings); + } + else + { + return false; + } + } + public void SetRenderPlanes(params bool[] luaParam) + { + if (Emulator is GPGX) + { + var gpgx = Emulator as GPGX; + var s = gpgx.GetSettings(); + s.DrawBGA = luaParam[0]; + s.DrawBGB = luaParam[1]; + s.DrawBGW = luaParam[2]; + s.DrawObj = luaParam[3]; + gpgx.PutSettings(s); + + } + else if (Emulator is LibsnesCore) + { + var snes = Emulator as LibsnesCore; + var s = snes.GetSettings(); + s.ShowBG1_0 = s.ShowBG1_1 = luaParam[0]; + s.ShowBG2_0 = s.ShowBG2_1 = luaParam[1]; + s.ShowBG3_0 = s.ShowBG3_1 = luaParam[2]; + s.ShowBG4_0 = s.ShowBG4_1 = luaParam[3]; + s.ShowOBJ_0 = luaParam[4]; + s.ShowOBJ_1 = luaParam[5]; + s.ShowOBJ_2 = luaParam[6]; + s.ShowOBJ_3 = luaParam[7]; + snes.PutSettings(s); + } + else if (Emulator is NES) + { + // in the future, we could do something more arbitrary here. + // but this isn't any worse than the old system + var nes = Emulator as NES; + var s = nes.GetSettings(); + s.DispSprites = luaParam[0]; + s.DispBackground = luaParam[1]; + nes.PutSettings(s); + } + else if (Emulator is QuickNES) + { + var quicknes = Emulator as QuickNES; + var s = quicknes.GetSettings(); + + // this core doesn't support disabling BG + bool showsp = GetSetting(0, luaParam); + if (showsp && s.NumSprites == 0) + { + s.NumSprites = 8; + } + else if (!showsp && s.NumSprites > 0) + { + s.NumSprites = 0; + } + + quicknes.PutSettings(s); + } + else if (Emulator is PCEngine) + { + var pce = Emulator as PCEngine; + var s = pce.GetSettings(); + s.ShowOBJ1 = GetSetting(0, luaParam); + s.ShowBG1 = GetSetting(1, luaParam); + if (luaParam.Length > 2) + { + s.ShowOBJ2 = GetSetting(2, luaParam); + s.ShowBG2 = GetSetting(3, luaParam); + } + + pce.PutSettings(s); + } + else if (Emulator is SMS) + { + var sms = Emulator as SMS; + var s = sms.GetSettings(); + s.DispOBJ = GetSetting(0, luaParam); + s.DispBG = GetSetting(1, luaParam); + sms.PutSettings(s); + } + else if (Emulator is WonderSwan) + { + var ws = Emulator as WonderSwan; + var s = ws.GetSettings(); + s.EnableSprites = GetSetting(0, luaParam); + s.EnableFG = GetSetting(1, luaParam); + s.EnableBG = GetSetting(2, luaParam); + ws.PutSettings(s); + } + } + + private static bool GetSetting(int index, bool[] settings) + { + if (index < settings.Length) + { + return settings[index]; + } + + return true; + } + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/Api/GameInfoApi.cs b/BizHawk.Client.ApiHawk/Classes/Api/GameInfoApi.cs new file mode 100644 index 0000000000..ca74d09da6 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/Api/GameInfoApi.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; + +using BizHawk.Client.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.ApiHawk +{ + public sealed class GameInfoApi : IGameInfo + { + [OptionalService] + private IBoardInfo BoardInfo { get; set; } + + public GameInfoApi() + { } + + public string GetRomName() + { + if (Global.Game != null) + { + return Global.Game.Name ?? ""; + } + + return ""; + } + + public string GetRomHash() + { + if (Global.Game != null) + { + return Global.Game.Hash ?? ""; + } + + return ""; + } + + public bool InDatabase() + { + if (Global.Game != null) + { + return !Global.Game.NotInDatabase; + } + + return false; + } + + public string GetStatus() + { + if (Global.Game != null) + { + return Global.Game.Status.ToString(); + } + + return ""; + } + + public bool IsStatusBad() + { + if (Global.Game != null) + { + return Global.Game.IsRomStatusBad(); + } + + return true; + } + + public string GetBoardType() + { + return BoardInfo?.BoardName ?? ""; + } + + public Dictionary GetOptions() + { + var options = new Dictionary(); + + if (Global.Game != null) + { + foreach (var option in Global.Game.GetOptionsDict()) + { + options[option.Key] = option.Value; + } + } + + return options; + } + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/Api/JoypadApi.cs b/BizHawk.Client.ApiHawk/Classes/Api/JoypadApi.cs new file mode 100644 index 0000000000..0c049ed97c --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/Api/JoypadApi.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Client.Common; + +namespace BizHawk.Client.ApiHawk +{ + public sealed class JoypadApi : IJoypad + { + public JoypadApi() + { } + + public Dictionary Get(int? controller = null) + { + var buttons = new Dictionary(); + var adaptor = Global.AutofireStickyXORAdapter; + foreach (var button in adaptor.Source.Definition.BoolButtons) + { + if (!controller.HasValue) + { + buttons[button] = adaptor.IsPressed(button); + } + else if (button.Length >= 3 && button.Substring(0, 2) == "P" + controller) + { + buttons[button.Substring(3)] = adaptor.IsPressed("P" + controller + " " + button.Substring(3)); + } + } + + foreach (var button in adaptor.Source.Definition.FloatControls) + { + if (controller == null) + { + buttons[button] = adaptor.GetFloat(button); + } + else if (button.Length >= 3 && button.Substring(0, 2) == "P" + controller) + { + buttons[button.Substring(3)] = adaptor.GetFloat("P" + controller + " " + button.Substring(3)); + } + } + + buttons["clear"] = null; + buttons["getluafunctionslist"] = null; + buttons["output"] = null; + + return buttons; + } + + // TODO: what about float controls? + public Dictionary GetImmediate() + { + var buttons = new Dictionary(); + var adaptor = Global.ActiveController; + foreach (var button in adaptor.Definition.BoolButtons) + { + buttons[button] = adaptor.IsPressed(button); + } + foreach (var button in adaptor.Definition.FloatControls) + { + buttons[button] = adaptor.GetFloat(button); + } + + return buttons; + } + + public void SetFromMnemonicStr(string inputLogEntry) + { + try + { + var lg = Global.MovieSession.MovieControllerInstance(); + lg.SetControllersAsMnemonic(inputLogEntry); + + foreach (var button in lg.Definition.BoolButtons) + { + Global.LuaAndAdaptor.SetButton(button, lg.IsPressed(button)); + } + + foreach (var floatButton in lg.Definition.FloatControls) + { + Global.LuaAndAdaptor.SetFloat(floatButton, lg.GetFloat(floatButton)); + } + } + catch (Exception) + { + Console.WriteLine("invalid mnemonic string: " + inputLogEntry); + } + } + + public void Set(Dictionary buttons, int? controller = null) + { + try + { + foreach (var button in buttons.Keys) + { + var invert = false; + bool? theValue; + var theValueStr = buttons[button].ToString(); + + if (!string.IsNullOrWhiteSpace(theValueStr)) + { + if (theValueStr.ToLower() == "false") + { + theValue = false; + } + else if (theValueStr.ToLower() == "true") + { + theValue = true; + } + else + { + invert = true; + theValue = null; + } + } + else + { + theValue = null; + } + + var toPress = button.ToString(); + if (controller.HasValue) + { + toPress = "P" + controller + " " + button; + } + + if (!invert) + { + if (theValue.HasValue) // Force + { + Global.LuaAndAdaptor.SetButton(toPress, theValue.Value); + Global.ActiveController.Overrides(Global.LuaAndAdaptor); + } + else // Unset + { + Global.LuaAndAdaptor.UnSet(toPress); + Global.ActiveController.Overrides(Global.LuaAndAdaptor); + } + } + else // Inverse + { + Global.LuaAndAdaptor.SetInverse(toPress); + Global.ActiveController.Overrides(Global.LuaAndAdaptor); + } + } + } + catch + { + /*Eat it*/ + } + } + public void Set(string button, bool? state = null, int? controller = null) + { + try + { + var toPress = button; + if (controller.HasValue) + { + toPress = "P" + controller + " " + button; + } + if (state.HasValue) + Global.LuaAndAdaptor.SetButton(toPress, state.Value); + else + Global.LuaAndAdaptor.UnSet(toPress); + Global.ActiveController.Overrides(Global.LuaAndAdaptor); + } + catch + { + /*Eat it*/ + } + } + public void SetAnalog(Dictionary controls, object controller = null) + { + try + { + foreach (var name in controls.Keys) + { + var theValueStr = controls[name].ToString(); + float? theValue = null; + + if (!string.IsNullOrWhiteSpace(theValueStr)) + { + float f; + if (float.TryParse(theValueStr, out f)) + { + theValue = f; + } + } + + if (controller == null) + { + Global.StickyXORAdapter.SetFloat(name.ToString(), theValue); + } + else + { + Global.StickyXORAdapter.SetFloat("P" + controller + " " + name, theValue); + } + } + } + catch + { + /*Eat it*/ + } + } + public void SetAnalog(string control, float? value = null, object controller = null) + { + try + { + if (controller == null) + { + Global.StickyXORAdapter.SetFloat(control, value); + } + else + { + Global.StickyXORAdapter.SetFloat("P" + controller + " " + control, value); + } + } + catch + { + /*Eat it*/ + } + } + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/Api/MemApi.cs b/BizHawk.Client.ApiHawk/Classes/Api/MemApi.cs new file mode 100644 index 0000000000..f54b282550 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/Api/MemApi.cs @@ -0,0 +1,288 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Common.IEmulatorExtensions; +using BizHawk.Common.BufferExtensions; + +namespace BizHawk.Client.ApiHawk +{ + public sealed class MemApi : MemApiBase, IMem + { + private MemoryDomain _currentMemoryDomain; + private bool _isBigEndian = false; + public MemApi() + : base() + { + } + + protected override MemoryDomain Domain + { + get + { + if (MemoryDomainCore != null) + { + if (_currentMemoryDomain == null) + { + _currentMemoryDomain = MemoryDomainCore.HasSystemBus + ? MemoryDomainCore.SystemBus + : MemoryDomainCore.MainMemory; + } + + return _currentMemoryDomain; + } + + var error = $"Error: {Emulator.Attributes().CoreName} does not implement memory domains"; + Console.WriteLine(error); + throw new NotImplementedException(error); + } + } + + #region Unique Library Methods + + public void SetBigEndian(bool enabled = true) + { + _isBigEndian = enabled; + } + + public List GetMemoryDomainList() + { + var list = new List(); + + foreach (var domain in DomainList) + { + list.Add(domain.Name); + } + + return list; + } + + public uint GetMemoryDomainSize(string name = "") + { + if (string.IsNullOrEmpty(name)) + { + return (uint)Domain.Size; + } + + return (uint)DomainList[VerifyMemoryDomain(name)].Size; + } + + public string GetCurrentMemoryDomain() + { + return Domain.Name; + } + + public uint GetCurrentMemoryDomainSize() + { + return (uint)Domain.Size; + } + + public bool UseMemoryDomain(string domain) + { + try + { + if (DomainList[domain] != null) + { + _currentMemoryDomain = DomainList[domain]; + return true; + } + + Console.WriteLine($"Unable to find domain: {domain}"); + return false; + } + catch // Just in case + { + Console.WriteLine($"Unable to find domain: {domain}"); + } + + return false; + } + + public string HashRegion(long addr, int count, string domain = null) + { + var d = string.IsNullOrEmpty(domain) ? Domain : DomainList[VerifyMemoryDomain(domain)]; + + // checks + if (addr < 0 || addr >= d.Size) + { + string error = $"Address {addr} is outside the bounds of domain {d.Name}"; + Console.WriteLine(error); + throw new ArgumentOutOfRangeException(error); + } + if (addr + count > d.Size) + { + string error = $"Address {addr} + count {count} is outside the bounds of domain {d.Name}"; + Console.WriteLine(error); + throw new ArgumentOutOfRangeException(error); + } + + byte[] data = new byte[count]; + for (int i = 0; i < count; i++) + { + data[i] = d.PeekByte(addr + i); + } + + using (var hasher = System.Security.Cryptography.SHA256.Create()) + { + return hasher.ComputeHash(data).BytesToHexString(); + } + } + + #endregion + + #region Endian Handling + + private int ReadSigned(long addr, int size, string domain = null) + { + if (_isBigEndian) return ReadSignedBig(addr, size, domain); + else return ReadSignedLittle(addr, size, domain); + } + + private uint ReadUnsigned(long addr, int size, string domain = null) + { + if (_isBigEndian) return ReadUnsignedBig(addr, size, domain); + else return ReadUnsignedLittle(addr, size, domain); + } + + private void WriteSigned(long addr, int value, int size, string domain = null) + { + if (_isBigEndian) WriteSignedBig(addr, value, size, domain); + else WriteSignedLittle(addr, value, size, domain); + } + + private void WriteUnsigned(long addr, uint value, int size, string domain = null) + { + if (_isBigEndian) WriteUnsignedBig(addr, value, size, domain); + else WriteUnsignedLittle(addr, value, size, domain); + } + + #endregion + + #region Common Special and Legacy Methods + + public uint ReadByte(long addr, string domain = null) + { + return ReadUnsignedByte(addr, domain); + } + + public void WriteByte(long addr, uint value, string domain = null) + { + WriteUnsignedByte(addr, value, domain); + } + + public new List ReadByteRange(long addr, int length, string domain = null) + { + return base.ReadByteRange(addr, length, domain); + } + + public new void WriteByteRange(long addr, List memoryblock, string domain = null) + { + base.WriteByteRange(addr, memoryblock, domain); + } + + public float ReadFloat(long addr, string domain = null) + { + return base.ReadFloat(addr, _isBigEndian, domain); + } + + public void WriteFloat(long addr, double value, string domain = null) + { + base.WriteFloat(addr, value, _isBigEndian, domain); + } + + #endregion + + #region 1 Byte + + public int ReadS8(long addr, string domain = null) + { + return (sbyte)ReadUnsignedByte(addr, domain); + } + + public uint ReadU8(long addr, string domain = null) + { + return (byte)ReadUnsignedByte(addr, domain); + } + + public void WriteS8(long addr, int value, string domain = null) + { + WriteSigned(addr, value, 1, domain); + } + + public void WriteU8(long addr, uint value, string domain = null) + { + WriteUnsignedByte(addr, value, domain); + } + + #endregion + + #region 2 Byte + public int ReadS16(long addr, string domain = null) + { + return (short)ReadSigned(addr, 2, domain); + } + + public void WriteS16(long addr, int value, string domain = null) + { + WriteSigned(addr, value, 2, domain); + } + + public uint ReadU16(long addr, string domain = null) + { + return (ushort)ReadUnsigned(addr, 2, domain); + } + + public void WriteU16(long addr, uint value, string domain = null) + { + WriteUnsigned(addr, value, 2, domain); + } + #endregion + + #region 3 Byte + + public int ReadS24(long addr, string domain = null) + { + return ReadSigned(addr, 3, domain); + } + public void WriteS24(long addr, int value, string domain = null) + { + WriteSigned(addr, value, 3, domain); + } + + public uint ReadU24(long addr, string domain = null) + { + return ReadUnsigned(addr, 3, domain); + } + + public void WriteU24(long addr, uint value, string domain = null) + { + WriteUnsigned(addr, value, 3, domain); + } + + #endregion + + #region 4 Byte + + public int ReadS32(long addr, string domain = null) + { + return ReadSigned(addr, 4, domain); + } + + public void WriteS32(long addr, int value, string domain = null) + { + WriteSigned(addr, value, 4, domain); + } + + public uint ReadU32(long addr, string domain = null) + { + return ReadUnsigned(addr, 4, domain); + } + + public void WriteU32(long addr, uint value, string domain = null) + { + WriteUnsigned(addr, value, 4, domain); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Client.ApiHawk/Classes/Api/MemApiBase.cs b/BizHawk.Client.ApiHawk/Classes/Api/MemApiBase.cs new file mode 100644 index 0000000000..bc5c328d66 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/Api/MemApiBase.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Common.IEmulatorExtensions; + +namespace BizHawk.Client.ApiHawk +{ + /// + /// Base class for the Memory and MainMemory plugin libraries + /// + public abstract class MemApiBase : IExternalApi + { + [RequiredService] + protected IEmulator Emulator { get; set; } + + [OptionalService] + protected IMemoryDomains MemoryDomainCore { get; set; } + + protected abstract MemoryDomain Domain { get; } + + protected MemApiBase() + { } + + protected IMemoryDomains DomainList + { + get + { + if (MemoryDomainCore != null) + { + return MemoryDomainCore; + } + + var error = $"Error: {Emulator.Attributes().CoreName} does not implement memory domains"; + Console.WriteLine(error); + throw new NotImplementedException(error); + } + } + + public string VerifyMemoryDomain(string domain) + { + try + { + if (DomainList[domain] == null) + { + Console.WriteLine($"Unable to find domain: {domain}, falling back to current"); + return Domain.Name; + } + + return domain; + } + catch // Just in case + { + Console.WriteLine($"Unable to find domain: {domain}, falling back to current"); + } + + return Domain.Name; + } + + protected uint ReadUnsignedByte(long addr, string domain = null) + { + var d = string.IsNullOrEmpty(domain) ? Domain : DomainList[VerifyMemoryDomain(domain)]; + if (addr < d.Size) + { + return d.PeekByte(addr); + } + + Console.WriteLine("Warning: attempted read of " + addr + " outside the memory size of " + d.Size); + return 0; + } + + protected void WriteUnsignedByte(long addr, uint v, string domain = null) + { + var d = string.IsNullOrEmpty(domain) ? Domain : DomainList[VerifyMemoryDomain(domain)]; + if (d.CanPoke()) + { + if (addr < d.Size) + { + d.PokeByte(addr, (byte)v); + } + else + { + Console.WriteLine("Warning: attempted write to " + addr + " outside the memory size of " + d.Size); + } + } + else + { + Console.WriteLine($"Error: the domain {d.Name} is not writable"); + } + } + + protected static int U2S(uint u, int size) + { + var s = (int)u; + s <<= 8 * (4 - size); + s >>= 8 * (4 - size); + return s; + } + + protected int ReadSignedLittle(long addr, int size, string domain = null) + { + return U2S(ReadUnsignedLittle(addr, size, domain), size); + } + + protected uint ReadUnsignedLittle(long addr, int size, string domain = null) + { + uint v = 0; + for (var i = 0; i < size; ++i) + { + v |= ReadUnsignedByte(addr + i, domain) << (8 * i); + } + + return v; + } + + protected int ReadSignedBig(long addr, int size, string domain = null) + { + return U2S(ReadUnsignedBig(addr, size, domain), size); + } + + protected uint ReadUnsignedBig(long addr, int size, string domain = null) + { + uint v = 0; + for (var i = 0; i < size; ++i) + { + v |= ReadUnsignedByte(addr + i, domain) << (8 * (size - 1 - i)); + } + + return v; + } + + protected void WriteSignedLittle(long addr, int v, int size, string domain = null) + { + WriteUnsignedLittle(addr, (uint)v, size, domain); + } + + protected void WriteUnsignedLittle(long addr, uint v, int size, string domain = null) + { + for (var i = 0; i < size; ++i) + { + WriteUnsignedByte(addr + i, (v >> (8 * i)) & 0xFF, domain); + } + } + + protected void WriteSignedBig(long addr, int v, int size, string domain = null) + { + WriteUnsignedBig(addr, (uint)v, size, domain); + } + + protected void WriteUnsignedBig(long addr, uint v, int size, string domain = null) + { + for (var i = 0; i < size; ++i) + { + WriteUnsignedByte(addr + i, (v >> (8 * (size - 1 - i))) & 0xFF, domain); + } + } + + #region public Library implementations + + protected List ReadByteRange(long addr, int length, string domain = null) + { + var d = string.IsNullOrEmpty(domain) ? Domain : DomainList[VerifyMemoryDomain(domain)]; + var lastAddr = length + addr; + var list = new List(); + for (; addr <= lastAddr; addr++) + { + if (addr < d.Size) + list.Add(d.PeekByte(addr)); + else { + Console.WriteLine("Warning: Attempted read " + addr + " outside memory domain size of " + d.Size + " in readbyterange()"); + list.Add(0); + } + } + + return list; + } + + protected void WriteByteRange(long addr, List memoryblock, string domain = null) + { + var d = string.IsNullOrEmpty(domain) ? Domain : DomainList[VerifyMemoryDomain(domain)]; + if (d.CanPoke()) + { + for (var i = 0; i < memoryblock.Count; i++) + { + if (addr < d.Size) + { + d.PokeByte(addr++, memoryblock[i]); + } + else + { + Console.WriteLine("Warning: Attempted write " + addr + " outside memory domain size of " + d.Size + " in writebyterange()"); + } + } + } + else + { + Console.WriteLine($"Error: the domain {d.Name} is not writable"); + } + } + + protected float ReadFloat(long addr, bool bigendian, string domain = null) + { + var d = string.IsNullOrEmpty(domain) ? Domain : DomainList[VerifyMemoryDomain(domain)]; + if (addr < d.Size) + { + var val = d.PeekUint(addr, bigendian); + var bytes = BitConverter.GetBytes(val); + return BitConverter.ToSingle(bytes, 0); + } + + Console.WriteLine("Warning: Attempted read " + addr + " outside memory size of " + d.Size); + + return 0; + } + + protected void WriteFloat(long addr, double value, bool bigendian, string domain = null) + { + var d = string.IsNullOrEmpty(domain) ? Domain : DomainList[VerifyMemoryDomain(domain)]; + if (d.CanPoke()) + { + if (addr < d.Size) + { + var dv = (float)value; + var bytes = BitConverter.GetBytes(dv); + var v = BitConverter.ToUInt32(bytes, 0); + d.PokeUint(addr, v, bigendian); + } + else + { + Console.WriteLine("Warning: Attempted write " + addr + " outside memory size of " + d.Size); + } + } + else + { + Console.WriteLine($"Error: the domain {Domain.Name} is not writable"); + } + } + + #endregion + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/Api/MemEventsApi.cs b/BizHawk.Client.ApiHawk/Classes/Api/MemEventsApi.cs new file mode 100644 index 0000000000..5848df5fc3 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/Api/MemEventsApi.cs @@ -0,0 +1,45 @@ +using System; + +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Common.IEmulatorExtensions; + +namespace BizHawk.Client.ApiHawk +{ + public sealed class MemEventsApi : IMemEvents + { + [RequiredService] + private IDebuggable DebuggableCore { get; set; } + + public MemEventsApi () : base() + { } + + public void AddReadCallback(Action cb, uint address, string domain) + { + if (DebuggableCore.MemoryCallbacksAvailable()) + { + DebuggableCore.MemoryCallbacks.Add(new MemoryCallback(domain, MemoryCallbackType.Read, "Plugin Hook", cb, address, null)); + } + } + public void AddWriteCallback(Action cb, uint address, string domain) + { + if (DebuggableCore.MemoryCallbacksAvailable()) + { + DebuggableCore.MemoryCallbacks.Add(new MemoryCallback(domain, MemoryCallbackType.Write, "Plugin Hook", cb, address, null)); + } + } + public void AddExecCallback(Action cb, uint address, string domain) + { + if (DebuggableCore.MemoryCallbacksAvailable() && DebuggableCore.MemoryCallbacks.ExecuteCallbacksAvailable) + { + DebuggableCore.MemoryCallbacks.Add(new MemoryCallback(domain, MemoryCallbackType.Execute, "Plugin Hook", cb, address, null)); + } + } + public void RemoveMemoryCallback(Action cb) + { + if (DebuggableCore.MemoryCallbacksAvailable()) + { + DebuggableCore.MemoryCallbacks.Remove(cb); + } + } + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/Api/MemorySaveStateApi.cs b/BizHawk.Client.ApiHawk/Classes/Api/MemorySaveStateApi.cs new file mode 100644 index 0000000000..b22eaa7540 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/Api/MemorySaveStateApi.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.ApiHawk +{ + public sealed class MemorySaveStateApi : IMemorySaveState + { + public MemorySaveStateApi() + { } + + [RequiredService] + private IStatable StatableCore { get; set; } + + private readonly Dictionary _memorySavestates = new Dictionary(); + + public string SaveCoreStateToMemory() + { + var guid = Guid.NewGuid(); + var bytes = (byte[])StatableCore.SaveStateBinary().Clone(); + + _memorySavestates.Add(guid, bytes); + + return guid.ToString(); + } + + public void LoadCoreStateFromMemory(string identifier) + { + var guid = new Guid(identifier); + + try + { + var state = _memorySavestates[guid]; + + using (var ms = new MemoryStream(state)) + using (var br = new BinaryReader(ms)) + { + StatableCore.LoadStateBinary(br); + } + } + catch + { + Console.WriteLine("Unable to find the given savestate in memory"); + } + } + + public void DeleteState(string identifier) + { + var guid = new Guid(identifier); + _memorySavestates.Remove(guid); + } + + public void ClearInMemoryStates() + { + _memorySavestates.Clear(); + } + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/Api/MovieApi.cs b/BizHawk.Client.ApiHawk/Classes/Api/MovieApi.cs new file mode 100644 index 0000000000..0b74cfd5cc --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/Api/MovieApi.cs @@ -0,0 +1,288 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using BizHawk.Client.Common; + +namespace BizHawk.Client.ApiHawk +{ + public sealed class MovieApi : IMovie + { + private static class MoviePluginStatic + { + public static string Filename() + { + return Global.MovieSession.Movie.Filename; + } + + public static bool GetReadOnly() + { + return Global.MovieSession.ReadOnly; + } + + public static ulong GetRerecordCount() + { + return Global.MovieSession.Movie.Rerecords; + } + + public static bool GetRerecordCounting() + { + return Global.MovieSession.Movie.IsCountingRerecords; + } + + public static bool IsLoaded() + { + return Global.MovieSession.Movie.IsActive; + } + + public static double Length() + { + return Global.MovieSession.Movie.FrameCount; + } + + public static string Mode() + { + if (Global.MovieSession.Movie.IsFinished) + { + return "FINISHED"; + } + + if (Global.MovieSession.Movie.IsPlaying) + { + return "PLAY"; + } + + if (Global.MovieSession.Movie.IsRecording) + { + return "RECORD"; + } + + return "INACTIVE"; + } + + public static void SetReadOnly(bool readOnly) + { + Global.MovieSession.ReadOnly = readOnly; + } + + public static void SetRerecordCount(double count) + { + // Lua numbers are always double, integer precision holds up + // to 53 bits, so throw an error if it's bigger than that. + const double PrecisionLimit = 9007199254740992d; + + if (count > PrecisionLimit) + { + throw new Exception("Rerecord count exceeds Lua integer precision."); + } + + Global.MovieSession.Movie.Rerecords = (ulong)count; + } + + public static void SetRerecordCounting(bool counting) + { + Global.MovieSession.Movie.IsCountingRerecords = counting; + } + + public static void Stop() + { + Global.MovieSession.Movie.Stop(); + } + + public static double GetFps() + { + if (Global.MovieSession.Movie.IsActive) + { + var movie = Global.MovieSession.Movie; + var system = movie.HeaderEntries[HeaderKeys.PLATFORM]; + var pal = movie.HeaderEntries.ContainsKey(HeaderKeys.PAL) && + movie.HeaderEntries[HeaderKeys.PAL] == "1"; + + return new PlatformFrameRates()[system, pal]; + } + + return 0.0; + } + + } + public MovieApi() + { } + + public bool StartsFromSavestate() + { + return Global.MovieSession.Movie.IsActive && Global.MovieSession.Movie.StartsFromSavestate; + } + + public bool StartsFromSaveram() + { + return Global.MovieSession.Movie.IsActive && Global.MovieSession.Movie.StartsFromSaveRam; + } + + public Dictionary GetInput(int frame) + { + if (!Global.MovieSession.Movie.IsActive) + { + Console.WriteLine("No movie loaded"); + return null; + } + + var input = new Dictionary(); + var adapter = Global.MovieSession.Movie.GetInputState(frame); + + if (adapter == null) + { + Console.WriteLine("Can't get input of the last frame of the movie. Use the previous frame"); + return null; + } + + foreach (var button in adapter.Definition.BoolButtons) + { + input[button] = adapter.IsPressed(button); + } + + foreach (var button in adapter.Definition.FloatControls) + { + input[button] = adapter.GetFloat(button); + } + + return input; + } + + public string GetInputAsMnemonic(int frame) + { + if (Global.MovieSession.Movie.IsActive && frame < Global.MovieSession.Movie.InputLogLength) + { + var lg = Global.MovieSession.LogGeneratorInstance(); + lg.SetSource(Global.MovieSession.Movie.GetInputState(frame)); + return lg.GenerateLogEntry(); + } + + return ""; + } + + public void Save(string filename = "") + { + if (!Global.MovieSession.Movie.IsActive) + { + return; + } + + if (!string.IsNullOrEmpty(filename)) + { + filename += "." + Global.MovieSession.Movie.PreferredExtension; + var test = new FileInfo(filename); + if (test.Exists) + { + Console.WriteLine($"File {filename} already exists, will not overwrite"); + return; + } + + Global.MovieSession.Movie.Filename = filename; + } + + Global.MovieSession.Movie.Save(); + } + + public Dictionary GetHeader() + { + var table = new Dictionary(); + if (Global.MovieSession.Movie.IsActive) + { + foreach (var kvp in Global.MovieSession.Movie.HeaderEntries) + { + table[kvp.Key] = kvp.Value; + } + } + + return table; + } + + public List GetComments() + { + var list = new List(Global.MovieSession.Movie.Comments.Count); + if (Global.MovieSession.Movie.IsActive) + { + for (int i = 0; i < Global.MovieSession.Movie.Comments.Count; i++) + { + list[i] = Global.MovieSession.Movie.Comments[i]; + } + } + + return list; + } + + public List GetSubtitles() + { + var list = new List(Global.MovieSession.Movie.Subtitles.Count); + if (Global.MovieSession.Movie.IsActive) + { + for (int i = 0; i < Global.MovieSession.Movie.Subtitles.Count; i++) + { + list[i] = Global.MovieSession.Movie.Subtitles[i].ToString(); + } + } + + return list; + } + + public string Filename() + { + return MoviePluginStatic.Filename(); + } + + public bool GetReadOnly() + { + return MoviePluginStatic.GetReadOnly(); + } + + public ulong GetRerecordCount() + { + return MoviePluginStatic.GetRerecordCount(); + } + + public bool GetRerecordCounting() + { + return MoviePluginStatic.GetRerecordCounting(); + } + + public bool IsLoaded() + { + return MoviePluginStatic.IsLoaded(); + } + + public double Length() + { + return MoviePluginStatic.Length(); + } + + public string Mode() + { + return MoviePluginStatic.Mode(); + } + + public void SetReadOnly(bool readOnly) + { + MoviePluginStatic.SetReadOnly(readOnly); + } + + public void SetRerecordCount(double count) + { + MoviePluginStatic.SetRerecordCount(count); + } + + public void SetRerecordCounting(bool counting) + { + MoviePluginStatic.SetRerecordCounting(counting); + } + + public void Stop() + { + MoviePluginStatic.Stop(); + } + + public double GetFps() + { + return MoviePluginStatic.GetFps(); + } + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/Api/PluginBase.cs b/BizHawk.Client.ApiHawk/Classes/Api/PluginBase.cs new file mode 100644 index 0000000000..24bf62c727 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/Api/PluginBase.cs @@ -0,0 +1,50 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.ApiHawk +{ + public abstract class PluginBase : IPlugin + { + /// + /// The base class from which all + /// plugins will be derived + /// + /// Actual plugins should implement + /// one of the below callback methods + /// or register memory callbacks in + /// their Init function. + /// + protected IApiContainer _api; + + public PluginBase() { } + + public abstract string Name { get; } + public abstract string Description { get; } + + public bool Enabled => Running; + public bool Paused => !Running; + + public bool Running { get; set; } + + public void Stop() + { + Running = false; + } + + public void Toggle() + { + Running = !Running; + } + + public virtual void PreFrameCallback() { } + public virtual void PostFrameCallback() { } + public virtual void SaveStateCallback(string name) { } + public virtual void LoadStateCallback(string name) { } + public virtual void InputPollCallback() { } + public virtual void ExitCallback() { } + public virtual void Init (IApiContainer api) + { + _api = api; + Running = true; + } + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/Api/SqlApi.cs b/BizHawk.Client.ApiHawk/Classes/Api/SqlApi.cs new file mode 100644 index 0000000000..6a18d507af --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/Api/SqlApi.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data.SQLite; + +namespace BizHawk.Client.ApiHawk +{ + public sealed class SqlApi : ISql + { + public SqlApi() : base() + { } + + SQLiteConnection m_dbConnection; + string connectionString; + + public string CreateDatabase(string name) + { + try + { + SQLiteConnection.CreateFile(name); + return "Database Created Successfully"; + } + catch (SQLiteException sqlEX) + { + return sqlEX.Message; + } + + } + + public string OpenDatabase(string name) + { + try + { + SQLiteConnectionStringBuilder connBuilder = new SQLiteConnectionStringBuilder(); + connBuilder.DataSource = name; + connBuilder.Version = 3; //SQLite version + connBuilder.JournalMode = SQLiteJournalModeEnum.Wal; //Allows for reads and writes to happen at the same time + connBuilder.DefaultIsolationLevel = System.Data.IsolationLevel.ReadCommitted; //This only helps make the database lock left. May be pointless now + connBuilder.SyncMode = SynchronizationModes.Off; //This shortens the delay for do synchronous calls. + m_dbConnection = new SQLiteConnection(connBuilder.ToString()); + connectionString = connBuilder.ToString(); + m_dbConnection.Open(); + m_dbConnection.Close(); + return "Database Opened Successfully"; + } + catch (SQLiteException sqlEX) + { + return sqlEX.Message; + } + } + + public string WriteCommand(string query = "") + { + if (query == "") + { + return "query is empty"; + } + try + { + m_dbConnection.Open(); + string sql = query; + SQLiteCommand command = new SQLiteCommand(sql, m_dbConnection); + command.ExecuteNonQuery(); + m_dbConnection.Close(); + + return "Command ran successfully"; + + } + catch (NullReferenceException nullEX) + { + return "Database not open."; + } + catch (SQLiteException sqlEX) + { + m_dbConnection.Close(); + return sqlEX.Message; + } + } + + public dynamic ReadCommand(string query = "") + { + if (query == "") + { + return "query is empty"; + } + try + { + var table = new Dictionary(); + m_dbConnection.Open(); + string sql = "PRAGMA read_uncommitted =1;" + query; + SQLiteCommand command = new SQLiteCommand(sql, m_dbConnection); + SQLiteDataReader reader = command.ExecuteReader(); + bool rows = reader.HasRows; + long rowCount = 0; + var columns = new List(); + for (int i = 0; i < reader.FieldCount; ++i) //Add all column names into list + { + columns.Add(reader.GetName(i)); + } + while (reader.Read()) + { + for (int i = 0; i < reader.FieldCount; ++i) + { + table[columns[i] + " " + rowCount.ToString()] = reader.GetValue(i); + } + rowCount += 1; + } + reader.Close(); + m_dbConnection.Close(); + if (rows == false) + { + return "No rows found"; + } + + return table; + + } + catch (NullReferenceException) + { + return "Database not opened."; + } + catch (SQLiteException sqlEX) + { + m_dbConnection.Close(); + return sqlEX.Message; + } + } + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/Api/UserDataApi.cs b/BizHawk.Client.ApiHawk/Classes/Api/UserDataApi.cs new file mode 100644 index 0000000000..735fe397b9 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/Api/UserDataApi.cs @@ -0,0 +1,52 @@ +using System; +using System.ComponentModel; + +using BizHawk.Client.Common; + +namespace BizHawk.Client.ApiHawk +{ + public sealed class UserDataApi : IUserData + { + public UserDataApi() : base() + { } + + public void Set(string name, object value) + { + if (value != null) + { + var t = value.GetType(); + if (!t.IsPrimitive && t != typeof(string)) + { + throw new InvalidOperationException("Invalid type for userdata"); + } + } + + Global.UserBag[name] = value; + } + + public object Get(string key) + { + if (Global.UserBag.ContainsKey(key)) + { + return Global.UserBag[key]; + } + + return null; + } + + public void Clear() + { + Global.UserBag.Clear(); + } + + public bool Remove(string key) + { + return Global.UserBag.Remove(key); + } + + public bool ContainsKey(string key) + { + return Global.UserBag.ContainsKey(key); + } + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/ApiInjector.cs b/BizHawk.Client.ApiHawk/Classes/ApiInjector.cs new file mode 100644 index 0000000000..811807659f --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/ApiInjector.cs @@ -0,0 +1,79 @@ +using System; +using System.Linq; + +using BizHawk.Common.ReflectionExtensions; + +namespace BizHawk.Client.ApiHawk +{ + /// + /// injects Apis into other classes + /// + public static class ApiInjector + { + /// + /// clears all Apis from a target + /// + public static void ClearApis(object target) + { + Type targetType = target.GetType(); + object[] tmp = new object[1]; + + foreach (var propinfo in + targetType.GetPropertiesWithAttrib(typeof(RequiredApiAttribute)) + .Concat(targetType.GetPropertiesWithAttrib(typeof(OptionalApiAttribute)))) + { + propinfo.GetSetMethod(true).Invoke(target, tmp); + } + } + + /// + /// Feeds the target its required Apis. + /// + /// false if update failed + public static bool UpdateApis(IExternalApiProvider source, object target) + { + Type targetType = target.GetType(); + object[] tmp = new object[1]; + + foreach (var propinfo in targetType.GetPropertiesWithAttrib(typeof(RequiredApiAttribute))) + { + tmp[0] = source.GetApi(propinfo.PropertyType); + if (tmp[0] == null) + { + return false; + } + + propinfo.GetSetMethod(true).Invoke(target, tmp); + } + + foreach (var propinfo in targetType.GetPropertiesWithAttrib(typeof(OptionalApiAttribute))) + { + tmp[0] = source.GetApi(propinfo.PropertyType); + propinfo.GetSetMethod(true).Invoke(target, tmp); + } + + return true; + } + + /// + /// Determines whether a target is available, considering its dependencies + /// and the Apis provided by the emulator core. + /// + public static bool IsAvailable(IExternalApiProvider source, Type targetType) + { + return targetType.GetPropertiesWithAttrib(typeof(RequiredApiAttribute)) + .Select(pi => pi.PropertyType) + .All(source.HasApi); + } + } + + [AttributeUsage(AttributeTargets.Property)] + public class RequiredApiAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public class OptionalApiAttribute : Attribute + { + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/BasicApiProvider.cs b/BizHawk.Client.ApiHawk/Classes/BasicApiProvider.cs new file mode 100644 index 0000000000..75da59a6d4 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Classes/BasicApiProvider.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BizHawk.Client.ApiHawk +{ + /// + /// A generic implementation of IExternalApi provider that provides + /// this functionality to any core. + /// The provider will scan an IExternal and register all IExternalApis + /// that the core object itself implements. In addition it provides + /// a Register() method to allow the core to pass in any additional Apis + /// + /// + public class BasicApiProvider : IExternalApiProvider + { + private readonly Dictionary _Apis = new Dictionary(); + + public BasicApiProvider(IApiContainer container) + { + // simplified logic here doesn't scan for possible Apis; just adds what it knows is implemented by the PluginApi + // this removes the possibility of automagically picking up a Api in a nested class, (find the type, then + // find the field), but we're going to keep such logic out of the basic provider. Anything the passed + // container doesn't implement directly needs to be added with Register() + // this also fully allows apis that are not IExternalApi + var libs = container.Libraries; + + _Apis = libs; + } + + /// + /// the client can call this to register an additional Api + /// + /// The to register + public void Register(T api) + where T : IExternalApi + { + if (api == null) + { + throw new ArgumentNullException(nameof(api)); + } + + _Apis[typeof(T)] = api; + } + + public T GetApi() + where T : IExternalApi + { + return (T)GetApi(typeof(T)); + } + + public object GetApi(Type t) + { + IExternalApi Api; + KeyValuePair[] k = _Apis.Where(kvp => t.IsAssignableFrom(kvp.Key)).ToArray(); + if (k.Length > 0) + { + return k[0].Value; + } + + return null; + } + + public bool HasApi() + where T : IExternalApi + { + return HasApi(typeof(T)); + } + + public bool HasApi(Type t) + { + return _Apis.ContainsKey(t); + } + + public IEnumerable AvailableApis + { + get + { + return _Apis.Select(d => d.Key); + } + } + } +} diff --git a/BizHawk.Client.ApiHawk/Classes/ClientApi.cs b/BizHawk.Client.ApiHawk/Classes/ClientApi.cs index 767d755105..19d01f9f08 100644 --- a/BizHawk.Client.ApiHawk/Classes/ClientApi.cs +++ b/BizHawk.Client.ApiHawk/Classes/ClientApi.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Threading.Tasks; using System.Windows.Forms; using BizHawk.Client.Common; +using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Nintendo.Gameboy; using BizHawk.Emulation.Cores.PCEngine; using BizHawk.Emulation.Cores.Sega.MasterSystem; @@ -21,6 +22,9 @@ namespace BizHawk.Client.ApiHawk { #region Fields + private static IEmulator Emulator; + private static IVideoProvider VideoProvider; + private static readonly Assembly clientAssembly; private static readonly object clientMainFormInstance; private static readonly Type mainFormClass; @@ -68,24 +72,57 @@ namespace BizHawk.Client.ApiHawk mainFormClass = clientAssembly.GetType("BizHawk.Client.EmuHawk.MainForm"); } + public static void UpdateEmulatorAndVP(IEmulator emu = null) + { + Emulator = emu; + VideoProvider = Emulation.Common.IEmulatorExtensions.Extensions.AsVideoProviderOrDefault(emu); + } + #endregion #region Methods + #region Helpers + + private static void InvokeMainFormMethod(string name, dynamic[] paramList = null) + { + List typeList = new List(); + MethodInfo method; + if (paramList != null) + { + foreach (var obj in paramList) + { + typeList.Add(obj.GetType()); + } + method = mainFormClass.GetMethod(name, typeList.ToArray()); + } + else method = mainFormClass.GetMethod(name); + method.Invoke(clientMainFormInstance, paramList); + } + + private static object GetMainFormField(string name) + { + return mainFormClass.GetField(name); + } + + private static void SetMainFormField(string name, object value) + { + mainFormClass.GetField(name).SetValue(clientMainFormInstance, value); + } + + #endregion + #region Public /// /// THE FrameAdvance stuff /// public static void DoFrameAdvance() { - MethodInfo method = mainFormClass.GetMethod("FrameAdvance"); - method.Invoke(clientMainFormInstance, null); + InvokeMainFormMethod("FrameAdvance", null); - method = mainFormClass.GetMethod("StepRunLoop_Throttle", BindingFlags.NonPublic | BindingFlags.Instance); - method.Invoke(clientMainFormInstance, null); + InvokeMainFormMethod("StepRunLoop_Throttle", null); - method = mainFormClass.GetMethod("Render", BindingFlags.NonPublic | BindingFlags.Instance); - method.Invoke(clientMainFormInstance, null); + InvokeMainFormMethod("Render", null); } /// @@ -124,8 +161,7 @@ namespace BizHawk.Client.ApiHawk /// Savetate friendly name public static void LoadState(string name) { - MethodInfo method = mainFormClass.GetMethod("LoadState"); - method.Invoke(clientMainFormInstance, new object[] { Path.Combine(PathManager.GetSaveStatePath(Global.Game), string.Format("{0}.{1}", name, "State")), name, false, false }); + InvokeMainFormMethod("LoadState", new object[] { Path.Combine(PathManager.GetSaveStatePath(Global.Game), string.Format("{0}.{1}", name, "State")), name, false, false }); } @@ -194,12 +230,11 @@ namespace BizHawk.Client.ApiHawk /// /// Raise when a rom is successfully Loaded /// - public static void OnRomLoaded() + public static void OnRomLoaded(IEmulator emu) { - if (RomLoaded != null) - { - RomLoaded(null, EventArgs.Empty); - } + Emulator = emu; + VideoProvider = Emulation.Common.IEmulatorExtensions.Extensions.AsVideoProviderOrDefault(emu); + RomLoaded?.Invoke(null, EventArgs.Empty); allJoypads = new List(RunningSystem.MaxControllers); for (int i = 1; i <= RunningSystem.MaxControllers; i++) @@ -215,10 +250,55 @@ namespace BizHawk.Client.ApiHawk /// Savetate friendly name public static void SaveState(string name) { - MethodInfo method = mainFormClass.GetMethod("SaveState"); - method.Invoke(clientMainFormInstance, new object[] { Path.Combine(PathManager.GetSaveStatePath(Global.Game), string.Format("{0}.{1}", name, "State")), name, false }); + InvokeMainFormMethod("SaveState", new object[] { Path.Combine(PathManager.GetSaveStatePath(Global.Game), string.Format("{0}.{1}", name, "State")), name, false }); } + /// + /// Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements + /// + /// Left padding + /// Top padding + /// Right padding + /// Bottom padding + public static void SetGameExtraPadding(int left, int top, int right, int bottom) + { + FieldInfo f = clientAssembly.GetType("BizHawk.Client.EmuHawk.GlobalWin").GetField("DisplayManager"); + object displayManager = f.GetValue(null); + f = f.FieldType.GetField("GameExtraPadding"); + f.SetValue(displayManager, new Padding(left, top, right, bottom)); + + InvokeMainFormMethod("FrameBufferResized"); + } + + /// + /// Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements + /// + /// Left padding + public static void SetGameExtraPadding(int left) + { + SetGameExtraPadding(left, 0, 0, 0); + } + + /// + /// Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements + /// + /// Left padding + /// Top padding + public static void SetGameExtraPadding(int left, int top) + { + SetGameExtraPadding(left, top, 0, 0); + } + + /// + /// Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements + /// + /// Left padding + /// Top padding + /// Right padding + public static void SetGameExtraPadding(int left, int top, int right) + { + SetGameExtraPadding(left, top, right, 0); + } /// /// Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements @@ -234,8 +314,7 @@ namespace BizHawk.Client.ApiHawk f = f.FieldType.GetField("ClientExtraPadding"); f.SetValue(displayManager, new Padding(left, top, right, bottom)); - MethodInfo resize = mainFormClass.GetMethod("FrameBufferResized"); - resize.Invoke(clientMainFormInstance, null); + InvokeMainFormMethod("FrameBufferResized"); } /// @@ -268,7 +347,6 @@ namespace BizHawk.Client.ApiHawk SetExtraPadding(left, top, right, 0); } - /// /// Set inputs in specified to specified player /// @@ -327,8 +405,7 @@ namespace BizHawk.Client.ApiHawk /// public static void UnpauseEmulation() { - MethodInfo method = mainFormClass.GetMethod("UnpauseEmulator"); - method.Invoke(clientMainFormInstance, null); + InvokeMainFormMethod("UnpauseEmulator", null); } #endregion Public @@ -375,6 +452,275 @@ namespace BizHawk.Client.ApiHawk } } + public static void CloseEmulator() + { + InvokeMainFormMethod("CloseEmulator"); + } + + public static void CloseEmulatorWithCode(int exitCode) + { + InvokeMainFormMethod("CloseEmulator", new object[] {exitCode}); + } + + public static int BorderHeight() + { + var point = new System.Drawing.Point(0, 0); + Type t = clientAssembly.GetType("BizHawk.Client.EmuHawk.GlobalWin"); + FieldInfo f = t.GetField("DisplayManager"); + object displayManager = f.GetValue(null); + MethodInfo m = t.GetMethod("TransFormPoint"); + point = (System.Drawing.Point) m.Invoke(displayManager, new object[] { point }); + return point.Y; + } + + public static int BorderWidth() + { + var point = new System.Drawing.Point(0, 0); + Type t = clientAssembly.GetType("BizHawk.Client.EmuHawk.GlobalWin"); + FieldInfo f = t.GetField("DisplayManager"); + object displayManager = f.GetValue(null); + MethodInfo m = t.GetMethod("TransFormPoint"); + point = (System.Drawing.Point)m.Invoke(displayManager, new object[] { point }); + return point.X; + } + + public static int BufferHeight() + { + return VideoProvider.BufferHeight; + } + + public static int BufferWidth() + { + return VideoProvider.BufferWidth; + } + + public static void ClearAutohold() + { + InvokeMainFormMethod("ClearHolds"); + } + + public static void CloseRom() + { + InvokeMainFormMethod("CloseRom"); + } + + public static void DisplayMessages(bool value) + { + Global.Config.DisplayMessages = value; + } + + public static void EnableRewind(bool enabled) + { + InvokeMainFormMethod("EnableRewind", new object[] {enabled}); + } + + public static void FrameSkip(int numFrames) + { + if (numFrames > 0) + { + Global.Config.FrameSkip = numFrames; + InvokeMainFormMethod("FrameSkipMessage"); + } + else + { + Console.WriteLine("Invalid frame skip value"); + } + } + + public static int GetTargetScanlineIntensity() + { + return Global.Config.TargetScanlineFilterIntensity; + } + + public static int GetWindowSize() + { + return Global.Config.TargetZoomFactors[Emulator.SystemId]; + } + + public static void SetSoundOn(bool enable) + { + Global.Config.SoundEnabled = enable; + } + + public static bool GetSoundOn() + { + return Global.Config.SoundEnabled; + } + + public static bool IsPaused() + { + return (bool) GetMainFormField("EmulatorPaused"); + } + + public static bool IsTurbo() + { + return (bool)GetMainFormField("IsTurboing"); + } + + public static bool IsSeeking() + { + return (bool)GetMainFormField("IsSeeking"); + } + + public static void OpenRom(string path) + { + var ioa = OpenAdvancedSerializer.ParseWithLegacy(path); + Type t = clientAssembly.GetType("BizHawk.Client.EmuHawk.GlobalWin.MainForm.LoadRomArgs"); + object o = Activator.CreateInstance(t); + t.GetField("OpenAdvanced").SetValue(o, ioa); + + InvokeMainFormMethod("LoadRom", new object[] {path, o}); + } + + public static void Pause() + { + InvokeMainFormMethod("PauseEmulator"); + } + + public static void PauseAv() + { + SetMainFormField("PauseAvi", true); + } + + public static void RebootCore() + { + InvokeMainFormMethod("RebootCore"); + } + + public static void SaveRam() + { + InvokeMainFormMethod("FlushSaveRAM"); + } + + public static int ScreenHeight() + { + Type t = GetMainFormField("PresentationPanel").GetType(); + object o = GetMainFormField("PresentationPanel"); + o = t.GetField("NativeSize").GetValue(o); + t = t.GetField("NativeSize").GetType(); + + return (int) t.GetField("Height").GetValue(o); + } + + public static void Screenshot(string path = null) + { + if (path == null) + { + InvokeMainFormMethod("TakeScreenshot"); + } + else + { + InvokeMainFormMethod("TakeScreenshot", new object[] {path}); + } + } + + public static void ScreenshotToClipboard() + { + InvokeMainFormMethod("TakeScreenshotToClipboard"); + } + + public static void SetTargetScanlineIntensity(int val) + { + Global.Config.TargetScanlineFilterIntensity = val; + } + + public static void SetScreenshotOSD(bool value) + { + Global.Config.Screenshot_CaptureOSD = value; + } + + public static int ScreenWidth() + { + Type t = GetMainFormField("PresentationPanel").GetType(); + object o = GetMainFormField("PresentationPanel"); + o = t.GetField("NativeSize").GetValue(o); + t = t.GetField("NativeSize").GetType(); + + return (int) t.GetField("Width").GetValue(o); + } + + public static void SetWindowSize(int size) + { + if (size == 1 || size == 2 || size == 3 || size == 4 || size == 5 || size == 10) + { + Global.Config.TargetZoomFactors[Emulator.SystemId] = size; + InvokeMainFormMethod("FrameBufferResized"); + Type t = clientAssembly.GetType("BizHawk.Client.EmuHawk.GlobalWin"); + FieldInfo f = t.GetField("OSD"); + object osd = f.GetValue(null); + t = f.GetType(); + MethodInfo m = t.GetMethod("AddMessage"); + m.Invoke(osd, new Object[] { "Window size set to " + size + "x" }); + } + else + { + Console.WriteLine("Invalid window size"); + } + } + + public static void SpeedMode(int percent) + { + if (percent > 0 && percent < 6400) + { + InvokeMainFormMethod("ClickSpeedItem", new object[] {percent}); + } + else + { + Console.WriteLine("Invalid speed value"); + } + } + + public static void TogglePause() + { + InvokeMainFormMethod("TogglePause"); + } + + public static int TransformPointX(int x) + { + var point = new System.Drawing.Point(x, 0); + Type t = clientAssembly.GetType("BizHawk.Client.EmuHawk.GlobalWin"); + FieldInfo f = t.GetField("DisplayManager"); + object displayManager = f.GetValue(null); + MethodInfo m = t.GetMethod("TransFormPoint"); + point = (System.Drawing.Point)m.Invoke(displayManager, new object[] { point }); + return point.X; + } + + public static int TransformPointY(int y) + { + var point = new System.Drawing.Point(0, y); + Type t = clientAssembly.GetType("BizHawk.Client.EmuHawk.GlobalWin"); + FieldInfo f = t.GetField("DisplayManager"); + object displayManager = f.GetValue(null); + MethodInfo m = t.GetMethod("TransFormPoint"); + point = (System.Drawing.Point)m.Invoke(displayManager, new object[] { point }); + return point.Y; + } + + public static void Unpause() + { + InvokeMainFormMethod("UnpauseEmulator"); + } + + public static void UnpauseAv() + { + SetMainFormField("PauseAvi", false); + } + + public static int Xpos() + { + object o = GetMainFormField("DesktopLocation"); + Type t = mainFormClass.GetField("DesktopLocation").GetType(); + return (int)t.GetField("X").GetValue(o); + } + + public static int Ypos() + { + object o = GetMainFormField("DesktopLocation"); + Type t = mainFormClass.GetField("DesktopLocation").GetType(); + return (int)t.GetField("Y").GetValue(o); + } + #endregion #region Properties @@ -427,11 +773,11 @@ namespace BizHawk.Client.ApiHawk } else { - return SystemInfo.DualGB; + return SystemInfo.DualGB; } default: - return SystemInfo.FindByCoreSystem(SystemIdConverter.Convert(Global.Emulator.SystemId)); + return SystemInfo.FindByCoreSystem(SystemIdConverter.Convert(Global.Emulator.SystemId)); } } } diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IApiContainer.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IApiContainer.cs new file mode 100644 index 0000000000..ec169fee9d --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IApiContainer.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Client.ApiHawk +{ + public interface IApiContainer + { + Dictionary Libraries { get; set; } + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IComm.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IComm.cs new file mode 100644 index 0000000000..9ef8a5e0c0 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IComm.cs @@ -0,0 +1,35 @@ +namespace BizHawk.Client.ApiHawk +{ + public interface IComm : IExternalApi + { + #region Sockets + string SocketServerScreenShot(); + string SocketServerScreenShotResponse(); + string SocketServerSend(string SendString); + string SocketServerResponse(); + bool SocketServerSuccessful(); + void SocketServerSetTimeout(int timeout); + #endregion + + #region MemoryMappedFiles + void MmfSetFilename(string filename); + string MmfSetFilename(); + int MmfScreenshot(); + int MmfWrite(string mmf_filename, string outputString); + string MmfRead(string mmf_filename, int expectedSize); + #endregion + + #region HTTP + string HttpTest(); + string HttpTestGet(); + string HttpGet(string url); + string HttpPost(string url, string payload); + string HttpPostScreenshot(); + void HttpSetTimeout(int timeout); + void HttpSetPostUrl(string url); + void HttpSetGetUrl(string url); + string HttpGetPostUrl(); + string HttpGetGetUrl(); + #endregion + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IEmu.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IEmu.cs new file mode 100644 index 0000000000..51136126ac --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IEmu.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Client.ApiHawk +{ + public interface IEmu : IExternalApi + { + Action FrameAdvanceCallback { get; set; } + Action YieldCallback { get; set; } + void DisplayVsync(bool enabled); + void FrameAdvance(); + int FrameCount(); + object Disassemble(uint pc, string name = ""); + ulong? GetRegister(string name); + Dictionary GetRegisters(); + void SetRegister(string register, int value); + long TotalExecutedycles(); + string GetSystemId(); + bool IsLagged(); + void SetIsLagged(bool value = true); + int LagCount(); + void SetLagCount(int count); + void LimitFramerate(bool enabled); + void MinimizeFrameskip(bool enabled); + void Yield(); + string GetDisplayType(); + string GetBoardName(); + object GetSettings(); + bool PutSettings(object settings); + void SetRenderPlanes(params bool[] param); + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IExternalApi.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IExternalApi.cs new file mode 100644 index 0000000000..8dc7c9da03 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IExternalApi.cs @@ -0,0 +1,10 @@ +namespace BizHawk.Client.ApiHawk +{ + /// + /// This interface specifies that a client exposes a given interface, such as , + /// for use by external tools. + /// + public interface IExternalApi + { + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IGameInfo.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IGameInfo.cs new file mode 100644 index 0000000000..814a6d268c --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IGameInfo.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace BizHawk.Client.ApiHawk +{ + public interface IGameInfo : IExternalApi + { + string GetRomName(); + string GetRomHash(); + bool InDatabase(); + string GetStatus(); + bool IsStatusBad(); + string GetBoardType(); + Dictionary GetOptions(); + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IGui.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IGui.cs new file mode 100644 index 0000000000..03e3247e51 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IGui.cs @@ -0,0 +1,51 @@ +using System.Drawing; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace BizHawk.Client.ApiHawk +{ + public interface IGui : IExternalApi + { + #region Gui API + void ToggleCompositingMode(); + ImageAttributes GetAttributes(); + void SetAttributes(ImageAttributes a); + void DrawNew(string name, bool? clear = true); + void DrawFinish(); + bool HasGUISurface { get; } + #endregion + + #region Helpers + void SetPadding(int all); + void SetPadding(int x, int y); + void SetPadding(int l, int t, int r, int b); + Padding GetPadding(); + #endregion + + void AddMessage(string message); + void ClearGraphics(); + void ClearText(); + void SetDefaultForegroundColor(Color color); + void SetDefaultBackgroundColor(Color color); + void SetDefaultTextBackground(Color color); + void SetDefaultPixelFont(string fontfamily); + void DrawBezier(Point p1, Point p2, Point p3, Point p4, Color? color = null); + void DrawBeziers(Point[] points, Color? color = null); + void DrawBox(int x, int y, int x2, int y2, Color? line = null, Color? background = null); + void DrawEllipse(int x, int y, int width, int height, Color? line = null, Color? background = null); + void DrawIcon(string path, int x, int y, int? width = null, int? height = null); + void DrawImage(string path, int x, int y, int? width = null, int? height = null, bool cache = true); + void ClearImageCache(); + void DrawImageRegion(string path, int source_x, int source_y, int source_width, int source_height, int dest_x, int dest_y, int? dest_width = null, int? dest_height = null); + void DrawLine(int x1, int y1, int x2, int y2, Color? color = null); + void DrawAxis(int x, int y, int size, Color? color = null); + void DrawPie(int x, int y, int width, int height, int startangle, int sweepangle, Color? line = null, Color? background = null); + void DrawPixel(int x, int y, Color? color = null); + void DrawPolygon(Point[] points, Color? line = null, Color? background = null); + void DrawRectangle(int x, int y, int width, int height, Color? line = null, Color? background = null); + void DrawString(int x, int y, string message, Color? forecolor = null, Color? backcolor = null, int? fontsize = null, + string fontfamily = null, string fontstyle = null, string horizalign = null, string vertalign = null); + void DrawText(int x, int y, string message, Color? forecolor = null, Color? backcolor = null, string fontfamily = null); + void Text(int x, int y, string message, Color? forecolor = null, string anchor = null); + } +} \ No newline at end of file diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IInput.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IInput.cs new file mode 100644 index 0000000000..507bb024bf --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IInput.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace BizHawk.Client.ApiHawk +{ + public interface IInput : IExternalApi + { + Dictionary Get(); + Dictionary GetMouse(); + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IJoypad.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IJoypad.cs new file mode 100644 index 0000000000..1a8ce3340a --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IJoypad.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace BizHawk.Client.ApiHawk +{ + public interface IJoypad : IExternalApi + { + Dictionary Get(int? controller = null); + + // TODO: what about float controls? + Dictionary GetImmediate(); + void SetFromMnemonicStr(string inputLogEntry); + void Set(Dictionary buttons, int? controller = null); + void Set(string button, bool? state = null, int? controller = null); + void SetAnalog(Dictionary controls, object controller = null); + void SetAnalog(string control, float? value = null, object controller = null); + + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IMem.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IMem.cs new file mode 100644 index 0000000000..7c8961ac5f --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IMem.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +namespace BizHawk.Client.ApiHawk +{ + public interface IMem : IExternalApi + { + void SetBigEndian(bool enabled = true); + + #region Domains + List GetMemoryDomainList(); + uint GetMemoryDomainSize(string name = ""); + string GetCurrentMemoryDomain(); + uint GetCurrentMemoryDomainSize(); + bool UseMemoryDomain(string domain); + string HashRegion(long addr, int count, string domain = null); + #endregion + #region Read + #region Special and Legacy Methods + uint ReadByte(long addr, string domain = null); + List ReadByteRange(long addr, int length, string domain = null); + float ReadFloat(long addr, string domain = null); + #endregion + #region Signed + int ReadS8(long addr, string domain = null); + int ReadS16(long addr, string domain = null); + int ReadS24(long addr, string domain = null); + int ReadS32(long addr, string domain = null); + #endregion + #region Unsigned + uint ReadU8(long addr, string domain = null); + uint ReadU16(long addr, string domain = null); + uint ReadU24(long addr, string domain = null); + uint ReadU32(long addr, string domain = null); + #endregion + #endregion + #region Write + #region Special and Legacy Methods + void WriteByte(long addr, uint value, string domain = null); + void WriteByteRange(long addr, List memoryblock, string domain = null); + void WriteFloat(long addr, double value, string domain = null); + #endregion + #region Signed + void WriteS8(long addr, int value, string domain = null); + void WriteS16(long addr, int value, string domain = null); + void WriteS24(long addr, int value, string domain = null); + void WriteS32(long addr, int value, string domain = null); + #endregion + #region Unigned + void WriteU8(long addr, uint value, string domain = null); + void WriteU16(long addr, uint value, string domain = null); + void WriteU24(long addr, uint value, string domain = null); + void WriteU32(long addr, uint value, string domain = null); + #endregion + #endregion + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IMemEvents.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IMemEvents.cs new file mode 100644 index 0000000000..b8650a9139 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IMemEvents.cs @@ -0,0 +1,12 @@ +using System; + +namespace BizHawk.Client.ApiHawk +{ + public interface IMemEvents : IExternalApi + { + void AddReadCallback(Action cb, uint address, string domain); + void AddWriteCallback(Action cb, uint address, string domain); + void AddExecCallback(Action cb, uint address, string domain); + void RemoveMemoryCallback(Action cb); + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IMemorySavestate.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IMemorySavestate.cs new file mode 100644 index 0000000000..e3444c7bb5 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IMemorySavestate.cs @@ -0,0 +1,10 @@ +namespace BizHawk.Client.ApiHawk +{ + public interface IMemorySaveState : IExternalApi + { + string SaveCoreStateToMemory(); + void LoadCoreStateFromMemory(string identifier); + void DeleteState(string identifier); + void ClearInMemoryStates(); + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IMovie.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IMovie.cs new file mode 100644 index 0000000000..c983ec1df1 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IMovie.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +namespace BizHawk.Client.ApiHawk +{ + public interface IMovie : IExternalApi + { + bool StartsFromSavestate(); + bool StartsFromSaveram(); + string Filename(); + Dictionary GetInput(int frame); + string GetInputAsMnemonic(int frame); + bool GetReadOnly(); + ulong GetRerecordCount(); + bool GetRerecordCounting(); + bool IsLoaded(); + double Length(); + string Mode(); + void Save(string filename = ""); + void SetReadOnly(bool readOnly); + void SetRerecordCount(double count); + void SetRerecordCounting(bool counting); + void Stop(); + double GetFps(); + Dictionary GetHeader(); + List GetComments(); + List GetSubtitles(); + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/ISaveState.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/ISaveState.cs new file mode 100644 index 0000000000..8f595610fe --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/ISaveState.cs @@ -0,0 +1,10 @@ +namespace BizHawk.Client.ApiHawk +{ + public interface ISaveState : IExternalApi + { + void Load(string path); + void LoadSlot(int slotNum); + void Save(string path); + void SaveSlot(int slotNum); + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/ISql.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/ISql.cs new file mode 100644 index 0000000000..99407ac1c2 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/ISql.cs @@ -0,0 +1,10 @@ +namespace BizHawk.Client.ApiHawk +{ + public interface ISql : IExternalApi + { + string CreateDatabase(string name); + string OpenDatabase(string name); + string WriteCommand(string query = ""); + dynamic ReadCommand(string query = ""); + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/ITool.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/ITool.cs new file mode 100644 index 0000000000..ce4cdce516 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/ITool.cs @@ -0,0 +1,16 @@ +using System; +namespace BizHawk.Client.ApiHawk +{ + public interface ITool : IExternalApi + { + Type GetTool(string name); + object CreateInstance(string name); + void OpenCheats(); + void OpenHexEditor(); + void OpenRamWatch(); + void OpenRamSearch(); + void OpenTasStudio(); + void OpenToolBox(); + void OpenTraceLogger(); + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/Api/IUserData.cs b/BizHawk.Client.ApiHawk/Interfaces/Api/IUserData.cs new file mode 100644 index 0000000000..234a7d4695 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/Api/IUserData.cs @@ -0,0 +1,11 @@ +namespace BizHawk.Client.ApiHawk +{ + public interface IUserData : IExternalApi + { + void Set(string name, object value); + object Get(string key); + void Clear(); + bool Remove(string key); + bool ContainsKey(string key); + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/IExternalApiProvider.cs b/BizHawk.Client.ApiHawk/Interfaces/IExternalApiProvider.cs new file mode 100644 index 0000000000..d44299f7b1 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/IExternalApiProvider.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; + +namespace BizHawk.Client.ApiHawk +{ + /// + /// This interface defines the mechanism by which External tools can retrieve + /// from a client implementation + /// An implementation should collect all available IExternalApi instances. + /// This interface defines only the external interaction. This interface does not specify the means + /// by which a api provider will be populated with available apis. However, an implementation + /// by design must provide this mechanism + /// + /// + public interface IExternalApiProvider + { + /// e + /// Returns whether or not T is available + /// + /// The to check + bool HasApi() where T : IExternalApi; + + /// + /// Returns whether or not t is available + /// + bool HasApi(Type t); + + /// + /// Returns an instance of T if T is available + /// Else returns null + /// + /// The requested + T GetApi() where T : IExternalApi; + + /// + /// Returns an instance of t if t is available + /// Else returns null + /// + object GetApi(Type t); + + /// + /// Gets a list of all currently registered Apis available to be retrieved + /// + IEnumerable AvailableApis { get; } + } +} diff --git a/BizHawk.Client.ApiHawk/Interfaces/IPlugin.cs b/BizHawk.Client.ApiHawk/Interfaces/IPlugin.cs new file mode 100644 index 0000000000..444ecec8b2 --- /dev/null +++ b/BizHawk.Client.ApiHawk/Interfaces/IPlugin.cs @@ -0,0 +1,12 @@ +namespace BizHawk.Client.ApiHawk +{ + interface IPlugin + { + void PreFrameCallback(); + void PostFrameCallback(); + void SaveStateCallback(string name); + void LoadStateCallback(string name); + void InputPollCallback(); + void Init(IApiContainer api); + } +} diff --git a/BizHawk.Client.Common/BizHawk.Client.Common.csproj b/BizHawk.Client.Common/BizHawk.Client.Common.csproj index 9816bf2bfb..cee56ac8a8 100644 --- a/BizHawk.Client.Common/BizHawk.Client.Common.csproj +++ b/BizHawk.Client.Common/BizHawk.Client.Common.csproj @@ -64,6 +64,7 @@ + @@ -244,6 +245,7 @@ + diff --git a/BizHawk.Client.EmuHawk/OpenAdvanced.cs b/BizHawk.Client.Common/OpenAdvanced.cs similarity index 94% rename from BizHawk.Client.EmuHawk/OpenAdvanced.cs rename to BizHawk.Client.Common/OpenAdvanced.cs index 9a9cfc1c91..b26bff967e 100644 --- a/BizHawk.Client.EmuHawk/OpenAdvanced.cs +++ b/BizHawk.Client.Common/OpenAdvanced.cs @@ -10,7 +10,7 @@ using Newtonsoft.Json; //this file contains some cumbersome self-"serialization" in order to gain a modicum of control over what the serialized output looks like //I don't want them to look like crufty json -namespace BizHawk.Client.EmuHawk +namespace BizHawk.Client.Common { public interface IOpenAdvanced { @@ -74,7 +74,7 @@ namespace BizHawk.Client.EmuHawk } } - class OpenAdvanced_Libretro : IOpenAdvanced, IOpenAdvancedLibretro + public class OpenAdvanced_Libretro : IOpenAdvanced, IOpenAdvancedLibretro { public OpenAdvanced_Libretro() { @@ -103,7 +103,7 @@ namespace BizHawk.Client.EmuHawk public string CorePath { get { return token.CorePath; } set { token.CorePath = value; } } } - class OpenAdvanced_LibretroNoGame : IOpenAdvanced, IOpenAdvancedLibretro + public class OpenAdvanced_LibretroNoGame : IOpenAdvanced, IOpenAdvancedLibretro { //you might think ideally we'd fetch the libretro core name from the core info inside it //but that would involve spinning up excess libretro core instances, which probably isnt good for stability, no matter how much we wish otherwise, not to mention slow. @@ -140,7 +140,7 @@ namespace BizHawk.Client.EmuHawk public string CorePath { get { return _corePath; } set { _corePath = value; } } } - class OpenAdvanced_OpenRom : IOpenAdvanced + public class OpenAdvanced_OpenRom : IOpenAdvanced { public OpenAdvanced_OpenRom() {} diff --git a/BizHawk.Client.Common/lua/EmuLuaLibrary.SQL.cs b/BizHawk.Client.Common/lua/EmuLuaLibrary.SQL.cs index 5738e975db..5cbc8d2df2 100644 --- a/BizHawk.Client.Common/lua/EmuLuaLibrary.SQL.cs +++ b/BizHawk.Client.Common/lua/EmuLuaLibrary.SQL.cs @@ -1,9 +1,8 @@ using System; -using System.Collections; +using System.Collections.Generic; using System.ComponentModel; using System.Data.SQLite; using NLua; -using System.Collections.Generic; namespace BizHawk.Client.Common { diff --git a/BizHawk.Client.Common/tools/Watch/DWordWatch.cs b/BizHawk.Client.Common/tools/Watch/DWordWatch.cs index 1e8c41f055..79a9ef03aa 100644 --- a/BizHawk.Client.Common/tools/Watch/DWordWatch.cs +++ b/BizHawk.Client.Common/tools/Watch/DWordWatch.cs @@ -217,9 +217,9 @@ namespace BizHawk.Client.Common case DisplayType.Hex: return val.ToHexString(8); case DisplayType.FixedPoint_20_12: - return $"{val / 4096.0:0.######}"; + return $"{(int)val / 4096.0:0.######}"; case DisplayType.FixedPoint_16_16: - return $"{val / 65536.0:0.######}"; + return $"{(int)val / 65536.0:0.######}"; case DisplayType.Float: var bytes = BitConverter.GetBytes(val); var _float = BitConverter.ToSingle(bytes, 0); diff --git a/BizHawk.Client.EmuHawk/Api/ApiContainer.cs b/BizHawk.Client.EmuHawk/Api/ApiContainer.cs new file mode 100644 index 0000000000..6a7b7102a3 --- /dev/null +++ b/BizHawk.Client.EmuHawk/Api/ApiContainer.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Linq; + +using BizHawk.Client.ApiHawk; + +namespace BizHawk.Client.EmuHawk +{ + public sealed class ApiContainer : IApiContainer + { + public IComm Comm => (IComm)Libraries[typeof(CommApi)]; + public IEmu Emu => (IEmu)Libraries[typeof(EmuApi)]; + public IGameInfo GameInfo => (IGameInfo)Libraries[typeof(GameInfoApi)]; + public IGui Gui => (IGui)Libraries[typeof(GuiApi)]; + public IInput Input => (IInput)Libraries[typeof(InputApi)]; + public IJoypad Joypad => (IJoypad)Libraries[typeof(JoypadApi)]; + public IMem Mem => (IMem)Libraries[typeof(MemApi)]; + public IMemEvents MemEvents => (IMemEvents)Libraries[typeof(MemEventsApi)]; + public IMemorySaveState MemorySaveState => (IMemorySaveState)Libraries[typeof(MemorySaveStateApi)]; + public IMovie Movie => (IMovie)Libraries[typeof(MovieApi)]; + public ISaveState SaveState => (ISaveState)Libraries[typeof(SaveStateApi)]; + public ISql Sql => (ISql)Libraries[typeof(SqlApi)]; + public ITool Tool => (ITool)Libraries[typeof(ToolApi)]; + public IUserData UserData => (IUserData)Libraries[typeof(UserDataApi)]; + public Dictionary Libraries { get; set; } + public ApiContainer(Dictionary libs) + { + Libraries = libs; + } + } +} diff --git a/BizHawk.Client.EmuHawk/Api/ApiManager.cs b/BizHawk.Client.EmuHawk/Api/ApiManager.cs new file mode 100644 index 0000000000..9dd5b71087 --- /dev/null +++ b/BizHawk.Client.EmuHawk/Api/ApiManager.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +using BizHawk.Common.ReflectionExtensions; +using BizHawk.Emulation.Common; +using BizHawk.Client.ApiHawk; + +namespace BizHawk.Client.EmuHawk + +{ + public static class ApiManager + { + private static ApiContainer container; + private static void Register(IEmulatorServiceProvider serviceProvider) + { + // Register external apis + var apis = Assembly + .Load("BizHawk.Client.ApiHawk") + .GetTypes() + .Where(t => typeof(IExternalApi).IsAssignableFrom(t)) + .Where(t => t.IsSealed) + .Where(t => ServiceInjector.IsAvailable(serviceProvider, t)) + .ToList(); + + apis.AddRange( + Assembly + .GetAssembly(typeof(ApiContainer)) + .GetTypes() + .Where(t => typeof(IExternalApi).IsAssignableFrom(t)) + .Where(t => t.IsSealed) + .Where(t => ServiceInjector.IsAvailable(serviceProvider, t))); + + foreach (var api in apis) + { + var instance = (IExternalApi)Activator.CreateInstance(api); + ServiceInjector.UpdateServices(serviceProvider, instance); + Libraries.Add(api, instance); + } + container = new ApiContainer(Libraries); + GlobalWin.ApiProvider = new BasicApiProvider(container); + } + private static readonly Dictionary Libraries = new Dictionary(); + public static void Restart(IEmulatorServiceProvider newServiceProvider) + { + Libraries.Clear(); + Register(newServiceProvider); + } + } +} diff --git a/BizHawk.Client.EmuHawk/Api/Libraries/CommApi.cs b/BizHawk.Client.EmuHawk/Api/Libraries/CommApi.cs new file mode 100644 index 0000000000..152658c639 --- /dev/null +++ b/BizHawk.Client.EmuHawk/Api/Libraries/CommApi.cs @@ -0,0 +1,121 @@ +using System; +using System.ComponentModel; + +using BizHawk.Emulation.Common; +using BizHawk.Client.ApiHawk; +using System.Text; +using System.Collections.Generic; +using System.Net.Http; +using System.Windows.Forms; + + +namespace BizHawk.Client.EmuHawk +{ + public sealed class CommApi : IComm + { + [RequiredService] + private IEmulator Emulator { get; set; } + + [RequiredService] + private IVideoProvider VideoProvider { get; set; } + + public CommApi() : base() + { } + + public string SocketServerScreenShot() + { + return GlobalWin.socketServer.SendScreenshot(); + } + public string SocketServerScreenShotResponse() + { + return GlobalWin.socketServer.SendScreenshot(1000).ToString(); + } + + public string SocketServerSend(string SendString) + { + return "Sent : " + GlobalWin.socketServer.SendString(SendString).ToString() + " bytes"; + } + public string SocketServerResponse() + { + return GlobalWin.socketServer.ReceiveMessage(); + } + + public bool SocketServerSuccessful() + { + return GlobalWin.socketServer.Successful(); + } + public void SocketServerSetTimeout(int timeout) + { + GlobalWin.socketServer.SetTimeout(timeout); + } + // All MemoryMappedFile related methods + public void MmfSetFilename(string filename) + { + GlobalWin.memoryMappedFiles.SetFilename(filename); + } + public string MmfSetFilename() + { + return GlobalWin.memoryMappedFiles.GetFilename(); + } + + public int MmfScreenshot() + { + return GlobalWin.memoryMappedFiles.ScreenShotToFile(); + } + + public int MmfWrite(string mmf_filename, string outputString) + { + return GlobalWin.memoryMappedFiles.WriteToFile(mmf_filename, Encoding.ASCII.GetBytes(outputString)); + } + public string MmfRead(string mmf_filename, int expectedSize) + { + return GlobalWin.memoryMappedFiles.ReadFromFile(mmf_filename, expectedSize).ToString(); + } + // All HTTP related methods + public string HttpTest() + { + var list = new StringBuilder(); + list.AppendLine(GlobalWin.httpCommunication.TestGet()); + list.AppendLine(GlobalWin.httpCommunication.SendScreenshot()); + list.AppendLine("done testing"); + return list.ToString(); + } + public string HttpTestGet() + { + return GlobalWin.httpCommunication.TestGet(); + } + public string HttpGet(string url) + { + return GlobalWin.httpCommunication.ExecGet(url); + } + + public string HttpPost(string url, string payload) + { + return GlobalWin.httpCommunication.ExecPost(url, payload); + } + public string HttpPostScreenshot() + { + return GlobalWin.httpCommunication.SendScreenshot(); + } + public void HttpSetTimeout(int timeout) + { + GlobalWin.httpCommunication.SetTimeout(timeout); + } + public void HttpSetPostUrl(string url) + { + GlobalWin.httpCommunication.SetPostUrl(url); + } + public void HttpSetGetUrl(string url) + { + GlobalWin.httpCommunication.SetGetUrl(url); + } + public string HttpGetPostUrl() + { + return GlobalWin.httpCommunication.GetPostUrl(); + } + public string HttpGetGetUrl() + { + return GlobalWin.httpCommunication.GetGetUrl(); + } + } +} diff --git a/BizHawk.Client.EmuHawk/Api/Libraries/GUIApi.cs b/BizHawk.Client.EmuHawk/Api/Libraries/GUIApi.cs new file mode 100644 index 0000000000..5270a6eece --- /dev/null +++ b/BizHawk.Client.EmuHawk/Api/Libraries/GUIApi.cs @@ -0,0 +1,655 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Windows.Forms; +using System.IO; + +using BizHawk.Client.ApiHawk; +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.EmuHawk +{ + public sealed class GuiApi : IGui + { + [RequiredService] + private IEmulator Emulator { get; set; } + private Color _defaultForeground = Color.White; + private Color? _defaultBackground; + private Color? _defaultTextBackground = Color.FromArgb(128, 0, 0, 0); + private int _defaultPixelFont = 1; // gens + private Padding _padding = new Padding(0); + private ImageAttributes _attributes = new ImageAttributes(); + private System.Drawing.Drawing2D.CompositingMode _compositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver; + + + public GuiApi() + { } + + private DisplaySurface _GUISurface = null; + + public bool HasGUISurface => _GUISurface != null; + + #region Gui API + public void ToggleCompositingMode() + { + _compositingMode = 1 - _compositingMode; + } + + public ImageAttributes GetAttributes() + { + return _attributes; + } + public void SetAttributes(ImageAttributes a) + { + _attributes = a; + } + + public void Dispose() + { + foreach (var brush in _solidBrushes.Values) + { + brush.Dispose(); + } + + foreach (var brush in _pens.Values) + { + brush.Dispose(); + } + } + + public void DrawNew(string name, bool? clear = true) + { + try + { + DrawFinish(); + _GUISurface = GlobalWin.DisplayManager.LockLuaSurface(name, clear ?? true); + } + catch (InvalidOperationException ex) + { + Console.WriteLine(ex.ToString()); + } + } + + public void DrawFinish() + { + if (_GUISurface != null) + { + GlobalWin.DisplayManager.UnlockLuaSurface(_GUISurface); + } + + _GUISurface = null; + } + #endregion + + #region Helpers + private readonly Dictionary _imageCache = new Dictionary(); + private readonly Dictionary _solidBrushes = new Dictionary(); + private readonly Dictionary _pens = new Dictionary(); + private SolidBrush GetBrush(Color color) + { + SolidBrush b; + if (!_solidBrushes.TryGetValue(color, out b)) + { + b = new SolidBrush(color); + _solidBrushes[color] = b; + } + + return b; + } + + private Pen GetPen(Color color) + { + Pen p; + if (!_pens.TryGetValue(color, out p)) + { + p = new Pen(color); + _pens[color] = p; + } + + return p; + } + + private Graphics GetGraphics() + { + var g = _GUISurface == null ? Graphics.FromImage(new Bitmap(1,1)) : _GUISurface.GetGraphics(); + + // we don't like CoreComm, right? Someone should find a different way to do this then. + var tx = Emulator.CoreComm.ScreenLogicalOffsetX; + var ty = Emulator.CoreComm.ScreenLogicalOffsetY; + if (tx != 0 || ty != 0) + { + var transform = g.Transform; + transform.Translate(-tx, -ty); + g.Transform = transform; + } + + return g; + } + public void SetPadding(int all) + { + _padding = new Padding(all); + } + public void SetPadding(int x, int y) + { + _padding = new Padding(x / 2, y / 2, x / 2 + x & 1, y / 2 + y & 1); + } + public void SetPadding(int l, int t, int r, int b) + { + _padding = new Padding(l, t, r, b); + } + public Padding GetPadding() + { + return _padding; + } + #endregion + + public void AddMessage(string message) + { + GlobalWin.OSD.AddMessage(message); + } + + public void ClearGraphics() + { + _GUISurface.Clear(); + DrawFinish(); + } + + public void ClearText() + { + GlobalWin.OSD.ClearGUIText(); + } + + public void SetDefaultForegroundColor(Color color) + { + _defaultForeground = color; + } + + public void SetDefaultBackgroundColor(Color color) + { + _defaultBackground = color; + } + + public void SetDefaultTextBackground(Color color) + { + _defaultTextBackground = color; + } + + public void SetDefaultPixelFont(string fontfamily) + { + switch (fontfamily) + { + case "fceux": + case "0": + _defaultPixelFont = 0; + break; + case "gens": + case "1": + _defaultPixelFont = 1; + break; + default: + Console.WriteLine($"Unable to find font family: {fontfamily}"); + return; + } + } + + public void DrawBezier(Point p1, Point p2, Point p3, Point p4, Color? color = null) + { + using (var g = GetGraphics()) + { + try + { + g.CompositingMode = _compositingMode; + g.DrawBezier(GetPen(color ?? _defaultForeground), p1, p2, p3, p4); + } + catch (Exception) + { + return; + } + } + } + + public void DrawBeziers(Point[] points, Color? color = null) + { + using (var g = GetGraphics()) + { + try + { + g.CompositingMode = _compositingMode; + g.DrawBeziers(GetPen(color ?? _defaultForeground), points); + } + catch (Exception) + { + return; + } + } + } + public void DrawBox(int x, int y, int x2, int y2, Color? line = null, Color? background = null) + { + using (var g = GetGraphics()) + { + try + { + float w; + float h; + if (x < x2) + { + w = x2 - x; + } + else + { + x2 = x - x2; + x -= x2; + w = Math.Max(x2, 0.1f); + } + + if (y < y2) + { + h = y2 - y; + } + else + { + y2 = y - y2; + y -= y2; + h = Math.Max(y2, 0.1f); + } + + g.CompositingMode = _compositingMode; + g.DrawRectangle(GetPen(line ?? _defaultForeground), x, y, w, h); + + var bg = background ?? _defaultBackground; + if (bg.HasValue) + { + g.FillRectangle(GetBrush(bg.Value), x + 1, y + 1, Math.Max(w - 1, 0), Math.Max(h - 1, 0)); + } + } + catch (Exception) + { + // need to stop the script from here + return; + } + } + } + + public void DrawEllipse(int x, int y, int width, int height, Color? line = null, Color? background = null) + { + using (var g = GetGraphics()) + { + try + { + var bg = background ?? _defaultBackground; + if (bg.HasValue) + { + var brush = GetBrush(bg.Value); + g.FillEllipse(brush, x, y, width, height); + } + + g.CompositingMode = _compositingMode; + g.DrawEllipse(GetPen(line ?? _defaultForeground), x, y, width, height); + } + catch (Exception) + { + // need to stop the script from here + return; + } + } + } + + public void DrawIcon(string path, int x, int y, int? width = null, int? height = null) + { + using (var g = GetGraphics()) + { + try + { + if (!File.Exists(path)) + { + AddMessage("File not found: " + path); + return; + } + + Icon icon; + if (width.HasValue && height.HasValue) + { + icon = new Icon(path, width.Value, height.Value); + } + else + { + icon = new Icon(path); + } + + g.CompositingMode = _compositingMode; + g.DrawIcon(icon, x, y); + } + catch (Exception) + { + return; + } + } + } + + public void DrawImage(string path, int x, int y, int? width = null, int? height = null, bool cache = true) + { + if (!File.Exists(path)) + { + Console.WriteLine("File not found: " + path); + return; + } + + using (var g = GetGraphics()) + { + Image img; + if (_imageCache.ContainsKey(path)) + { + img = _imageCache[path]; + } + else + { + img = Image.FromFile(path); + if (cache) + { + _imageCache.Add(path, img); + } + } + var destRect = new Rectangle(x, y, width ?? img.Width, height ?? img.Height); + + g.CompositingMode = _compositingMode; + g.DrawImage(img, destRect, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, _attributes); + } + } + + public void ClearImageCache() + { + foreach (var image in _imageCache) + { + image.Value.Dispose(); + } + + _imageCache.Clear(); + } + + public void DrawImageRegion(string path, int source_x, int source_y, int source_width, int source_height, int dest_x, int dest_y, int? dest_width = null, int? dest_height = null) + { + if (!File.Exists(path)) + { + Console.WriteLine("File not found: " + path); + return; + } + + using (var g = GetGraphics()) + { + Image img; + if (_imageCache.ContainsKey(path)) + { + img = _imageCache[path]; + } + else + { + img = Image.FromFile(path); + _imageCache.Add(path, img); + } + + var destRect = new Rectangle(dest_x, dest_y, dest_width ?? source_width, dest_height ?? source_height); + + g.CompositingMode = _compositingMode; + g.DrawImage(img, destRect, source_x, source_y, source_width, source_height, GraphicsUnit.Pixel, _attributes); + } + } + + public void DrawLine(int x1, int y1, int x2, int y2, Color? color = null) + { + using (var g = GetGraphics()) + { + g.CompositingMode = _compositingMode; + g.DrawLine(GetPen(color ?? _defaultForeground), x1, y1, x2, y2); + } + } + + public void DrawAxis(int x, int y, int size, Color? color = null) + { + DrawLine(x + size, y, x - size, y, color ?? _defaultForeground); + DrawLine(x, y + size, x, y - size, color ?? _defaultForeground); + } + + public void DrawPie(int x, int y, int width, int height, int startangle, int sweepangle, Color? line = null, Color? background = null) + { + using (var g = GetGraphics()) + { + g.CompositingMode = _compositingMode; + var bg = background ?? _defaultBackground; + if (bg.HasValue) + { + var brush = GetBrush(bg.Value); + g.FillPie(brush, x, y, width, height, startangle, sweepangle); + } + + g.DrawPie(GetPen(line ?? _defaultForeground), x + 1, y + 1, width - 1, height - 1, startangle, sweepangle); + } + } + + public void DrawPixel(int x, int y, Color? color = null) + { + using (var g = GetGraphics()) + { + try + { + g.DrawLine(GetPen(color ?? _defaultForeground), x, y, x + 0.1F, y); + } + catch (Exception) + { + return; + } + } + } + + public void DrawPolygon(Point[] points, Color? line = null, Color? background = null) + { + using (var g = GetGraphics()) + { + try + { + g.DrawPolygon(GetPen(line ?? _defaultForeground), points); + var bg = background ?? _defaultBackground; + if (bg.HasValue) + { + g.FillPolygon(GetBrush(bg.Value), points); + } + } + catch (Exception) + { + return; + } + } + } + + public void DrawRectangle(int x, int y, int width, int height, Color? line = null, Color? background = null) + { + using (var g = GetGraphics()) + { + var w = Math.Max(width, 0.1F); + var h = Math.Max(height, 0.1F); + g.DrawRectangle(GetPen(line ?? _defaultForeground), x, y, w, h); + var bg = background ?? _defaultBackground; + if (bg.HasValue) + { + g.FillRectangle(GetBrush(bg.Value), x + 1, y + 1, Math.Max(w - 1, 0), Math.Max(h - 1, 0)); + } + } + } + + public void DrawString(int x, int y, string message, Color? forecolor = null, Color? backcolor = null, int? fontsize = null, + string fontfamily = null, string fontstyle = null, string horizalign = null, string vertalign = null) + { + using (var g = GetGraphics()) + { + try + { + var family = FontFamily.GenericMonospace; + if (fontfamily != null) + { + family = new FontFamily(fontfamily); + } + + var fstyle = FontStyle.Regular; + if (fontstyle != null) + { + switch (fontstyle.ToLower()) + { + default: + case "regular": + break; + case "bold": + fstyle = FontStyle.Bold; + break; + case "italic": + fstyle = FontStyle.Italic; + break; + case "strikethrough": + fstyle = FontStyle.Strikeout; + break; + case "underline": + fstyle = FontStyle.Underline; + break; + } + } + + // The text isn't written out using GenericTypographic, so measuring it using GenericTypographic seemed to make it worse. + // And writing it out with GenericTypographic just made it uglier. :p + var f = new StringFormat(StringFormat.GenericDefault); + var font = new Font(family, fontsize ?? 12, fstyle, GraphicsUnit.Pixel); + Size sizeOfText = g.MeasureString(message, font, 0, f).ToSize(); + if (horizalign != null) + { + switch (horizalign.ToLower()) + { + default: + case "left": + break; + case "center": + x -= sizeOfText.Width / 2; + break; + case "right": + x -= sizeOfText.Width; + break; + } + } + + if (vertalign != null) + { + switch (vertalign.ToLower()) + { + default: + case "bottom": + break; + case "middle": + y -= sizeOfText.Height / 2; + break; + case "top": + y -= sizeOfText.Height; + break; + } + } + + var bg = backcolor ?? _defaultBackground; + if (bg.HasValue) + { + for (var xd = -1; xd <= 1; xd++) + { + for (var yd = -1; yd <= 1; yd++) + { + g.DrawString(message, font, GetBrush(bg.Value), x + xd, y + yd); + } + } + } + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit; + g.DrawString(message, font, GetBrush(forecolor ?? _defaultForeground), x, y); + } + catch (Exception) + { + return; + } + } + } + + public void DrawText(int x, int y, string message, Color? forecolor = null, Color? backcolor = null, string fontfamily = null) + { + using (var g = GetGraphics()) + { + try + { + var index = 0; + if (string.IsNullOrEmpty(fontfamily)) + { + index = _defaultPixelFont; + } + else + { + switch (fontfamily) + { + case "fceux": + case "0": + index = 0; + break; + case "gens": + case "1": + index = 1; + break; + default: + Console.WriteLine($"Unable to find font family: {fontfamily}"); + return; + } + } + + var f = new StringFormat(StringFormat.GenericTypographic) + { + FormatFlags = StringFormatFlags.MeasureTrailingSpaces + }; + var font = new Font(GlobalWin.DisplayManager.CustomFonts.Families[index], 8, FontStyle.Regular, GraphicsUnit.Pixel); + Size sizeOfText = g.MeasureString(message, font, 0, f).ToSize(); + var rect = new Rectangle(new Point(x, y), sizeOfText + new Size(1, 0)); + if (backcolor.HasValue) g.FillRectangle(GetBrush(backcolor.Value), rect); + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit; + g.DrawString(message, font, GetBrush(forecolor ?? _defaultForeground), x, y); + } + catch (Exception) + { + return; + } + } + } + + public void Text(int x, int y, string message, Color? forecolor = null, string anchor = null) + { + var a = 0; + + if (!string.IsNullOrEmpty(anchor)) + { + switch (anchor) + { + case "0": + case "topleft": + a = 0; + break; + case "1": + case "topright": + a = 1; + break; + case "2": + case "bottomleft": + a = 2; + break; + case "3": + case "bottomright": + a = 3; + break; + } + } + else + { + x -= Emulator.CoreComm.ScreenLogicalOffsetX; + y -= Emulator.CoreComm.ScreenLogicalOffsetY; + } + + GlobalWin.OSD.AddGUIText(message, x, y, Color.Black, forecolor ?? Color.White, a); + } + } +} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/Api/Libraries/InputApi.cs b/BizHawk.Client.EmuHawk/Api/Libraries/InputApi.cs new file mode 100644 index 0000000000..56e2cd767f --- /dev/null +++ b/BizHawk.Client.EmuHawk/Api/Libraries/InputApi.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Windows.Forms; + +using BizHawk.Client.ApiHawk; +using BizHawk.Client.Common; + +namespace BizHawk.Client.EmuHawk +{ + public sealed class InputApi : IInput + { + public InputApi() : base() + { } + + public Dictionary Get() + { + var buttons = new Dictionary(); + foreach (var kvp in Global.ControllerInputCoalescer.BoolButtons().Where(kvp => kvp.Value)) + { + buttons[kvp.Key] = true; + } + + return buttons; + } + + public Dictionary GetMouse() + { + var buttons = new Dictionary(); + + // TODO - need to specify whether in "emu" or "native" coordinate space. + var p = GlobalWin.DisplayManager.UntransformPoint(Control.MousePosition); + buttons["X"] = p.X; + buttons["Y"] = p.Y; + buttons[MouseButtons.Left.ToString()] = (Control.MouseButtons & MouseButtons.Left) != 0; + buttons[MouseButtons.Middle.ToString()] = (Control.MouseButtons & MouseButtons.Middle) != 0; + buttons[MouseButtons.Right.ToString()] = (Control.MouseButtons & MouseButtons.Right) != 0; + buttons[MouseButtons.XButton1.ToString()] = (Control.MouseButtons & MouseButtons.XButton1) != 0; + buttons[MouseButtons.XButton2.ToString()] = (Control.MouseButtons & MouseButtons.XButton2) != 0; + buttons["Wheel"] = GlobalWin.MainForm.MouseWheelTracker; + return buttons; + } + } +} diff --git a/BizHawk.Client.EmuHawk/Api/Libraries/SaveStateAPI.cs b/BizHawk.Client.EmuHawk/Api/Libraries/SaveStateAPI.cs new file mode 100644 index 0000000000..df75742920 --- /dev/null +++ b/BizHawk.Client.EmuHawk/Api/Libraries/SaveStateAPI.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using BizHawk.Client.ApiHawk; + +namespace BizHawk.Client.EmuHawk +{ + public sealed class SaveStateApi : ISaveState + { + public SaveStateApi() : base() + { } + + public void Load(string path) + { + if (!File.Exists(path)) + { + Console.WriteLine($"could not find file: {path}"); + } + else + { + GlobalWin.MainForm.LoadState(path, Path.GetFileName(path), true); + } + } + + public void LoadSlot(int slotNum) + { + if (slotNum >= 0 && slotNum <= 9) + { + GlobalWin.MainForm.LoadQuickSave("QuickSave" + slotNum, true); + } + } + + public void Save(string path) + { + GlobalWin.MainForm.SaveState(path, path, true); + } + + public void SaveSlot(int slotNum) + { + if (slotNum >= 0 && slotNum <= 9) + { + GlobalWin.MainForm.SaveQuickSave("QuickSave" + slotNum); + } + } + } +} diff --git a/BizHawk.Client.EmuHawk/Api/Libraries/ToolApi.cs b/BizHawk.Client.EmuHawk/Api/Libraries/ToolApi.cs new file mode 100644 index 0000000000..d356761cdf --- /dev/null +++ b/BizHawk.Client.EmuHawk/Api/Libraries/ToolApi.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using BizHawk.Common; +using BizHawk.Emulation.Common; +using BizHawk.Client.ApiHawk; +using BizHawk.Client.Common; + +namespace BizHawk.Client.EmuHawk +{ + public sealed class ToolApi : ITool + { + private class ToolStatic + { + public Type GetTool(string name) + { + var toolType = ReflectionUtil.GetTypeByName(name) + .FirstOrDefault(x => typeof(IToolForm).IsAssignableFrom(x) && !x.IsInterface); + + if (toolType != null) + { + GlobalWin.Tools.Load(toolType); + } + + var selectedTool = GlobalWin.Tools.AvailableTools + .FirstOrDefault(tool => tool.GetType().Name.ToLower() == name.ToLower()); + + if (selectedTool != null) + { + return selectedTool; + } + + return null; + } + + public object CreateInstance(string name) + { + var possibleTypes = ReflectionUtil.GetTypeByName(name); + + if (possibleTypes.Any()) + { + return Activator.CreateInstance(possibleTypes.First()); + } + + return null; + } + + public static void OpenCheats() + { + GlobalWin.Tools.Load(); + } + + public static void OpenHexEditor() + { + GlobalWin.Tools.Load(); + } + + public static void OpenRamWatch() + { + GlobalWin.Tools.LoadRamWatch(loadDialog: true); + } + + public static void OpenRamSearch() + { + GlobalWin.Tools.Load(); + } + + public static void OpenTasStudio() + { + GlobalWin.Tools.Load(); + } + + public static void OpenToolBox() + { + GlobalWin.Tools.Load(); + } + + public static void OpenTraceLogger() + { + GlobalWin.Tools.Load(); + } + + } + [RequiredService] + private static IEmulator Emulator { get; set; } + + [RequiredService] + private static IVideoProvider VideoProvider { get; set; } + + public ToolApi() + { } + + public Type GetTool(string name) + { + var toolType = ReflectionUtil.GetTypeByName(name) + .FirstOrDefault(x => typeof(IToolForm).IsAssignableFrom(x) && !x.IsInterface); + + if (toolType != null) + { + GlobalWin.Tools.Load(toolType); + } + + var selectedTool = GlobalWin.Tools.AvailableTools + .FirstOrDefault(tool => tool.GetType().Name.ToLower() == name.ToLower()); + + if (selectedTool != null) + { + return selectedTool; + } + + return null; + } + + public object CreateInstance(string name) + { + var possibleTypes = ReflectionUtil.GetTypeByName(name); + + if (possibleTypes.Any()) + { + return Activator.CreateInstance(possibleTypes.First()); + } + + return null; + } + + public void OpenCheats() + { + ToolStatic.OpenCheats(); + } + + public void OpenHexEditor() + { + ToolStatic.OpenHexEditor(); + } + + public void OpenRamWatch() + { + ToolStatic.OpenRamWatch(); + } + + public void OpenRamSearch() + { + ToolStatic.OpenRamSearch(); + } + + public void OpenTasStudio() + { + ToolStatic.OpenTasStudio(); + } + + public void OpenToolBox() + { + ToolStatic.OpenToolBox(); + } + + public void OpenTraceLogger() + { + ToolStatic.OpenTraceLogger(); + } + } +} diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index 0a3bf56323..53c12c988e 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -661,6 +661,13 @@ + + + + + + + @@ -734,7 +741,6 @@ NameStateForm.cs - Form diff --git a/BizHawk.Client.EmuHawk/GlobalWin.cs b/BizHawk.Client.EmuHawk/GlobalWin.cs index 5df3f651bf..06a2bbaab5 100644 --- a/BizHawk.Client.EmuHawk/GlobalWin.cs +++ b/BizHawk.Client.EmuHawk/GlobalWin.cs @@ -1,4 +1,5 @@ using BizHawk.Bizware.BizwareGL; +using BizHawk.Client.ApiHawk; // ReSharper disable StyleCop.SA1401 namespace BizHawk.Client.EmuHawk @@ -7,6 +8,7 @@ namespace BizHawk.Client.EmuHawk { public static MainForm MainForm; public static ToolManager Tools; + public static BasicApiProvider ApiProvider; /// /// the IGL to be used for rendering diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index a3d9533159..09f671b765 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -3783,6 +3783,7 @@ namespace BizHawk.Client.EmuHawk } } + ApiManager.Restart(Emulator.ServiceProvider); GlobalWin.Tools.Restart(); if (Global.Config.LoadCheatFileByGame) @@ -3831,7 +3832,7 @@ namespace BizHawk.Client.EmuHawk } } - ClientApi.OnRomLoaded(); + ClientApi.OnRomLoaded(Emulator); return true; } else @@ -3842,10 +3843,11 @@ namespace BizHawk.Client.EmuHawk // The ROM has been loaded by a recursive invocation of the LoadROM method. if (!(Emulator is NullEmulator)) { - ClientApi.OnRomLoaded(); + ClientApi.OnRomLoaded(Emulator); return true; } + ClientApi.UpdateEmulatorAndVP(Emulator); HandlePlatformMenus(); _stateSlots.Clear(); UpdateStatusSlots(); @@ -3935,6 +3937,7 @@ namespace BizHawk.Client.EmuHawk var coreComm = CreateCoreComm(); CoreFileProvider.SyncCoreCommInputSignals(coreComm); Emulator = new NullEmulator(coreComm, Global.Config.GetCoreSettings()); + ClientApi.UpdateEmulatorAndVP(Emulator); Global.ActiveController = new Controller(NullController.Instance.Definition); Global.AutoFireController = _autofireNullControls; RewireSound(); @@ -3957,6 +3960,7 @@ namespace BizHawk.Client.EmuHawk Global.Game = GameInfo.NullInstance; GlobalWin.Tools.Restart(); + ApiManager.Restart(Emulator.ServiceProvider); RewireSound(); Text = "BizHawk" + (VersionInfo.DeveloperBuild ? " (interim) " : ""); HandlePlatformMenus(); diff --git a/BizHawk.Client.EmuHawk/tools/ToolBox.cs b/BizHawk.Client.EmuHawk/tools/ToolBox.cs index 503714c6b1..67aefdfd0f 100644 --- a/BizHawk.Client.EmuHawk/tools/ToolBox.cs +++ b/BizHawk.Client.EmuHawk/tools/ToolBox.cs @@ -6,7 +6,7 @@ using System.Reflection; using System.Windows.Forms; using BizHawk.Emulation.Common; -using BizHawk.Client.Common; +using BizHawk.Client.ApiHawk; namespace BizHawk.Client.EmuHawk { @@ -66,6 +66,8 @@ namespace BizHawk.Client.EmuHawk continue; if (!ServiceInjector.IsAvailable(Emulator.ServiceProvider, t)) continue; +// if (!ApiInjector.IsAvailable(, t)) +// continue; var instance = Activator.CreateInstance(t); diff --git a/BizHawk.Client.EmuHawk/tools/ToolManager.cs b/BizHawk.Client.EmuHawk/tools/ToolManager.cs index e26c0930ef..05128567ec 100644 --- a/BizHawk.Client.EmuHawk/tools/ToolManager.cs +++ b/BizHawk.Client.EmuHawk/tools/ToolManager.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.ComponentModel; using System.Windows.Forms; +using BizHawk.Client.ApiHawk; using BizHawk.Client.Common; using BizHawk.Client.EmuHawk; using BizHawk.Client.EmuHawk.CoreExtensions; @@ -123,6 +124,11 @@ namespace BizHawk.Client.EmuHawk (newTool as Form).Owner = GlobalWin.MainForm; } + if (isExternal) + { + ApiInjector.UpdateApis(GlobalWin.ApiProvider, newTool); + } + ServiceInjector.UpdateServices(Global.Emulator.ServiceProvider, newTool); string toolType = typeof(T).ToString(); @@ -493,6 +499,8 @@ namespace BizHawk.Client.EmuHawk if ((tool.IsHandleCreated && !tool.IsDisposed) || tool is RamWatch) // Hack for RAM Watch - in display watches mode it wants to keep running even closed, it will handle disposed logic { + if (tool is IExternalToolForm) + ApiInjector.UpdateApis(GlobalWin.ApiProvider, tool); tool.Restart(); } } diff --git a/BizHawk.Common/BizHawk.Common.csproj b/BizHawk.Common/BizHawk.Common.csproj index bc85c00225..876c145ad2 100644 --- a/BizHawk.Common/BizHawk.Common.csproj +++ b/BizHawk.Common/BizHawk.Common.csproj @@ -110,4 +110,4 @@ --> - + \ No newline at end of file diff --git a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj index 0a8aa6ec09..7287faab8b 100644 --- a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj +++ b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj @@ -121,6 +121,10 @@ + + + + diff --git a/BizHawk.Emulation.Common/WorkingTypes/wbyte.cs b/BizHawk.Emulation.Common/WorkingTypes/wbyte.cs new file mode 100644 index 0000000000..621ea82131 --- /dev/null +++ b/BizHawk.Emulation.Common/WorkingTypes/wbyte.cs @@ -0,0 +1,116 @@ +using System; +using System.Globalization; +using System.Security; + + +namespace BizHawk.Emulation.Common.WorkingTypes +{ + // + // Summary: + // Represents an 8-bit unsigned integer, that is capable of arithmetic without making you weep. + // Also provides all the base functionality of the standard C# Byte by calling its methods where relevant. + public unsafe class wbyte : IComparable, IFormattable, IComparable, IEquatable + { + private Byte val; + public const Byte MaxValue = Byte.MaxValue; + public const Byte MinValue = Byte.MinValue; + public static implicit operator wbyte(ulong value) + { + return new wbyte(value); + } + public static implicit operator wbyte(wushort value) + { + return new wbyte(value); + } + public static implicit operator byte(wbyte value) + { + return value.val; + } + public wbyte() + { + + } + public wbyte(ulong value) + { + val = (Byte)(value & 0xFF); + } + public wbyte(long value) + { + val = (Byte)(value & 0xFF); + } + public wbyte(double value) + { + val = (Byte)(((long)value) & 0xFF); + } + public static wbyte Parse(string s, NumberStyles style, IFormatProvider provider) + { + return (ulong)Byte.Parse(s, style, provider); + } + public static wbyte Parse(string s, IFormatProvider provider) + { + return (ulong)Byte.Parse(s, provider); + } + public static wbyte Parse(string s) + { + return (ulong)Byte.Parse(s); + } + public static wbyte Parse(string s, NumberStyles style) + { + return (ulong)Byte.Parse(s, style); + } + public static bool TryParse(string s, out wbyte result) + { + result = new wbyte(); + return byte.TryParse(s, out result.val); + } + public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out wbyte result) + { + result = new wbyte(); + return byte.TryParse(s, style, provider, out result.val); + } + public int CompareTo(wbyte value) + { + return val.CompareTo(value.val); + } + public int CompareTo(object value) + { + return val.CompareTo(value); + } + public override bool Equals(object obj) + { + return val.Equals(obj); + } + public bool Equals(wbyte obj) + { + return val.Equals(obj); + } + public override int GetHashCode() + { + return val.GetHashCode(); + } + public TypeCode GetTypeCode() + { + return val.GetTypeCode(); + } + [SecuritySafeCritical] + public string ToString(string format, IFormatProvider provider) + { + return val.ToString(format, provider); + } + [SecuritySafeCritical] + public override string ToString() + { + return val.ToString(); + } + [SecuritySafeCritical] + public string ToString(string format) + { + return val.ToString(format); + } + [SecuritySafeCritical] + public string ToString(IFormatProvider provider) + { + return val.ToString(provider); + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation.Common/WorkingTypes/wsbyte.cs b/BizHawk.Emulation.Common/WorkingTypes/wsbyte.cs new file mode 100644 index 0000000000..187cf46c39 --- /dev/null +++ b/BizHawk.Emulation.Common/WorkingTypes/wsbyte.cs @@ -0,0 +1,111 @@ +using System; +using System.Globalization; +using System.Security; + +namespace BizHawk.Emulation.Common.WorkingTypes +{ + // + // Summary: + // Represents an 8-bit unsigned integer, that is capable of arithmetic without making you weep. + // Also provides all the base functionality of the standard C# SByte by calling its methods where relevant. + public unsafe class wsbyte : IComparable, IFormattable, IComparable, IEquatable + { + private SByte val; + public const SByte MaxValue = SByte.MaxValue; + public const SByte MinValue = SByte.MinValue; + public static implicit operator wsbyte(long value) + { + return new wsbyte(value); + } + public static implicit operator SByte(wsbyte value) + { + return value.val; + } + public wsbyte() + { + + } + public wsbyte(long value) + { + val = (SByte)(value & 0xFF); + } + public wsbyte(ulong value) + { + val = (SByte)(value & 0xFF); + } + public wsbyte(double value) + { + val = (SByte)(((ulong)value) & 0xFF); + } + public static wsbyte Parse(string s, NumberStyles style, IFormatProvider provider) + { + return (long)SByte.Parse(s, style, provider); + } + public static wsbyte Parse(string s, IFormatProvider provider) + { + return (long)SByte.Parse(s, provider); + } + public static wsbyte Parse(string s) + { + return (long)SByte.Parse(s); + } + public static wsbyte Parse(string s, NumberStyles style) + { + return (long)SByte.Parse(s, style); + } + public static bool TryParse(string s, out wsbyte result) + { + result = new wsbyte(); + return SByte.TryParse(s, out result.val); + } + public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out wsbyte result) + { + result = new wsbyte(); + return SByte.TryParse(s, style, provider, out result.val); + } + public int CompareTo(wsbyte value) + { + return val.CompareTo(value.val); + } + public int CompareTo(object value) + { + return val.CompareTo(value); + } + public override bool Equals(object obj) + { + return val.Equals(obj); + } + public bool Equals(wsbyte obj) + { + return val.Equals(obj); + } + public override int GetHashCode() + { + return val.GetHashCode(); + } + public TypeCode GetTypeCode() + { + return val.GetTypeCode(); + } + [SecuritySafeCritical] + public string ToString(string format, IFormatProvider provider) + { + return val.ToString(format, provider); + } + [SecuritySafeCritical] + public override string ToString() + { + return val.ToString(); + } + [SecuritySafeCritical] + public string ToString(string format) + { + return val.ToString(format); + } + [SecuritySafeCritical] + public string ToString(IFormatProvider provider) + { + return val.ToString(provider); + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation.Common/WorkingTypes/wshort.cs b/BizHawk.Emulation.Common/WorkingTypes/wshort.cs new file mode 100644 index 0000000000..aa66438db3 --- /dev/null +++ b/BizHawk.Emulation.Common/WorkingTypes/wshort.cs @@ -0,0 +1,111 @@ +using System; +using System.Globalization; +using System.Security; + +namespace BizHawk.Emulation.Common.WorkingTypes +{ + // + // Summary: + // Represents an 16-bit unsigned integer, that is capable of arithmetic without making you weep. + // Also provides all the base functionality of the standard C# Int16 by calling its methods where relevant. + public unsafe class wshort : IComparable, IFormattable, IComparable, IEquatable + { + private Int16 val; + public const Int16 MaxValue = Int16.MaxValue; + public const Int16 MinValue = Int16.MinValue; + public static implicit operator wshort(long value) + { + return new wshort(value); + } + public static implicit operator Int16(wshort value) + { + return value.val; + } + public wshort() + { + + } + public wshort(long value) + { + val = (Int16)(value & 0xFFFF); + } + public wshort(ulong value) + { + val = (Int16)(value & 0xFFFF); + } + public wshort(double value) + { + val = (Int16)(((ulong)value) & 0xFFFF); + } + public static wshort Parse(string s, NumberStyles style, IFormatProvider provider) + { + return (long)Int16.Parse(s, style, provider); + } + public static wshort Parse(string s, IFormatProvider provider) + { + return (long)Int16.Parse(s, provider); + } + public static wshort Parse(string s) + { + return (long)Int16.Parse(s); + } + public static wshort Parse(string s, NumberStyles style) + { + return (long)Int16.Parse(s, style); + } + public static bool TryParse(string s, out wshort result) + { + result = new wshort(); + return Int16.TryParse(s, out result.val); + } + public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out wshort result) + { + result = new wshort(); + return Int16.TryParse(s, style, provider, out result.val); + } + public int CompareTo(wshort value) + { + return val.CompareTo(value.val); + } + public int CompareTo(object value) + { + return val.CompareTo(value); + } + public override bool Equals(object obj) + { + return val.Equals(obj); + } + public bool Equals(wshort obj) + { + return val.Equals(obj); + } + public override int GetHashCode() + { + return val.GetHashCode(); + } + public TypeCode GetTypeCode() + { + return val.GetTypeCode(); + } + [SecuritySafeCritical] + public string ToString(string format, IFormatProvider provider) + { + return val.ToString(format, provider); + } + [SecuritySafeCritical] + public override string ToString() + { + return val.ToString(); + } + [SecuritySafeCritical] + public string ToString(string format) + { + return val.ToString(format); + } + [SecuritySafeCritical] + public string ToString(IFormatProvider provider) + { + return val.ToString(provider); + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation.Common/WorkingTypes/wushort.cs b/BizHawk.Emulation.Common/WorkingTypes/wushort.cs new file mode 100644 index 0000000000..427a414c6f --- /dev/null +++ b/BizHawk.Emulation.Common/WorkingTypes/wushort.cs @@ -0,0 +1,116 @@ +using System; +using System.Globalization; +using System.Security; + + +namespace BizHawk.Emulation.Common.WorkingTypes +{ + // + // Summary: + // Represents an 16-bit unsigned integer, that is capable of arithmetic without making you weep. + // Also provides all the base functionality of the standard C# UInt16 by calling its methods where relevant. + public unsafe class wushort : IComparable, IFormattable, IComparable, IEquatable + { + private UInt16 val; + public const UInt16 MaxValue = UInt16.MaxValue; + public const UInt16 MinValue = UInt16.MinValue; + public static implicit operator wushort(ulong value) + { + return new wushort(value); + } + public static implicit operator wushort(wbyte value) + { + return new wushort(value); + } + public static implicit operator UInt16(wushort value) + { + return value.val; + } + public wushort() + { + + } + public wushort(ulong value) + { + val = (UInt16)(value & 0xFFFF); + } + public wushort(long value) + { + val = (UInt16)(value & 0xFFFF); + } + public wushort(double value) + { + val = (UInt16)(((long)value) & 0xFFFF); + } + public static wushort Parse(string s, NumberStyles style, IFormatProvider provider) + { + return (uint)UInt16.Parse(s, style, provider); + } + public static wushort Parse(string s, IFormatProvider provider) + { + return (uint)UInt16.Parse(s, provider); + } + public static wushort Parse(string s) + { + return (uint)UInt16.Parse(s); + } + public static wushort Parse(string s, NumberStyles style) + { + return (uint)UInt16.Parse(s, style); + } + public static bool TryParse(string s, out wushort result) + { + result = new wushort(); + return ushort.TryParse(s, out result.val); + } + public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out wushort result) + { + result = new wushort(); + return ushort.TryParse(s, style, provider, out result.val); + } + public int CompareTo(wushort value) + { + return val.CompareTo(value.val); + } + public int CompareTo(object value) + { + return val.CompareTo(value); + } + public override bool Equals(object obj) + { + return val.Equals(obj); + } + public bool Equals(wushort obj) + { + return val.Equals(obj); + } + public override int GetHashCode() + { + return val.GetHashCode(); + } + public TypeCode GetTypeCode() + { + return val.GetTypeCode(); + } + [SecuritySafeCritical] + public string ToString(string format, IFormatProvider provider) + { + return val.ToString(format, provider); + } + [SecuritySafeCritical] + public override string ToString() + { + return val.ToString(); + } + [SecuritySafeCritical] + public string ToString(string format) + { + return val.ToString(format); + } + [SecuritySafeCritical] + public string ToString(IFormatProvider provider) + { + return val.ToString(provider); + } + } +} \ No newline at end of file diff --git a/BizHawk.sln b/BizHawk.sln index 7f33fcf4e2..7db07c23e3 100644 --- a/BizHawk.sln +++ b/BizHawk.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2047 MinimumVisualStudioVersion = 12.0.31101.0 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Version", "Version\Version.csproj", "{0CE8B337-08E3-4602-BF10-C4D4C75D2F13}" EndProject @@ -264,6 +264,9 @@ Global {B95649F5-A0AE-41EB-B62B-578A2AFF5E18} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA} {8E2F11F2-3955-4382-8C3A-CEBA1276CAEA} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1A77376C-2741-489C-90E1-03E415910B65} + EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = BizHawk.Client.EmuHawk\BizHawk.Client.EmuHawk.csproj EndGlobalSection diff --git a/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/IGL_TK.cs b/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/IGL_TK.cs index 349bc67819..7a8f02bc9d 100644 --- a/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/IGL_TK.cs +++ b/Bizware/BizHawk.Bizware.BizwareGL.OpenTK/IGL_TK.cs @@ -860,7 +860,7 @@ namespace BizHawk.Bizware.BizwareGL.Drivers.OpenTK _rsBlendNormal = new CacheBlendState( true, BlendingFactorSrc.SrcAlpha, BlendEquationMode.FuncAdd, BlendingFactorDest.OneMinusSrcAlpha, - BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero); + BlendingFactorSrc.One, BlendEquationMode.Max, BlendingFactorDest.One); } CacheBlendState _rsBlendNoneVerbatim, _rsBlendNoneOpaque, _rsBlendNormal; diff --git a/Bizware/BizHawk.Bizware.BizwareGL.SlimDX/IGL_SlimDX9.cs b/Bizware/BizHawk.Bizware.BizwareGL.SlimDX/IGL_SlimDX9.cs index e437875303..5d97323759 100644 --- a/Bizware/BizHawk.Bizware.BizwareGL.SlimDX/IGL_SlimDX9.cs +++ b/Bizware/BizHawk.Bizware.BizwareGL.SlimDX/IGL_SlimDX9.cs @@ -368,7 +368,7 @@ namespace BizHawk.Bizware.BizwareGL.Drivers.SlimDX _rsBlendNormal = new CacheBlendState( true, gl.BlendingFactorSrc.SrcAlpha, gl.BlendEquationMode.FuncAdd, gl.BlendingFactorDest.OneMinusSrcAlpha, - gl.BlendingFactorSrc.One, gl.BlendEquationMode.FuncAdd, gl.BlendingFactorDest.Zero); + gl.BlendingFactorSrc.One, gl.BlendEquationMode.Max, gl.BlendingFactorDest.One); } CacheBlendState _rsBlendNoneVerbatim, _rsBlendNoneOpaque, _rsBlendNormal; diff --git a/Bizware/BizHawk.Bizware.BizwareGL/BitmapBuffer.cs b/Bizware/BizHawk.Bizware.BizwareGL/BitmapBuffer.cs index df5fc3591a..6bfb577f7b 100644 --- a/Bizware/BizHawk.Bizware.BizwareGL/BitmapBuffer.cs +++ b/Bizware/BizHawk.Bizware.BizwareGL/BitmapBuffer.cs @@ -287,7 +287,7 @@ namespace BizHawk.Bizware.BizwareGL /// public void DiscardAlpha() { - HasAlpha = false; + //HasAlpha = false; } void LoadInternal(Stream stream, sd.Bitmap bitmap, BitmapLoadOptions options) diff --git a/libmupen64plus/GLideN64 b/libmupen64plus/GLideN64 index 7432d1f408..57ba873c5e 160000 --- a/libmupen64plus/GLideN64 +++ b/libmupen64plus/GLideN64 @@ -1 +1 @@ -Subproject commit 7432d1f40808b22d3ef7e403cf7ae45b1061dcd7 +Subproject commit 57ba873c5e117a42f94299cb7ddaa1066249b416