BizHawk/BizHawk.Client.ApiHawk/Classes/ClientApi.cs

442 lines
14 KiB
C#

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
{
/// <summary>
/// This class contains some methods that
/// interract with BizHawk client
/// </summary>
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<Joypad> allJoypads;
/// <summary>
/// Occurs before a quickload is done (just after user has pressed the shortcut button
/// or has click on the item menu)
/// </summary>
public static event BeforeQuickLoadEventHandler BeforeQuickLoad;
/// <summary>
/// Occurs before a quicksave is done (just after user has pressed the shortcut button
/// or has click on the item menu)
/// </summary>
public static event BeforeQuickSaveEventHandler BeforeQuickSave;
/// <summary>
/// Occurs when a ROM is succesfully loaded
/// </summary>
public static event EventHandler RomLoaded;
/// <summary>
/// Occurs when a savestate is sucessfully loaded
/// </summary>
public static event StateLoadedEventHandler StateLoaded;
/// <summary>
/// Occurs when a savestate is successfully saved
/// </summary>
public static event StateSavedEventHandler StateSaved;
#endregion
#region cTor(s)
/// <summary>
/// Static stuff initilization
/// </summary>
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
/// <summary>
/// THE FrameAdvance stuff
/// </summary>
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);
}
/// <summary>
/// THE FrameAdvance stuff
/// Auto unpause emulation
/// </summary>
public static void DoFrameAdvanceAndUnpause()
{
DoFrameAdvance();
UnpauseEmulation();
}
/// <summary>
/// Gets a <see cref="Joypad"/> for specified player
/// </summary>
/// <param name="player">Player (one based) you want current inputs</param>
/// <returns>A <see cref="Joypad"/> populated with current inputs</returns>
/// <exception cref="IndexOutOfRangeException">Raised when you specify a player less than 1 or greater than maximum allows (see SystemInfo class to get this information)</exception>
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];
}
}
/// <summary>
/// Load a savestate specified by its name
/// </summary>
/// <param name="name">Savetate friendly name</param>
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 });
}
/// <summary>
/// Raised before a quickload is done (just after pressing shortcut button)
/// </summary>
/// <param name="sender">Object who raised the event</param>
/// <param name="quickSaveSlotName">Slot used for quickload</param>
/// <param name="eventHandled">A boolean that can be set if users want to handle save themselves; if so, BizHawk won't do anything</param>
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;
}
}
/// <summary>
/// Raised before a quicksave is done (just after pressing shortcut button)
/// </summary>
/// <param name="sender">Object who raised the event</param>
/// <param name="quickSaveSlotName">Slot used for quicksave</param>
/// <param name="eventHandled">A boolean that can be set if users want to handle save themselves; if so, BizHawk won't do anything</param>
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;
}
}
/// <summary>
/// Raise when a state is loaded
/// </summary>
/// <param name="sender">Object who raised the event</param>
/// <param name="stateName">User friendly name for saved state</param>
public static void OnStateLoaded(object sender, string stateName)
{
if (StateLoaded != null)
{
StateLoaded(sender, new StateLoadedEventArgs(stateName));
}
}
/// <summary>
/// Raise when a state is saved
/// </summary>
/// <param name="sender">Object who raised the event</param>
/// <param name="stateName">User friendly name for saved state</param>
public static void OnStateSaved(object sender, string stateName)
{
if (StateSaved != null)
{
StateSaved(sender, new StateSavedEventArgs(stateName));
}
}
/// <summary>
/// Raise when a rom is successfully Loaded
/// </summary>
public static void OnRomLoaded()
{
if (RomLoaded != null)
{
RomLoaded(null, EventArgs.Empty);
}
allJoypads = new List<Joypad>(RunningSystem.MaxControllers);
for (int i = 1; i <= RunningSystem.MaxControllers; i++)
{
allJoypads.Add(new Joypad(RunningSystem, i));
}
}
/// <summary>
/// Save a state with specified name
/// </summary>
/// <param name="name">Savetate friendly name</param>
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 });
}
/// <summary>
/// Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements
/// </summary>
/// <param name="left">Left padding</param>
/// <param name="top">Top padding</param>
/// <param name="right">Right padding</param>
/// <param name="bottom">Bottom padding</param>
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);
}
/// <summary>
/// Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements
/// </summary>
/// <param name="left">Left padding</param>
public static void SetExtraPadding(int left)
{
SetExtraPadding(left, 0, 0, 0);
}
/// <summary>
/// Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements
/// </summary>
/// <param name="left">Left padding</param>
/// <param name="top">Top padding</param>
public static void SetExtraPadding(int left, int top)
{
SetExtraPadding(left, top, 0, 0);
}
/// <summary>
/// Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements
/// </summary>
/// <param name="left">Left padding</param>
/// <param name="top">Top padding</param>
/// <param name="right">Right padding</param>
public static void SetExtraPadding(int left, int top, int right)
{
SetExtraPadding(left, top, right, 0);
}
/// <summary>
/// Set inputs in specified <see cref="Joypad"/> to specified player
/// </summary>
/// <param name="player">Player (one based) whom inputs must be set</param>
/// <param name="joypad"><see cref="Joypad"/> with inputs</param>
/// <exception cref="IndexOutOfRangeException">Raised when you specify a player less than 1 or greater than maximum allows (see SystemInfo class to get this information)</exception>
/// <remarks>Still have some strange behaviour with multiple inputs; so this feature is still in beta</remarks>
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);
}
}*/
}
}
/// <summary>
/// Resume the emulation
/// </summary>
public static void UnpauseEmulation()
{
MethodInfo method = mainFormClass.GetMethod("UnpauseEmulator");
method.Invoke(clientMainFormInstance, null);
}
#endregion Public
/// <summary>
/// Gets all current inputs for each joypad and store
/// them in <see cref="Joypad"/> class collection
/// </summary>
private static void GetAllInputs()
{
AutoFireStickyXorAdapter joypadAdaptor = Global.AutofireStickyXORAdapter;
IEnumerable<string> pressedButtons = from button in joypadAdaptor.Definition.BoolButtons
where joypadAdaptor.IsPressed(button)
select button;
foreach (Joypad j in allJoypads)
{
j.ClearInputs();
}
Parallel.ForEach<string>(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
/// <summary>
/// Gets current emulated system
/// </summary>
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
}
}