using System; using System.Collections.Generic; using System.Linq; 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; using BizHawk.Client.ApiHawk.Classes.Events; using System.IO; namespace BizHawk.Client.ApiHawk { /// /// This class contains some methods that /// interract with BizHawk client /// public static class ClientApi { #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; private static readonly Array joypadButtonsArray = Enum.GetValues(typeof(JoypadButton)); internal static readonly BizHawkSystemIdToEnumConverter SystemIdConverter = new BizHawkSystemIdToEnumConverter(); internal static readonly JoypadStringToEnumConverter JoypadConverter = new JoypadStringToEnumConverter(); private static List allJoypads; /// /// Occurs before a quickload is done (just after user has pressed the shortcut button /// or has click on the item menu) /// public static event BeforeQuickLoadEventHandler BeforeQuickLoad; /// /// Occurs before a quicksave is done (just after user has pressed the shortcut button /// or has click on the item menu) /// public static event BeforeQuickSaveEventHandler BeforeQuickSave; /// /// Occurs when a ROM is succesfully loaded /// public static event EventHandler RomLoaded; /// /// Occurs when a savestate is sucessfully loaded /// public static event StateLoadedEventHandler StateLoaded; /// /// Occurs when a savestate is successfully saved /// public static event StateSavedEventHandler StateSaved; #endregion #region cTor(s) /// /// Static stuff initilization /// static ClientApi() { clientAssembly = Assembly.GetEntryAssembly(); clientMainFormInstance = clientAssembly.GetType("BizHawk.Client.EmuHawk.GlobalWin").GetField("MainForm").GetValue(null); 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); if(method != null) 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() { InvokeMainFormMethod("FrameAdvance", null); InvokeMainFormMethod("StepRunLoop_Throttle", null); InvokeMainFormMethod("Render", null); } /// /// THE FrameAdvance stuff /// Auto unpause emulation /// public static void DoFrameAdvanceAndUnpause() { DoFrameAdvance(); UnpauseEmulation(); } /// /// Gets a for specified player /// /// Player (one based) you want current inputs /// A populated with current inputs /// Raised when you specify a player less than 1 or greater than maximum allows (see SystemInfo class to get this information) public static Joypad GetInput(int player) { if (player < 1 || player > RunningSystem.MaxControllers) { throw new IndexOutOfRangeException($"{RunningSystem.DisplayName} does not support {player} controller(s)"); } else { GetAllInputs(); return allJoypads[player - 1]; } } /// /// Load a savestate specified by its name /// /// Savetate friendly name public static void LoadState(string name) { InvokeMainFormMethod("LoadState", new object[] { Path.Combine(PathManager.GetSaveStatePath(Global.Game), $"{name}.{"State"}"), name, false, false }); } /// /// Raised before a quickload is done (just after pressing shortcut button) /// /// Object who raised the event /// Slot used for quickload /// A boolean that can be set if users want to handle save themselves; if so, BizHawk won't do anything public static void OnBeforeQuickLoad(object sender, string quickSaveSlotName, out bool eventHandled) { eventHandled = false; if (BeforeQuickLoad != null) { BeforeQuickLoadEventArgs e = new BeforeQuickLoadEventArgs(quickSaveSlotName); BeforeQuickLoad(sender, e); eventHandled = e.Handled; } } /// /// Raised before a quicksave is done (just after pressing shortcut button) /// /// Object who raised the event /// Slot used for quicksave /// A boolean that can be set if users want to handle save themselves; if so, BizHawk won't do anything public static void OnBeforeQuickSave(object sender, string quickSaveSlotName, out bool eventHandled) { eventHandled = false; if (BeforeQuickSave != null) { BeforeQuickSaveEventArgs e = new BeforeQuickSaveEventArgs(quickSaveSlotName); BeforeQuickSave(sender, e); eventHandled = e.Handled; } } /// /// Raise when a state is loaded /// /// Object who raised the event /// User friendly name for saved state public static void OnStateLoaded(object sender, string stateName) { if (StateLoaded != null) { StateLoaded(sender, new StateLoadedEventArgs(stateName)); } } /// /// Raise when a state is saved /// /// Object who raised the event /// User friendly name for saved state public static void OnStateSaved(object sender, string stateName) { if (StateSaved != null) { StateSaved(sender, new StateSavedEventArgs(stateName)); } } /// /// Raise when a rom is successfully Loaded /// public static void OnRomLoaded(IEmulator emu) { 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++) { allJoypads.Add(new Joypad(RunningSystem, i)); } } /// /// Save a state with specified name /// /// Savetate friendly name public static void SaveState(string name) { InvokeMainFormMethod("SaveState", new object[] { Path.Combine(PathManager.GetSaveStatePath(Global.Game), $"{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 /// /// Left padding /// Top padding /// Right padding /// Bottom padding public static void SetExtraPadding(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("ClientExtraPadding"); 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 SetExtraPadding(int left) { SetExtraPadding(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 SetExtraPadding(int left, int top) { SetExtraPadding(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 SetExtraPadding(int left, int top, int right) { SetExtraPadding(left, top, right, 0); } /// /// Set inputs in specified to specified player /// /// Player (one based) whom inputs must be set /// with inputs /// Raised when you specify a player less than 1 or greater than maximum allows (see SystemInfo class to get this information) /// Still have some strange behaviour with multiple inputs; so this feature is still in beta public static void SetInput(int player, Joypad joypad) { if (player < 1 || player > RunningSystem.MaxControllers) { throw new IndexOutOfRangeException($"{RunningSystem.DisplayName} does not support {player} controller(s)"); } else { if (joypad.Inputs == 0) { AutoFireStickyXorAdapter joypadAdaptor = Global.AutofireStickyXORAdapter; joypadAdaptor.ClearStickies(); } else { foreach (JoypadButton button in joypadButtonsArray) { if (joypad.Inputs.HasFlag(button)) { AutoFireStickyXorAdapter joypadAdaptor = Global.AutofireStickyXORAdapter; if (RunningSystem == SystemInfo.GB) { joypadAdaptor.SetSticky($"{JoypadConverter.ConvertBack(button, RunningSystem)}", true); } else { joypadAdaptor.SetSticky($"P{player} {JoypadConverter.ConvertBack(button, RunningSystem)}", true); } } } } //Using this break joypad usage (even in UI); have to figure out why /*if ((RunningSystem.AvailableButtons & JoypadButton.AnalogStick) == JoypadButton.AnalogStick) { AutoFireStickyXorAdapter joypadAdaptor = Global.AutofireStickyXORAdapter; for (int i = 1; i <= RunningSystem.MaxControllers; i++) { joypadAdaptor.SetFloat($"P{i} X Axis", allJoypads[i - 1].AnalogX); joypadAdaptor.SetFloat($"P{i} Y Axis", allJoypads[i - 1].AnalogY); } }*/ } } /// /// Resume the emulation /// public static void UnpauseEmulation() { InvokeMainFormMethod("UnpauseEmulator", null); } #endregion Public /// /// Gets all current inputs for each joypad and store /// them in class collection /// private static void GetAllInputs() { AutoFireStickyXorAdapter joypadAdaptor = Global.AutofireStickyXORAdapter; IEnumerable pressedButtons = from button in joypadAdaptor.Definition.BoolButtons where joypadAdaptor.IsPressed(button) select button; foreach (Joypad j in allJoypads) { j.ClearInputs(); } Parallel.ForEach(pressedButtons, button => { int player; if (RunningSystem == SystemInfo.GB) { allJoypads[0].AddInput(JoypadConverter.Convert(button)); } else { if (int.TryParse(button.Substring(1, 2), out player)) { allJoypads[player - 1].AddInput(JoypadConverter.Convert(button.Substring(3))); } } }); if ((RunningSystem.AvailableButtons & JoypadButton.AnalogStick) == JoypadButton.AnalogStick) { for (int i = 1; i <= RunningSystem.MaxControllers; i++) { allJoypads[i - 1].AnalogX = joypadAdaptor.GetFloat($"P{i} X Axis"); allJoypads[i - 1].AnalogY = joypadAdaptor.GetFloat($"P{i} Y Axis"); } } } 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 /// /// Gets current emulated system /// public static SystemInfo RunningSystem { get { switch (Global.Emulator.SystemId) { case "PCE": if (((PCEngine)Global.Emulator).Type == NecSystemType.TurboGrafx) { return SystemInfo.PCE; } else if (((PCEngine)Global.Emulator).Type == NecSystemType.SuperGrafx) { return SystemInfo.SGX; } else { return SystemInfo.PCECD; } case "SMS": if (((SMS)Global.Emulator).IsSG1000) { return SystemInfo.SG; } else if (((SMS)Global.Emulator).IsGameGear) { return SystemInfo.GG; } else { return SystemInfo.SMS; } case "GB": if (Global.Emulator is Gameboy) { return SystemInfo.GB; } else if (Global.Emulator is GBColors) { return SystemInfo.GBC; } else { return SystemInfo.DualGB; } default: return SystemInfo.FindByCoreSystem(SystemIdConverter.Convert(Global.Emulator.SystemId)); } } } #endregion } }