diff --git a/src/BizHawk.Client.Common/Api/Interfaces/IEmuClient.cs b/src/BizHawk.Client.Common/Api/Interfaces/IEmuClient.cs new file mode 100644 index 0000000000..b3fa3101aa --- /dev/null +++ b/src/BizHawk.Client.Common/Api/Interfaces/IEmuClient.cs @@ -0,0 +1,225 @@ +using System; +using System.Drawing; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.Common +{ + public interface IEmuClient + { + SystemInfo RunningSystem { get; } + + /// + /// Occurs before a quickload is done (just after user has pressed the shortcut button + /// or has click on the item menu) + /// + event BeforeQuickLoadEventHandler BeforeQuickLoad; + + /// + /// Occurs before a quicksave is done (just after user has pressed the shortcut button + /// or has click on the item menu) + /// + event BeforeQuickSaveEventHandler BeforeQuickSave; + + /// + /// Occurs when a ROM is successfully loaded + /// + event EventHandler RomLoaded; + + /// + /// Occurs when a savestate is successfully loaded + /// + event StateLoadedEventHandler StateLoaded; + + /// + /// Occurs when a savestate is successfully saved + /// + event StateSavedEventHandler StateSaved; + + int BorderHeight(); + + int BorderWidth(); + + int BufferHeight(); + + int BufferWidth(); + + void ClearAutohold(); + + void CloseEmulator(); + + void CloseEmulatorWithCode(int exitCode); + + void CloseRom(); + + void DisplayMessages(bool value); + + /// + /// THE FrameAdvance stuff + /// + void DoFrameAdvance(); + + /// + /// THE FrameAdvance stuff + /// Auto unpause emulation + /// + void DoFrameAdvanceAndUnpause(); + + void EnableRewind(bool enabled); + + void FrameSkip(int numFrames); + + /// + /// 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) + Joypad GetInput(int player); + + bool GetSoundOn(); + + int GetTargetScanlineIntensity(); + + int GetWindowSize(); + + /// + /// Use with for CamHack. + /// Refer to MainForm.InvisibleEmulation for the workflow details. + /// + void InvisibleEmulation(bool invisible); + + bool IsPaused(); + + bool IsSeeking(); + + bool IsTurbo(); + + /// + /// Load a savestate specified by its name + /// + /// Savestate friendly name + void LoadState(string name); + + /// + /// 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 + void OnBeforeQuickLoad(object sender, string quickSaveSlotName, out bool eventHandled); + + /// + /// 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 + void OnBeforeQuickSave(object sender, string quickSaveSlotName, out bool eventHandled); + + /// + /// Raise when a rom is successfully Loaded + /// + void OnRomLoaded(IEmulator emu); + + /// + /// Raise when a state is loaded + /// + /// Object who raised the event + /// User friendly name for saved state + void OnStateLoaded(object sender, string stateName); + + /// + /// Raise when a state is saved + /// + /// Object who raised the event + /// User friendly name for saved state + void OnStateSaved(object sender, string stateName); + + void OpenRom(string path); + + void Pause(); + + void PauseAv(); + + void RebootCore(); + + void SaveRam(); + + /// + /// Save a state with specified name + /// + /// Savestate friendly name + void SaveState(string name); + + int ScreenHeight(); + + void Screenshot(string path = null); + + void ScreenshotToClipboard(); + + int ScreenWidth(); + + /// + /// Use with for CamHack. + /// Refer to MainForm.InvisibleEmulation for the workflow details. + /// + void SeekFrame(int frame); + + /// + /// 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 + void SetExtraPadding(int left, int top = 0, int right = 0, int bottom = 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 + void SetGameExtraPadding(int left, int top = 0, int right = 0, int bottom = 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 + void SetInput(int player, Joypad joypad); + + void SetScreenshotOSD(bool value); + + void SetSoundOn(bool enable); + + void SetTargetScanlineIntensity(int val); + + void SetWindowSize(int size); + + void SpeedMode(int percent); + + void TogglePause(); + + Point TransformPoint(Point point); + + void Unpause(); + + void UnpauseAv(); + + /// + /// Resume the emulation + /// + void UnpauseEmulation(); + + void UpdateEmulatorAndVP(IEmulator emu = null); + + int Xpos(); + + int Ypos(); + } +} diff --git a/src/BizHawk.Client.EmuHawk/Api/Joypad.cs b/src/BizHawk.Client.Common/Api/Joypad.cs similarity index 95% rename from src/BizHawk.Client.EmuHawk/Api/Joypad.cs rename to src/BizHawk.Client.Common/Api/Joypad.cs index a2f2fe58de..79d1eaa680 100644 --- a/src/BizHawk.Client.EmuHawk/Api/Joypad.cs +++ b/src/BizHawk.Client.Common/Api/Joypad.cs @@ -1,8 +1,8 @@ using System; -using BizHawk.Client.Common; + using BizHawk.Common; -namespace BizHawk.Client.EmuHawk +namespace BizHawk.Client.Common { /// /// This class holds a joypad for any type of console @@ -20,7 +20,7 @@ namespace BizHawk.Client.EmuHawk /// What this is used for /// Which player this controller is assigned to /// not in range 1..max where max is . - internal Joypad(SystemInfo system, int player) + public Joypad(SystemInfo system, int player) { if (!1.RangeTo(system.MaxControllers).Contains(player)) { diff --git a/src/BizHawk.Client.EmuHawk/Api/EventTypes.cs b/src/BizHawk.Client.Common/EventTypes.cs similarity index 93% rename from src/BizHawk.Client.EmuHawk/Api/EventTypes.cs rename to src/BizHawk.Client.Common/EventTypes.cs index bbc9c82175..c9b4bfae8f 100644 --- a/src/BizHawk.Client.EmuHawk/Api/EventTypes.cs +++ b/src/BizHawk.Client.Common/EventTypes.cs @@ -1,13 +1,13 @@ using System; -namespace BizHawk.Client.EmuHawk +namespace BizHawk.Client.Common { /// /// This class holds event data for BeforeQuickLoad event /// public sealed class BeforeQuickLoadEventArgs : EventArgs { - internal BeforeQuickLoadEventArgs(string name) + public BeforeQuickLoadEventArgs(string name) { Name = name; } @@ -35,7 +35,7 @@ namespace BizHawk.Client.EmuHawk /// public sealed class BeforeQuickSaveEventArgs : EventArgs { - internal BeforeQuickSaveEventArgs(string name) + public BeforeQuickSaveEventArgs(string name) { Name = name; } @@ -66,7 +66,7 @@ namespace BizHawk.Client.EmuHawk /// Initialize a new instance of /// /// User friendly name of loaded state - internal StateLoadedEventArgs(string stateName) + public StateLoadedEventArgs(string stateName) { Name = stateName; } @@ -86,7 +86,7 @@ namespace BizHawk.Client.EmuHawk /// Initialize a new instance of /// /// User friendly name of loaded state - internal StateSavedEventArgs(string stateName) + public StateSavedEventArgs(string stateName) { Name = stateName; } diff --git a/src/BizHawk.Client.EmuHawk/Api/ClientApi.cs b/src/BizHawk.Client.EmuHawk/Api/ClientApi.cs index 1370f26c1a..7aed212f74 100644 --- a/src/BizHawk.Client.EmuHawk/Api/ClientApi.cs +++ b/src/BizHawk.Client.EmuHawk/Api/ClientApi.cs @@ -1,449 +1,211 @@ using System; -using System.Collections.Generic; using System.Drawing; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; using BizHawk.Client.Common; -using BizHawk.Common; using BizHawk.Emulation.Common; -using BizHawk.Emulation.Cores.Nintendo.Gameboy; -using BizHawk.Emulation.Cores.PCEngine; -using BizHawk.Emulation.Cores.Sega.MasterSystem; -// ReSharper disable UnusedMember.Global namespace BizHawk.Client.EmuHawk { - /// - /// This class contains some methods that - /// interact with BizHawk client - /// public static class ClientApi { - private static IEmulator Emulator { get; set; } + /// + public static SystemInfo RunningSystem => GlobalWin.ClientApi.RunningSystem; - private static IVideoProvider VideoProvider { get; set; } - - private static readonly IReadOnlyCollection JoypadButtonsArray = Enum.GetValues(typeof(JoypadButton)).Cast().ToList(); //TODO can the return of GetValues be cast to JoypadButton[]? --yoshi - - internal static readonly BizHawkSystemIdToEnumConverter SystemIdConverter = new BizHawkSystemIdToEnumConverter(); - - private 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 successfully loaded - /// - public static event EventHandler RomLoaded; - - /// - /// Occurs when a savestate is successfully loaded - /// - public static event StateLoadedEventHandler StateLoaded; - - /// - /// Occurs when a savestate is successfully saved - /// - public static event StateSavedEventHandler StateSaved; - - public static void UpdateEmulatorAndVP(IEmulator emu = null) + /// + public static event BeforeQuickLoadEventHandler BeforeQuickLoad { - Emulator = emu; - VideoProvider = emu.AsVideoProviderOrDefault(); + add => GlobalWin.ClientApi.BeforeQuickLoad += value; + remove => GlobalWin.ClientApi.BeforeQuickLoad -= value; } - /// - /// THE FrameAdvance stuff - /// - public static void DoFrameAdvance() + /// + public static event BeforeQuickSaveEventHandler BeforeQuickSave { - GlobalWin.MainForm.FrameAdvance(); - GlobalWin.MainForm.StepRunLoop_Throttle(); - GlobalWin.MainForm.Render(); + add => GlobalWin.ClientApi.BeforeQuickSave += value; + remove => GlobalWin.ClientApi.BeforeQuickSave -= value; } - /// - /// THE FrameAdvance stuff - /// Auto unpause emulation - /// - public static void DoFrameAdvanceAndUnpause() + /// + public static event EventHandler RomLoaded { - DoFrameAdvance(); - UnpauseEmulation(); + add => GlobalWin.ClientApi.RomLoaded += value; + remove => GlobalWin.ClientApi.RomLoaded -= value; } - /// - /// Use with for CamHack. - /// Refer to for the workflow details. - /// - public static void SeekFrame(int frame) + /// + public static event StateLoadedEventHandler StateLoaded { - var wasPaused = GlobalWin.MainForm.EmulatorPaused; - while (Emulator.Frame != frame) GlobalWin.MainForm.SeekFrameAdvance(); - if (!wasPaused) GlobalWin.MainForm.UnpauseEmulator(); + add => GlobalWin.ClientApi.StateLoaded += value; + remove => GlobalWin.ClientApi.StateLoaded -= value; } - /// - /// Use with for CamHack. - /// Refer to for the workflow details. - /// - public static void InvisibleEmulation(bool invisible) => GlobalWin.MainForm.InvisibleEmulation = invisible; - - /// - /// 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) + /// + public static event StateSavedEventHandler StateSaved { - if (!1.RangeTo(RunningSystem.MaxControllers).Contains(player)) - throw new IndexOutOfRangeException($"{RunningSystem.DisplayName} does not support {player} controller(s)"); - GetAllInputs(); - return _allJoyPads[player - 1]; + add => GlobalWin.ClientApi.StateSaved += value; + remove => GlobalWin.ClientApi.StateSaved -= value; } - /// - /// Load a savestate specified by its name - /// - /// Savestate friendly name - public static void LoadState(string name) => GlobalWin.MainForm.LoadState(Path.Combine(GlobalWin.Config.PathEntries.SaveStateAbsolutePath(GlobalWin.Game.System), $"{name}.State"), name, suppressOSD: false); + /// + public static int BorderHeight() => GlobalWin.ClientApi.BorderHeight(); - /// - /// 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) - { - if (BeforeQuickLoad == null) - { - eventHandled = false; - return; - } - var e = new BeforeQuickLoadEventArgs(quickSaveSlotName); - BeforeQuickLoad(sender, e); - eventHandled = e.Handled; - } + /// + public static int BorderWidth() => GlobalWin.ClientApi.BorderWidth(); + /// + public static int BufferHeight() => GlobalWin.ClientApi.BufferHeight(); - /// - /// 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) - { - if (BeforeQuickSave == null) - { - eventHandled = false; - return; - } - var e = new BeforeQuickSaveEventArgs(quickSaveSlotName); - BeforeQuickSave(sender, e); - eventHandled = e.Handled; - } + /// + public static int BufferWidth() => GlobalWin.ClientApi.BufferWidth(); + /// + public static void ClearAutohold() => GlobalWin.ClientApi.ClearAutohold(); - /// - /// 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) => StateLoaded?.Invoke(sender, new StateLoadedEventArgs(stateName)); + /// + public static void CloseEmulator() => GlobalWin.ClientApi.CloseEmulator(); - /// - /// 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) => StateSaved?.Invoke(sender, new StateSavedEventArgs(stateName)); + /// + public static void CloseEmulatorWithCode(int exitCode) => GlobalWin.ClientApi.CloseEmulatorWithCode(exitCode); - /// - /// Raise when a rom is successfully Loaded - /// - public static void OnRomLoaded(IEmulator emu) - { - Emulator = emu; - VideoProvider = emu.AsVideoProviderOrDefault(); - RomLoaded?.Invoke(null, EventArgs.Empty); + /// + public static void CloseRom() => GlobalWin.ClientApi.CloseRom(); - try - { - _allJoyPads = new List(RunningSystem.MaxControllers); - for (var i = 1; i <= RunningSystem.MaxControllers; i++) - _allJoyPads.Add(new Joypad(RunningSystem, i)); - } - catch (Exception e) - { - Console.Error.WriteLine("Apihawk is garbage and may not work this session."); - Console.Error.WriteLine(e); - } - } + /// + public static void DisplayMessages(bool value) => GlobalWin.ClientApi.DisplayMessages(value); - /// - /// Save a state with specified name - /// - /// Savestate friendly name - public static void SaveState(string name) => GlobalWin.MainForm.SaveState(Path.Combine(GlobalWin.Config.PathEntries.SaveStateAbsolutePath(GlobalWin.Game.System), $"{name}.State"), name, fromLua: false); + /// + public static void DoFrameAdvance() => GlobalWin.ClientApi.DoFrameAdvance(); - /// - /// 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 = 0, int right = 0, int bottom = 0) - { - GlobalWin.DisplayManager.GameExtraPadding = new Padding(left, top, right, bottom); - GlobalWin.MainForm.FrameBufferResized(); - } + /// + public static void DoFrameAdvanceAndUnpause() => GlobalWin.ClientApi.DoFrameAdvanceAndUnpause(); - /// - /// 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 = 0, int right = 0, int bottom = 0) - { - GlobalWin.DisplayManager.ClientExtraPadding = new Padding(left, top, right, bottom); - GlobalWin.MainForm.FrameBufferResized(); - } + /// + public static void EnableRewind(bool enabled) => GlobalWin.ClientApi.EnableRewind(enabled); - /// - /// 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 (!1.RangeTo(RunningSystem.MaxControllers).Contains(player)) throw new IndexOutOfRangeException($"{RunningSystem.DisplayName} does not support {player} controller(s)"); + /// + public static void FrameSkip(int numFrames) => GlobalWin.ClientApi.FrameSkip(numFrames); - if (joypad.Inputs == 0) - { - GlobalWin.InputManager.AutofireStickyXorAdapter.ClearStickies(); - } - else - { - foreach (var button in JoypadButtonsArray.Where(button => joypad.Inputs.HasFlag(button))) - { - GlobalWin.InputManager.AutofireStickyXorAdapter.SetSticky( - RunningSystem == SystemInfo.GB - ? $"{JoypadConverter.ConvertBack(button, RunningSystem)}" - : $"P{player} {JoypadConverter.ConvertBack(button, RunningSystem)}", - isSticky: true - ); - } - } + /// + public static Joypad GetInput(int player) => GlobalWin.ClientApi.GetInput(player); -#if false // Using this breaks joypad usage (even in UI); have to figure out why - if ((RunningSystem.AvailableButtons & JoypadButton.AnalogStick) == JoypadButton.AnalogStick) - { - for (var i = 1; i <= RunningSystem.MaxControllers; i++) - { - GlobalWin.InputManager.AutofireStickyXorAdapter.SetAxis($"P{i} X Axis", _allJoyPads[i - 1].AnalogX); - GlobalWin.InputManager.AutofireStickyXorAdapter.SetAxis($"P{i} Y Axis", _allJoyPads[i - 1].AnalogY); - } - } -#endif - } + /// + public static bool GetSoundOn() => GlobalWin.ClientApi.GetSoundOn(); - /// - /// Resume the emulation - /// - public static void UnpauseEmulation() => GlobalWin.MainForm.UnpauseEmulator(); + /// + public static int GetTargetScanlineIntensity() => GlobalWin.ClientApi.GetTargetScanlineIntensity(); - /// - /// Gets all current inputs for each joypad and store - /// them in class collection - /// - private static void GetAllInputs() - { - var joypadAdapter = GlobalWin.InputManager.AutofireStickyXorAdapter; + /// + public static int GetWindowSize() => GlobalWin.ClientApi.GetWindowSize(); - var pressedButtons = joypadAdapter.Definition.BoolButtons.Where(b => joypadAdapter.IsPressed(b)); + /// + public static void InvisibleEmulation(bool invisible) => GlobalWin.ClientApi.InvisibleEmulation(invisible); - foreach (var j in _allJoyPads) j.ClearInputs(); + /// + public static bool IsPaused() => GlobalWin.ClientApi.IsPaused(); - Parallel.ForEach(pressedButtons, button => - { - if (RunningSystem == SystemInfo.GB) _allJoyPads[0].AddInput(JoypadConverter.Convert(button)); - else if (int.TryParse(button.Substring(1, 2), out var player)) _allJoyPads[player - 1].AddInput(JoypadConverter.Convert(button.Substring(3))); - }); + /// + public static bool IsSeeking() => GlobalWin.ClientApi.IsSeeking(); - if ((RunningSystem.AvailableButtons & JoypadButton.AnalogStick) == JoypadButton.AnalogStick) - { - for (var i = 1; i <= RunningSystem.MaxControllers; i++) - { - _allJoyPads[i - 1].AnalogX = joypadAdapter.AxisValue($"P{i} X Axis"); - _allJoyPads[i - 1].AnalogY = joypadAdapter.AxisValue($"P{i} Y Axis"); - } - } - } + /// + public static bool IsTurbo() => GlobalWin.ClientApi.IsTurbo(); - public static void CloseEmulator() => GlobalWin.MainForm.CloseEmulator(); + /// + public static void LoadState(string name) => GlobalWin.ClientApi.LoadState(name); - public static void CloseEmulatorWithCode(int exitCode) => GlobalWin.MainForm.CloseEmulator(exitCode); + /// + public static void OnBeforeQuickLoad(object sender, string quickSaveSlotName, out bool eventHandled) => GlobalWin.ClientApi.OnBeforeQuickLoad(sender, quickSaveSlotName, out eventHandled); - public static int BorderHeight() => GlobalWin.DisplayManager.TransformPoint(new Point(0, 0)).Y; + /// + public static void OnBeforeQuickSave(object sender, string quickSaveSlotName, out bool eventHandled) => GlobalWin.ClientApi.OnBeforeQuickSave(sender, quickSaveSlotName, out eventHandled); - public static int BorderWidth() => GlobalWin.DisplayManager.TransformPoint(new Point(0, 0)).X; + /// + public static void OnRomLoaded(IEmulator emu) => GlobalWin.ClientApi.OnRomLoaded(emu); - public static int BufferHeight() => VideoProvider.BufferHeight; + /// + public static void OnStateLoaded(object sender, string stateName) => GlobalWin.ClientApi.OnStateLoaded(sender, stateName); - public static int BufferWidth() => VideoProvider.BufferWidth; + /// + public static void OnStateSaved(object sender, string stateName) => GlobalWin.ClientApi.OnStateSaved(sender, stateName); - public static void ClearAutohold() => GlobalWin.MainForm.ClearHolds(); + /// + public static void OpenRom(string path) => GlobalWin.ClientApi.OpenRom(path); - public static void CloseRom() => GlobalWin.MainForm.CloseRom(); + /// + public static void Pause() => GlobalWin.ClientApi.Pause(); - public static void DisplayMessages(bool value) => GlobalWin.Config.DisplayMessages = value; + /// + public static void PauseAv() => GlobalWin.ClientApi.PauseAv(); - public static void EnableRewind(bool enabled) => GlobalWin.MainForm.EnableRewind(enabled); + /// + public static void RebootCore() => GlobalWin.ClientApi.RebootCore(); - public static void FrameSkip(int numFrames) - { - if (numFrames > 0) - { - GlobalWin.Config.FrameSkip = numFrames; - GlobalWin.MainForm.FrameSkipMessage(); - } - else - { - Console.WriteLine("Invalid frame skip value"); - } - } + /// + public static void SaveRam() => GlobalWin.ClientApi.SaveRam(); - public static int GetTargetScanlineIntensity() => GlobalWin.Config.TargetScanlineFilterIntensity; + /// + public static void SaveState(string name) => GlobalWin.ClientApi.SaveState(name); - public static int GetWindowSize() => GlobalWin.Config.TargetZoomFactors[Emulator.SystemId]; + /// + public static int ScreenHeight() => GlobalWin.ClientApi.ScreenHeight(); - public static void SetSoundOn(bool enable) - { - if (enable != GlobalWin.Config.SoundEnabled) GlobalWin.MainForm.ToggleSound(); - } + /// + public static void Screenshot(string path = null) => GlobalWin.ClientApi.Screenshot(path); - public static bool GetSoundOn() => GlobalWin.Config.SoundEnabled; + /// + public static void ScreenshotToClipboard() => GlobalWin.ClientApi.ScreenshotToClipboard(); - public static bool IsPaused() => GlobalWin.MainForm.EmulatorPaused; + /// + public static int ScreenWidth() => GlobalWin.ClientApi.ScreenWidth(); - public static bool IsTurbo() => GlobalWin.MainForm.IsTurboing; + /// + public static void SeekFrame(int frame) => GlobalWin.ClientApi.SeekFrame(frame); - public static bool IsSeeking() => GlobalWin.MainForm.IsSeeking; + /// + public static void SetExtraPadding(int left, int top = 0, int right = 0, int bottom = 0) => GlobalWin.ClientApi.SetExtraPadding(left, top, right, bottom); - public static void OpenRom(string path) => GlobalWin.MainForm.LoadRom(path, new MainForm.LoadRomArgs { OpenAdvanced = OpenAdvancedSerializer.ParseWithLegacy(path) }); + /// + public static void SetGameExtraPadding(int left, int top = 0, int right = 0, int bottom = 0) => GlobalWin.ClientApi.SetGameExtraPadding(left, top, right, bottom); - public static void Pause() => GlobalWin.MainForm.PauseEmulator(); + /// + public static void SetInput(int player, Joypad joypad) => GlobalWin.ClientApi.SetInput(player, joypad); - public static void PauseAv() => GlobalWin.MainForm.PauseAvi = true; + /// + public static void SetScreenshotOSD(bool value) => GlobalWin.ClientApi.SetScreenshotOSD(value); - public static void RebootCore() => GlobalWin.MainForm.RebootCore(); + /// + public static void SetSoundOn(bool enable) => GlobalWin.ClientApi.SetSoundOn(enable); - public static void SaveRam() => GlobalWin.MainForm.FlushSaveRAM(); + /// + public static void SetTargetScanlineIntensity(int val) => GlobalWin.ClientApi.SetTargetScanlineIntensity(val); - public static int ScreenHeight() => GlobalWin.MainForm.PresentationPanel.NativeSize.Height; + /// + public static void SetWindowSize(int size) => GlobalWin.ClientApi.SetWindowSize(size); - public static void Screenshot(string path = null) - { - if (path == null) GlobalWin.MainForm.TakeScreenshot(); - else GlobalWin.MainForm.TakeScreenshot(path); - } + /// + public static void SpeedMode(int percent) => GlobalWin.ClientApi.SpeedMode(percent); - public static void ScreenshotToClipboard() => GlobalWin.MainForm.TakeScreenshotToClipboard(); + /// + public static void TogglePause() => GlobalWin.ClientApi.TogglePause(); - public static void SetTargetScanlineIntensity(int val) => GlobalWin.Config.TargetScanlineFilterIntensity = val; + /// + public static Point TransformPoint(Point point) => GlobalWin.ClientApi.TransformPoint(point); - public static void SetScreenshotOSD(bool value) => GlobalWin.Config.ScreenshotCaptureOsd = value; + /// + public static void Unpause() => GlobalWin.ClientApi.Unpause(); - public static int ScreenWidth() => GlobalWin.MainForm.PresentationPanel.NativeSize.Width; + /// + public static void UnpauseAv() => GlobalWin.ClientApi.UnpauseAv(); - public static void SetWindowSize(int size) - { - if (size == 1 || size == 2 || size == 3 || size == 4 || size == 5 || size == 10) - { - GlobalWin.Config.TargetZoomFactors[Emulator.SystemId] = size; - GlobalWin.MainForm.FrameBufferResized(); - GlobalWin.OSD.AddMessage($"Window size set to {size}x"); - } - else - { - Console.WriteLine("Invalid window size"); - } - } + /// + public static void UnpauseEmulation() => GlobalWin.ClientApi.UnpauseEmulation(); - public static void SpeedMode(int percent) - { - if (percent.StrictlyBoundedBy(0.RangeTo(6400))) GlobalWin.MainForm.ClickSpeedItem(percent); - else Console.WriteLine("Invalid speed value"); - } + /// + public static void UpdateEmulatorAndVP(IEmulator emu = null) => GlobalWin.ClientApi.UpdateEmulatorAndVP(emu); - public static void TogglePause() => GlobalWin.MainForm.TogglePause(); + /// + public static int Xpos() => GlobalWin.ClientApi.Xpos(); - public static Point TransformPoint(Point point) => GlobalWin.DisplayManager.TransformPoint(point); - - public static void Unpause() => GlobalWin.MainForm.UnpauseEmulator(); - - public static void UnpauseAv() => GlobalWin.MainForm.PauseAvi = false; - - public static int Xpos() => GlobalWin.MainForm.DesktopLocation.X; - - public static int Ypos() => GlobalWin.MainForm.DesktopLocation.Y; - - /// - /// Gets current emulated system - /// - public static SystemInfo RunningSystem - { - get - { - switch (GlobalWin.Emulator.SystemId) - { - case "PCE" when GlobalWin.Emulator is PCEngine pceHawk: - return pceHawk.Type switch - { - NecSystemType.TurboGrafx => SystemInfo.PCE, - NecSystemType.TurboCD => SystemInfo.PCECD, - NecSystemType.SuperGrafx => SystemInfo.SGX, - _ => throw new ArgumentOutOfRangeException() - }; - case "PCE": - return SystemInfo.PCE; // not always accurate, but anyone wanting accuracy has probably figured out how to use IEmu.GetSystemId() - case "SMS": - var sms = (SMS) GlobalWin.Emulator; - return sms.IsSG1000 - ? SystemInfo.SG - : sms.IsGameGear - ? SystemInfo.GG - : SystemInfo.SMS; - case "GB": - if (GlobalWin.Emulator is Gameboy gb) return gb.IsCGBMode() ? SystemInfo.GBC : SystemInfo.GB; - return SystemInfo.DualGB; - default: - return SystemInfo.FindByCoreSystem(SystemIdConverter.Convert(GlobalWin.Emulator.SystemId)); - } - } - } + /// + public static int Ypos() => GlobalWin.ClientApi.Ypos(); } } diff --git a/src/BizHawk.Client.EmuHawk/Api/Libraries/EmuClientApi.cs b/src/BizHawk.Client.EmuHawk/Api/Libraries/EmuClientApi.cs new file mode 100644 index 0000000000..8739667ffd --- /dev/null +++ b/src/BizHawk.Client.EmuHawk/Api/Libraries/EmuClientApi.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +using BizHawk.Client.Common; +using BizHawk.Common; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Nintendo.Gameboy; +using BizHawk.Emulation.Cores.PCEngine; +using BizHawk.Emulation.Cores.Sega.MasterSystem; + +namespace BizHawk.Client.EmuHawk +{ + public class EmuClientApi : IEmuClient + { + private List _allJoyPads; + + private IEmulator Emulator { get; set; } + + private readonly IReadOnlyCollection JoypadButtonsArray = Enum.GetValues(typeof(JoypadButton)).Cast().ToList(); //TODO can the return of GetValues be cast to JoypadButton[]? --yoshi + + private readonly JoypadStringToEnumConverter JoypadConverter = new JoypadStringToEnumConverter(); + + public SystemInfo RunningSystem + { + get + { + switch (GlobalWin.Emulator.SystemId) + { + case "PCE" when GlobalWin.Emulator is PCEngine pceHawk: + return pceHawk.Type switch + { + NecSystemType.TurboGrafx => SystemInfo.PCE, + NecSystemType.TurboCD => SystemInfo.PCECD, + NecSystemType.SuperGrafx => SystemInfo.SGX, + _ => throw new ArgumentOutOfRangeException() + }; + case "PCE": + return SystemInfo.PCE; // not always accurate, but anyone wanting accuracy has probably figured out how to use IEmu.GetSystemId() + case "SMS": + var sms = (SMS) GlobalWin.Emulator; + return sms.IsSG1000 + ? SystemInfo.SG + : sms.IsGameGear + ? SystemInfo.GG + : SystemInfo.SMS; + case "GB": + if (GlobalWin.Emulator is Gameboy gb) return gb.IsCGBMode() ? SystemInfo.GBC : SystemInfo.GB; + return SystemInfo.DualGB; + default: + return SystemInfo.FindByCoreSystem(SystemIdConverter.Convert(GlobalWin.Emulator.SystemId)); + } + } + } + + internal readonly BizHawkSystemIdToEnumConverter SystemIdConverter = new BizHawkSystemIdToEnumConverter(); + + private IVideoProvider VideoProvider { get; set; } + + public event BeforeQuickLoadEventHandler BeforeQuickLoad; + + public event BeforeQuickSaveEventHandler BeforeQuickSave; + + public event EventHandler RomLoaded; + + public event StateLoadedEventHandler StateLoaded; + + public event StateSavedEventHandler StateSaved; + + public int BorderHeight() => GlobalWin.DisplayManager.TransformPoint(new Point(0, 0)).Y; + + public int BorderWidth() => GlobalWin.DisplayManager.TransformPoint(new Point(0, 0)).X; + + public int BufferHeight() => VideoProvider.BufferHeight; + + public int BufferWidth() => VideoProvider.BufferWidth; + + public void ClearAutohold() => GlobalWin.MainForm.ClearHolds(); + + public void CloseEmulator() => GlobalWin.MainForm.CloseEmulator(); + + public void CloseEmulatorWithCode(int exitCode) => GlobalWin.MainForm.CloseEmulator(exitCode); + + public void CloseRom() => GlobalWin.MainForm.CloseRom(); + + public void DisplayMessages(bool value) => GlobalWin.Config.DisplayMessages = value; + + public void DoFrameAdvance() + { + GlobalWin.MainForm.FrameAdvance(); + GlobalWin.MainForm.StepRunLoop_Throttle(); + GlobalWin.MainForm.Render(); + } + + public void DoFrameAdvanceAndUnpause() + { + DoFrameAdvance(); + UnpauseEmulation(); + } + + public void EnableRewind(bool enabled) => GlobalWin.MainForm.EnableRewind(enabled); + + public void FrameSkip(int numFrames) + { + if (numFrames > 0) + { + GlobalWin.Config.FrameSkip = numFrames; + GlobalWin.MainForm.FrameSkipMessage(); + } + else + { + Console.WriteLine("Invalid frame skip value"); + } + } + + private void GetAllInputs() + { + var joypadAdapter = GlobalWin.InputManager.AutofireStickyXorAdapter; + + var pressedButtons = joypadAdapter.Definition.BoolButtons.Where(b => joypadAdapter.IsPressed(b)); + + foreach (var j in _allJoyPads) j.ClearInputs(); + + Parallel.ForEach(pressedButtons, button => + { + if (RunningSystem == SystemInfo.GB) _allJoyPads[0].AddInput(JoypadConverter.Convert(button)); + else if (int.TryParse(button.Substring(1, 2), out var player)) _allJoyPads[player - 1].AddInput(JoypadConverter.Convert(button.Substring(3))); + }); + + if ((RunningSystem.AvailableButtons & JoypadButton.AnalogStick) == JoypadButton.AnalogStick) + { + for (var i = 1; i <= RunningSystem.MaxControllers; i++) + { + _allJoyPads[i - 1].AnalogX = joypadAdapter.AxisValue($"P{i} X Axis"); + _allJoyPads[i - 1].AnalogY = joypadAdapter.AxisValue($"P{i} Y Axis"); + } + } + } + + public Joypad GetInput(int player) + { + if (!1.RangeTo(RunningSystem.MaxControllers).Contains(player)) + throw new IndexOutOfRangeException($"{RunningSystem.DisplayName} does not support {player} controller(s)"); + GetAllInputs(); + return _allJoyPads[player - 1]; + } + + public bool GetSoundOn() => GlobalWin.Config.SoundEnabled; + + public int GetTargetScanlineIntensity() => GlobalWin.Config.TargetScanlineFilterIntensity; + + public int GetWindowSize() => GlobalWin.Config.TargetZoomFactors[Emulator.SystemId]; + + public void InvisibleEmulation(bool invisible) => GlobalWin.MainForm.InvisibleEmulation = invisible; + + public bool IsPaused() => GlobalWin.MainForm.EmulatorPaused; + + public bool IsSeeking() => GlobalWin.MainForm.IsSeeking; + + public bool IsTurbo() => GlobalWin.MainForm.IsTurboing; + + public void LoadState(string name) => GlobalWin.MainForm.LoadState(Path.Combine(GlobalWin.Config.PathEntries.SaveStateAbsolutePath(GlobalWin.Game.System), $"{name}.State"), name, suppressOSD: false); + + public void OnBeforeQuickLoad(object sender, string quickSaveSlotName, out bool eventHandled) + { + if (BeforeQuickLoad == null) + { + eventHandled = false; + return; + } + var e = new BeforeQuickLoadEventArgs(quickSaveSlotName); + BeforeQuickLoad(sender, e); + eventHandled = e.Handled; + } + + public void OnBeforeQuickSave(object sender, string quickSaveSlotName, out bool eventHandled) + { + if (BeforeQuickSave == null) + { + eventHandled = false; + return; + } + var e = new BeforeQuickSaveEventArgs(quickSaveSlotName); + BeforeQuickSave(sender, e); + eventHandled = e.Handled; + } + + public void OnRomLoaded(IEmulator emu) + { + Emulator = emu; + VideoProvider = emu.AsVideoProviderOrDefault(); + RomLoaded?.Invoke(null, EventArgs.Empty); + + try + { + _allJoyPads = new List(RunningSystem.MaxControllers); + for (var i = 1; i <= RunningSystem.MaxControllers; i++) + _allJoyPads.Add(new Joypad(RunningSystem, i)); + } + catch (Exception e) + { + Console.Error.WriteLine("Apihawk is garbage and may not work this session."); + Console.Error.WriteLine(e); + } + } + + public void OnStateLoaded(object sender, string stateName) => StateLoaded?.Invoke(sender, new StateLoadedEventArgs(stateName)); + + public void OnStateSaved(object sender, string stateName) => StateSaved?.Invoke(sender, new StateSavedEventArgs(stateName)); + + public void OpenRom(string path) => GlobalWin.MainForm.LoadRom(path, new MainForm.LoadRomArgs { OpenAdvanced = OpenAdvancedSerializer.ParseWithLegacy(path) }); + + public void Pause() => GlobalWin.MainForm.PauseEmulator(); + + public void PauseAv() => GlobalWin.MainForm.PauseAvi = true; + + public void RebootCore() => GlobalWin.MainForm.RebootCore(); + + public void SaveRam() => GlobalWin.MainForm.FlushSaveRAM(); + + public void SaveState(string name) => GlobalWin.MainForm.SaveState(Path.Combine(GlobalWin.Config.PathEntries.SaveStateAbsolutePath(GlobalWin.Game.System), $"{name}.State"), name, fromLua: false); + + public int ScreenHeight() => GlobalWin.MainForm.PresentationPanel.NativeSize.Height; + + public void Screenshot(string path) + { + if (path == null) GlobalWin.MainForm.TakeScreenshot(); + else GlobalWin.MainForm.TakeScreenshot(path); + } + + public void ScreenshotToClipboard() => GlobalWin.MainForm.TakeScreenshotToClipboard(); + + public int ScreenWidth() => GlobalWin.MainForm.PresentationPanel.NativeSize.Width; + + public void SeekFrame(int frame) + { + var wasPaused = GlobalWin.MainForm.EmulatorPaused; + while (Emulator.Frame != frame) GlobalWin.MainForm.SeekFrameAdvance(); + if (!wasPaused) GlobalWin.MainForm.UnpauseEmulator(); + } + + public void SetExtraPadding(int left, int top, int right, int bottom) + { + GlobalWin.DisplayManager.ClientExtraPadding = new Padding(left, top, right, bottom); + GlobalWin.MainForm.FrameBufferResized(); + } + + public void SetGameExtraPadding(int left, int top, int right, int bottom) + { + GlobalWin.DisplayManager.GameExtraPadding = new Padding(left, top, right, bottom); + GlobalWin.MainForm.FrameBufferResized(); + } + + public void SetInput(int player, Joypad joypad) + { + if (!1.RangeTo(RunningSystem.MaxControllers).Contains(player)) throw new IndexOutOfRangeException($"{RunningSystem.DisplayName} does not support {player} controller(s)"); + + if (joypad.Inputs == 0) + { + GlobalWin.InputManager.AutofireStickyXorAdapter.ClearStickies(); + } + else + { + foreach (var button in JoypadButtonsArray.Where(button => joypad.Inputs.HasFlag(button))) + { + GlobalWin.InputManager.AutofireStickyXorAdapter.SetSticky( + RunningSystem == SystemInfo.GB + ? $"{JoypadConverter.ConvertBack(button, RunningSystem)}" + : $"P{player} {JoypadConverter.ConvertBack(button, RunningSystem)}", + isSticky: true + ); + } + } + +#if false // Using this breaks joypad usage (even in UI); have to figure out why + if ((RunningSystem.AvailableButtons & JoypadButton.AnalogStick) == JoypadButton.AnalogStick) + { + for (var i = 1; i <= RunningSystem.MaxControllers; i++) + { + GlobalWin.InputManager.AutofireStickyXorAdapter.SetAxis($"P{i} X Axis", _allJoyPads[i - 1].AnalogX); + GlobalWin.InputManager.AutofireStickyXorAdapter.SetAxis($"P{i} Y Axis", _allJoyPads[i - 1].AnalogY); + } + } +#endif + } + + public void SetScreenshotOSD(bool value) => GlobalWin.Config.ScreenshotCaptureOsd = value; + + public void SetSoundOn(bool enable) + { + if (enable != GlobalWin.Config.SoundEnabled) GlobalWin.MainForm.ToggleSound(); + } + + public void SetTargetScanlineIntensity(int val) => GlobalWin.Config.TargetScanlineFilterIntensity = val; + + public void SetWindowSize(int size) + { + if (size == 1 || size == 2 || size == 3 || size == 4 || size == 5 || size == 10) + { + GlobalWin.Config.TargetZoomFactors[Emulator.SystemId] = size; + GlobalWin.MainForm.FrameBufferResized(); + GlobalWin.OSD.AddMessage($"Window size set to {size}x"); + } + else + { + Console.WriteLine("Invalid window size"); + } + } + + public void SpeedMode(int percent) + { + if (percent.StrictlyBoundedBy(0.RangeTo(6400))) GlobalWin.MainForm.ClickSpeedItem(percent); + else Console.WriteLine("Invalid speed value"); + } + + public void TogglePause() => GlobalWin.MainForm.TogglePause(); + + public Point TransformPoint(Point point) => GlobalWin.DisplayManager.TransformPoint(point); + + public void Unpause() => GlobalWin.MainForm.UnpauseEmulator(); + + public void UnpauseAv() => GlobalWin.MainForm.PauseAvi = false; + + public void UnpauseEmulation() => GlobalWin.MainForm.UnpauseEmulator(); + + public void UpdateEmulatorAndVP(IEmulator emu) + { + Emulator = emu; + VideoProvider = emu.AsVideoProviderOrDefault(); + } + + public int Xpos() => GlobalWin.MainForm.DesktopLocation.X; + + public int Ypos() => GlobalWin.MainForm.DesktopLocation.Y; + } +} diff --git a/src/BizHawk.Client.EmuHawk/GlobalWin.cs b/src/BizHawk.Client.EmuHawk/GlobalWin.cs index a5f9ae269d..4ae81959e0 100644 --- a/src/BizHawk.Client.EmuHawk/GlobalWin.cs +++ b/src/BizHawk.Client.EmuHawk/GlobalWin.cs @@ -47,5 +47,7 @@ namespace BizHawk.Client.EmuHawk public static GameInfo Game { get; set; } public static IMovieSession MovieSession { get; set; } public static InputManager InputManager { get; } = new InputManager(); + + public static EmuClientApi ClientApi { get; set; } } } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 14b766e7f0..6c65110e4d 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -275,6 +275,7 @@ namespace BizHawk.Client.EmuHawk public MainForm(string[] args) { GlobalWin.MainForm = this; + GlobalWin.ClientApi = new EmuClientApi(); //do this threaded stuff early so it has plenty of time to run in background Database.InitializeDatabase(Path.Combine(PathUtils.ExeDirectoryPath, "gamedb", "gamedb.txt")); diff --git a/src/BizHawk.Client.EmuHawk/tools/ExternalToolManager.cs b/src/BizHawk.Client.EmuHawk/tools/ExternalToolManager.cs index 3979146488..4165c63460 100644 --- a/src/BizHawk.Client.EmuHawk/tools/ExternalToolManager.cs +++ b/src/BizHawk.Client.EmuHawk/tools/ExternalToolManager.cs @@ -99,7 +99,7 @@ namespace BizHawk.Client.EmuHawk item.Tag = (externalToolFile.Location, entryPoint.FullName); // Tag set => no errors (show custom icon even when disabled) if (applicabilityAttrs.Count == 1) { - var system = ClientApi.SystemIdConverter.Convert(GlobalWin.Emulator.SystemId); + var system = GlobalWin.ClientApi.SystemIdConverter.Convert(GlobalWin.Emulator.SystemId); if (applicabilityAttrs[0].NotApplicableTo(system)) { item.ToolTipText = system == CoreSystem.Null