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.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 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"); } #endregion #region Methods #region Public /// /// THE FrameAdvance stuff /// public static void DoFrameAdvance() { MethodInfo method = mainFormClass.GetMethod("FrameAdvance"); method.Invoke(clientMainFormInstance, null); method = mainFormClass.GetMethod("StepRunLoop_Throttle", BindingFlags.NonPublic | BindingFlags.Instance); method.Invoke(clientMainFormInstance, null); method = mainFormClass.GetMethod("Render", BindingFlags.NonPublic | BindingFlags.Instance); method.Invoke(clientMainFormInstance, 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(string.Format("{0} does not support {1} controller(s)", RunningSystem.DisplayName, player)); } else { GetAllInputs(); return allJoypads[player - 1]; } } /// /// Load a savestate specified by its name /// /// 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 }); } /// /// 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() { if (RomLoaded != null) { RomLoaded(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) { MethodInfo method = mainFormClass.GetMethod("SaveState"); method.Invoke(clientMainFormInstance, 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 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)); MethodInfo resize = mainFormClass.GetMethod("FrameBufferResized"); resize.Invoke(clientMainFormInstance, null); } /// /// 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(string.Format("{0} does not support {1} controller(s)", RunningSystem.DisplayName, player)); } 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(string.Format("{0}", JoypadConverter.ConvertBack(button, RunningSystem)), true); } else { joypadAdaptor.SetSticky(string.Format("P{0} {1}", 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(string.Format("P{0} X Axis", i), allJoypads[i - 1].AnalogX); joypadAdaptor.SetFloat(string.Format("P{0} Y Axis", i), allJoypads[i - 1].AnalogY); } }*/ } } /// /// Resume the emulation /// public static void UnpauseEmulation() { MethodInfo method = mainFormClass.GetMethod("UnpauseEmulator"); method.Invoke(clientMainFormInstance, 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(string.Format("P{0} X Axis", i)); allJoypads[i - 1].AnalogY = joypadAdaptor.GetFloat(string.Format("P{0} Y Axis", i)); } } } #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 } }