libretro handling cleanup, reorg some of this, fix some input cases, better domain names
funsie found in this cleanup: can't use `in` params with the BizInvoker as it doesn't like the read only semantics (results in some exception in CreateType)
This commit is contained in:
parent
dc774ed8be
commit
9e4836d300
|
@ -637,7 +637,7 @@ namespace BizHawk.Client.Common
|
|||
// must be done before LoadNoGame (which triggers retro_init and the paths to be consumed by the core)
|
||||
// game name == name of core
|
||||
Game = game = new GameInfo { Name = Path.GetFileNameWithoutExtension(launchLibretroCore), System = VSystemID.Raw.Libretro };
|
||||
var retro = new LibretroEmulator(nextComm, game, launchLibretroCore);
|
||||
var retro = new LibretroHost(nextComm, game, launchLibretroCore);
|
||||
nextEmulator = retro;
|
||||
|
||||
if (retro.Description.SupportsNoGame && string.IsNullOrEmpty(path))
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace BizHawk.Client.Common
|
|||
key = key.Substring(3);
|
||||
}
|
||||
}
|
||||
key = key.RemovePrefix(LibretroEmulator.LibretroControllerDef.PFX_RETROPAD);
|
||||
key = key.RemovePrefix(LibretroHost.LibretroControllerDef.PFX_RETROPAD);
|
||||
|
||||
if (SystemOverrides.TryGetValue(systemId, out var overridesForSys) && overridesForSys.TryGetValue(key, out var c))
|
||||
{
|
||||
|
|
|
@ -2837,7 +2837,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
items.Add(CreateCoreSubmenu(VSystemCategory.Consoles, CoreNames.Gpgx, CreateGenericCoreConfigItem<GPGX>(CoreNames.Gpgx)));
|
||||
|
||||
// Handy
|
||||
items.Add(CreateCoreSubmenu(VSystemCategory.Handhelds, CoreNames.Handy, CreateGenericCoreConfigItem<Lynx>(CoreNames.Handy))); // as Handy doesn't implement `IStatable<,>`, this opens an empty `GenericCoreConfig`, which is dumb, but matches the existing behaviour
|
||||
items.Add(CreateCoreSubmenu(VSystemCategory.Handhelds, CoreNames.Handy, CreateGenericCoreConfigItem<Lynx>(CoreNames.Handy))); // as Handy doesn't implement `ISettable<,>`, this opens an empty `GenericCoreConfig`, which is dumb, but matches the existing behaviour
|
||||
|
||||
// HyperNyma
|
||||
items.Add(CreateCoreSubmenu(VSystemCategory.Consoles, CoreNames.HyperNyma, CreateGenericNymaCoreConfigItem<HyperNyma>(CoreNames.HyperNyma, HyperNyma.CachedSettingsInfo)));
|
||||
|
@ -2852,7 +2852,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
items.Add(CreateCoreSubmenu(
|
||||
VSystemCategory.Other,
|
||||
CoreNames.Libretro,
|
||||
CreateGenericCoreConfigItem<LibretroEmulator>(CoreNames.Libretro))); // as Libretro doesn't implement `IStatable<,>`, this opens an empty `GenericCoreConfig`, which is dumb, but matches the existing behaviour
|
||||
CreateGenericCoreConfigItem<LibretroHost>(CoreNames.Libretro))); // as Libretro doesn't implement `ISettable<,>`, this opens an empty `GenericCoreConfig`, which is dumb, but matches the existing behaviour
|
||||
|
||||
// MAME
|
||||
var mameSettingsItem = CreateSettingsItem("Settings...", (_, _) => OpenGenericCoreConfig());
|
||||
|
@ -3016,7 +3016,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
items.Add(CreateCoreSubmenu(VSystemCategory.Consoles, CoreNames.TurboNyma, CreateGenericNymaCoreConfigItem<TurboNyma>(CoreNames.TurboNyma, TurboNyma.CachedSettingsInfo)));
|
||||
|
||||
// uzem
|
||||
items.Add(CreateCoreSubmenu(VSystemCategory.Consoles, CoreNames.Uzem, CreateGenericCoreConfigItem<Uzem>(CoreNames.Uzem))); // as uzem doesn't implement `IStatable<,>`, this opens an empty `GenericCoreConfig`, which is dumb, but matches the existing behaviour
|
||||
items.Add(CreateCoreSubmenu(VSystemCategory.Consoles, CoreNames.Uzem, CreateGenericCoreConfigItem<Uzem>(CoreNames.Uzem))); // as uzem doesn't implement `ISettable<,>`, this opens an empty `GenericCoreConfig`, which is dumb, but matches the existing behaviour
|
||||
|
||||
// VectrexHawk
|
||||
items.Add(CreateCoreSubmenu(VSystemCategory.Consoles, CoreNames.VectrexHawk, CreateGenericCoreConfigItem<VectrexHawk>(CoreNames.VectrexHawk)));
|
||||
|
|
|
@ -83,7 +83,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
try
|
||||
{
|
||||
var coreComm = _createCoreComm();
|
||||
using var retro = new LibretroEmulator(coreComm, _game, core, true);
|
||||
using var retro = new LibretroHost(coreComm, _game, core, true);
|
||||
btnLibretroLaunchGame.Enabled = true;
|
||||
if (retro.Description.SupportsNoGame)
|
||||
btnLibretroLaunchNoGame.Enabled = true;
|
||||
|
|
|
@ -359,10 +359,12 @@ namespace BizHawk.Emulation.Cores.Libretro
|
|||
public abstract uint retro_api_version();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_get_system_info(IntPtr retro_system_info);
|
||||
public abstract void retro_get_system_info(out retro_system_info retro_system_info);
|
||||
|
||||
// this is allowed to not initialize every variable, so ref is used instead of out
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_get_system_av_info(IntPtr retro_system_av_info);
|
||||
public abstract void retro_get_system_av_info(ref retro_system_av_info retro_system_av_info);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_set_environment(IntPtr retro_environment);
|
||||
|
@ -395,22 +397,27 @@ namespace BizHawk.Emulation.Cores.Libretro
|
|||
public abstract long retro_serialize_size();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract bool retro_serialize(IntPtr data, long size);
|
||||
public abstract bool retro_serialize(byte[] data, long size);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract bool retro_unserialize(IntPtr data, long size);
|
||||
public abstract bool retro_unserialize(byte[] data, long size);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_cheat_reset();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_cheat_set(uint index, bool enabled, IntPtr code);
|
||||
public abstract void retro_cheat_set(uint index, bool enabled, string code);
|
||||
|
||||
// maybe it would be better if retro_game_info was just a class instead of a struct?
|
||||
|
||||
[BizImport(cc, EntryPoint = "retro_load_game")]
|
||||
public abstract bool retro_load_no_game(IntPtr no_game_info = default); // don't send anything here
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract bool retro_load_game(IntPtr retro_game_info);
|
||||
public abstract bool retro_load_game(ref retro_game_info retro_game_info);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract bool retro_load_game_special(uint game_type, IntPtr retro_game_info, long num_info);
|
||||
public abstract bool retro_load_game_special(uint game_type, ref retro_game_info retro_game_info, long num_info);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_unload_game();
|
||||
|
@ -448,22 +455,22 @@ namespace BizHawk.Emulation.Cores.Libretro
|
|||
public abstract bool LibretroBridge_GetRetroTimingInfo(IntPtr cbHandler, ref LibretroApi.retro_system_timing t);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_GetRetroMessage(IntPtr cbHandler, ref LibretroApi.retro_message m);
|
||||
public abstract void LibretroBridge_GetRetroMessage(IntPtr cbHandler, out LibretroApi.retro_message m);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_SetDirectories(IntPtr cbHandler, byte[] systemDirectory, byte[] saveDirectory, byte[] coreDirectory, byte[] coreAssetsDirectory);
|
||||
public abstract void LibretroBridge_SetDirectories(IntPtr cbHandler, string systemDirectory, string saveDirectory, string coreDirectory, string coreAssetsDirectory);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_SetVideoSize(IntPtr cbHandler, int sz);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_GetVideo(IntPtr cbHandler, ref int width, ref int height, int[] videoBuf);
|
||||
public abstract void LibretroBridge_GetVideo(IntPtr cbHandler, out int width, out int height, int[] videoBuf);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract uint LibretroBridge_GetAudioSize(IntPtr cbHandler);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_GetAudio(IntPtr cbHandler, ref int numSamples, short[] sampleBuf);
|
||||
public abstract void LibretroBridge_GetAudio(IntPtr cbHandler, out int numSamples, short[] sampleBuf);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_SetInput(IntPtr cbHandler, LibretroApi.RETRO_DEVICE device, int port, short[] input);
|
||||
|
@ -479,6 +486,6 @@ namespace BizHawk.Emulation.Cores.Libretro
|
|||
}
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_GetRetroProcs(ref retro_procs cb_procs);
|
||||
public abstract void LibretroBridge_GetRetroProcs(out retro_procs cb_procs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,385 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.BizInvoke;
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
[PortedCore(CoreNames.Libretro, "CasualPokePlayer", singleInstance: true, isReleased: false)]
|
||||
[ServiceNotApplicable(new[] { typeof(IDriveLight) })]
|
||||
public partial class LibretroEmulator : IEmulator
|
||||
{
|
||||
private static readonly LibretroBridge bridge;
|
||||
private static readonly LibretroBridge.retro_procs cb_procs;
|
||||
|
||||
static LibretroEmulator()
|
||||
{
|
||||
var resolver = new DynamicLibraryImportResolver(
|
||||
OSTailoredCode.IsUnixHost ? "libLibretroBridge.so" : "libLibretroBridge.dll", hasLimitedLifetime: false);
|
||||
|
||||
bridge = BizInvoker.GetInvoker<LibretroBridge>(resolver, CallingConventionAdapters.Native);
|
||||
|
||||
cb_procs = new();
|
||||
bridge.LibretroBridge_GetRetroProcs(ref cb_procs);
|
||||
}
|
||||
|
||||
private readonly LibretroApi api;
|
||||
|
||||
private readonly BasicServiceProvider _serviceProvider;
|
||||
public IEmulatorServiceProvider ServiceProvider => _serviceProvider;
|
||||
|
||||
private readonly IntPtr cbHandler;
|
||||
|
||||
// please call this before calling any retro functions
|
||||
private void UpdateCallbackHandler()
|
||||
{
|
||||
bridge.LibretroBridge_SetGlobalCallbackHandler(cbHandler);
|
||||
}
|
||||
|
||||
public LibretroEmulator(CoreComm comm, IGameInfo game, string corePath, bool analysis = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
cbHandler = bridge.LibretroBridge_CreateCallbackHandler();
|
||||
|
||||
if (cbHandler == IntPtr.Zero)
|
||||
{
|
||||
throw new Exception("Failed to create callback handler!");
|
||||
}
|
||||
|
||||
UpdateCallbackHandler();
|
||||
|
||||
api = BizInvoker.GetInvoker<LibretroApi>(
|
||||
new DynamicLibraryImportResolver(corePath, hasLimitedLifetime: false), CallingConventionAdapters.Native);
|
||||
|
||||
_serviceProvider = new(this);
|
||||
Comm = comm;
|
||||
|
||||
if (api.retro_api_version() != 1)
|
||||
{
|
||||
throw new InvalidOperationException("Unsupported Libretro API version (or major error in interop)");
|
||||
}
|
||||
|
||||
var SystemDirectory = RetroString(Comm.CoreFileProvider.GetRetroSystemPath(game));
|
||||
var SaveDirectory = RetroString(Comm.CoreFileProvider.GetRetroSaveRAMDirectory(game));
|
||||
var CoreDirectory = RetroString(Path.GetDirectoryName(corePath));
|
||||
var CoreAssetsDirectory = RetroString(Path.GetDirectoryName(corePath));
|
||||
|
||||
bridge.LibretroBridge_SetDirectories(cbHandler, SystemDirectory, SaveDirectory, CoreDirectory, CoreAssetsDirectory);
|
||||
|
||||
ControllerDefinition = ControllerDef;
|
||||
|
||||
// check if we're just analysing the core and the core path matches the loaded core path anyways
|
||||
if (analysis && corePath == LoadedCorePath)
|
||||
{
|
||||
Description = CalculateDescription();
|
||||
Description.SupportsNoGame = LoadedCoreSupportsNoGame;
|
||||
// don't set init, we don't want the core deinit later
|
||||
}
|
||||
else
|
||||
{
|
||||
api.retro_set_environment(cb_procs.retro_environment_proc);
|
||||
Description = CalculateDescription();
|
||||
}
|
||||
|
||||
if (!analysis)
|
||||
{
|
||||
LoadedCorePath = corePath;
|
||||
LoadedCoreSupportsNoGame = Description.SupportsNoGame;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private class RetroData
|
||||
{
|
||||
private readonly GCHandle _handle;
|
||||
|
||||
public IntPtr PinnedData => _handle.AddrOfPinnedObject();
|
||||
public long Length { get; }
|
||||
|
||||
public RetroData(object o, long len = 0)
|
||||
{
|
||||
_handle = GCHandle.Alloc(o, GCHandleType.Pinned);
|
||||
Length = len;
|
||||
}
|
||||
|
||||
~RetroData() => _handle.Free();
|
||||
}
|
||||
|
||||
private byte[] RetroString(string managedString)
|
||||
{
|
||||
var s = Encoding.UTF8.GetBytes(managedString);
|
||||
var ret = new byte[s.Length + 1];
|
||||
Array.Copy(s, ret, s.Length);
|
||||
ret[s.Length] = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private LibretroApi.retro_system_av_info av_info;
|
||||
|
||||
private bool inited = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
if (inited)
|
||||
{
|
||||
api.retro_unload_game();
|
||||
api.retro_deinit();
|
||||
inited = false;
|
||||
}
|
||||
|
||||
bridge.LibretroBridge_DestroyCallbackHandler(cbHandler);
|
||||
|
||||
_blipL?.Dispose();
|
||||
_blipR?.Dispose();
|
||||
}
|
||||
|
||||
public RetroDescription Description { get; }
|
||||
|
||||
// single instance hacks
|
||||
private static string LoadedCorePath { get; set; }
|
||||
private static bool LoadedCoreSupportsNoGame { get; set; }
|
||||
|
||||
public enum RETRO_LOAD
|
||||
{
|
||||
DATA,
|
||||
PATH,
|
||||
NO_GAME,
|
||||
}
|
||||
|
||||
public bool LoadData(byte[] data, string id) => LoadHandler(RETRO_LOAD.DATA, new(RetroString(id)), new(data, data.LongLength));
|
||||
|
||||
public bool LoadPath(string path) => LoadHandler(RETRO_LOAD.PATH, new(RetroString(path)));
|
||||
|
||||
public bool LoadNoGame() => LoadHandler(RETRO_LOAD.NO_GAME);
|
||||
|
||||
private unsafe bool LoadHandler(RETRO_LOAD which, RetroData path = null, RetroData data = null)
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
var game = new LibretroApi.retro_game_info();
|
||||
var gameptr = (IntPtr)(&game);
|
||||
|
||||
if (which == RETRO_LOAD.NO_GAME)
|
||||
{
|
||||
gameptr = IntPtr.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
game.path = path.PinnedData;
|
||||
if (which == RETRO_LOAD.DATA)
|
||||
{
|
||||
game.data = data.PinnedData;
|
||||
game.size = data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
api.retro_init();
|
||||
bool success = api.retro_load_game(gameptr);
|
||||
if (!success)
|
||||
{
|
||||
api.retro_deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
var av = new LibretroApi.retro_system_av_info();
|
||||
api.retro_get_system_av_info((IntPtr)(&av));
|
||||
av_info = av;
|
||||
|
||||
api.retro_set_video_refresh(cb_procs.retro_video_refresh_proc);
|
||||
api.retro_set_audio_sample(cb_procs.retro_audio_sample_proc);
|
||||
api.retro_set_audio_sample_batch(cb_procs.retro_audio_sample_batch_proc);
|
||||
api.retro_set_input_poll(cb_procs.retro_input_poll_proc);
|
||||
api.retro_set_input_state(cb_procs.retro_input_state_proc);
|
||||
|
||||
_stateBuf = new byte[_stateLen = api.retro_serialize_size()];
|
||||
|
||||
_region = api.retro_get_region();
|
||||
|
||||
//this stuff can only happen after the game is loaded
|
||||
|
||||
//allocate a video buffer which will definitely be large enough
|
||||
InitVideoBuffer((int)av.geometry.base_width, (int)av.geometry.base_height, (int)(av.geometry.max_width * av.geometry.max_height));
|
||||
|
||||
// TODO: more precise
|
||||
VsyncNumerator = (int)(10000000 * av.timing.fps);
|
||||
VsyncDenominator = 10000000;
|
||||
|
||||
SetupResampler(av.timing.fps, av.timing.sample_rate);
|
||||
|
||||
InitMemoryDomains(); // im going to assume this should happen when a game is loaded
|
||||
|
||||
inited = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private LibretroApi.retro_message retro_msg = new();
|
||||
|
||||
private CoreComm Comm { get; }
|
||||
|
||||
private void FrameAdvancePrep(IController controller)
|
||||
{
|
||||
UpdateInput(controller);
|
||||
|
||||
if (controller.IsPressed("Reset"))
|
||||
{
|
||||
api.retro_reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void FrameAdvancePost(bool render, bool renderSound)
|
||||
{
|
||||
if (bridge.LibretroBridge_GetRetroGeometryInfo(cbHandler, ref av_info.geometry))
|
||||
{
|
||||
vidBuffer = new int[av_info.geometry.max_width * av_info.geometry.max_height];
|
||||
}
|
||||
|
||||
if (bridge.LibretroBridge_GetRetroTimingInfo(cbHandler, ref av_info.timing))
|
||||
{
|
||||
VsyncNumerator = (int)(10000000 * av_info.timing.fps);
|
||||
_blipL.SetRates(av_info.timing.sample_rate, 44100);
|
||||
_blipR.SetRates(av_info.timing.sample_rate, 44100);
|
||||
}
|
||||
|
||||
if (render)
|
||||
{
|
||||
UpdateVideoBuffer();
|
||||
}
|
||||
|
||||
ProcessSound();
|
||||
if (!renderSound)
|
||||
{
|
||||
DiscardSamples();
|
||||
}
|
||||
|
||||
bridge.LibretroBridge_GetRetroMessage(cbHandler, ref retro_msg);
|
||||
if (retro_msg.frames > 0)
|
||||
{
|
||||
Comm.Notify(Mershul.PtrToStringUtf8(retro_msg.msg));
|
||||
}
|
||||
}
|
||||
|
||||
public bool FrameAdvance(IController controller, bool render, bool renderSound = true)
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
FrameAdvancePrep(controller);
|
||||
api.retro_run();
|
||||
FrameAdvancePost(render, renderSound);
|
||||
|
||||
Frame++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static readonly LibretroControllerDef ControllerDef = new();
|
||||
|
||||
public class LibretroControllerDef : ControllerDefinition
|
||||
{
|
||||
private const string CAT_KEYBOARD = "RetroKeyboard";
|
||||
|
||||
public const string PFX_RETROPAD = "RetroPad ";
|
||||
|
||||
public LibretroControllerDef()
|
||||
: base(name: "LibRetro Controls"/*for compatibility*/)
|
||||
{
|
||||
for (var player = 1; player <= 2; player++) foreach (var button in new[] { "Up", "Down", "Left", "Right", "Select", "Start", "Y", "B", "X", "A", "L", "R" })
|
||||
{
|
||||
BoolButtons.Add($"P{player} {PFX_RETROPAD}{button}");
|
||||
}
|
||||
|
||||
BoolButtons.Add("Pointer Pressed");
|
||||
this.AddXYPair("Pointer {0}", AxisPairOrientation.RightAndUp, (-32767).RangeTo(32767), 0);
|
||||
|
||||
foreach (var s in new[] {
|
||||
"Backspace", "Tab", "Clear", "Return", "Pause", "Escape",
|
||||
"Space", "Exclaim", "QuoteDbl", "Hash", "Dollar", "Ampersand", "Quote", "LeftParen", "RightParen", "Asterisk", "Plus", "Comma", "Minus", "Period", "Slash",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
"Colon", "Semicolon", "Less", "Equals", "Greater", "Question", "At", "LeftBracket", "Backslash", "RightBracket", "Caret", "Underscore", "Backquote",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
"Delete",
|
||||
"KP0", "KP1", "KP2", "KP3", "KP4", "KP5", "KP6", "KP7", "KP8", "KP9",
|
||||
"KP_Period", "KP_Divide", "KP_Multiply", "KP_Minus", "KP_Plus", "KP_Enter", "KP_Equals",
|
||||
"Up", "Down", "Right", "Left", "Insert", "Home", "End", "PageUp", "PageDown",
|
||||
"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15",
|
||||
"NumLock", "CapsLock", "ScrollLock", "RShift", "LShift", "RCtrl", "LCtrl", "RAlt", "LAlt", "RMeta", "LMeta", "LSuper", "RSuper", "Mode", "Compose",
|
||||
"Help", "Print", "SysReq", "Break", "Menu", "Power", "Euro", "Undo"
|
||||
})
|
||||
{
|
||||
var buttonName = $"Key {s}";
|
||||
BoolButtons.Add(buttonName);
|
||||
CategoryLabels[buttonName] = CAT_KEYBOARD;
|
||||
}
|
||||
|
||||
BoolButtons.Add("Reset");
|
||||
|
||||
MakeImmutable();
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<IReadOnlyList<string>> GenOrderedControls()
|
||||
{
|
||||
// all this is to remove the keyboard buttons from P0 and put them in P3 so they appear at the end of the input display
|
||||
var players = base.GenOrderedControls().ToList();
|
||||
List<string> retroKeyboard = new();
|
||||
var p0 = (List<string>) players[0];
|
||||
for (var i = 0; i < p0.Count; /* incremented in body */)
|
||||
{
|
||||
var buttonName = p0[i];
|
||||
if (CategoryLabels.TryGetValue(buttonName, out var v) && v is CAT_KEYBOARD)
|
||||
{
|
||||
retroKeyboard.Add(buttonName);
|
||||
p0.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
players.Add(retroKeyboard);
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
public ControllerDefinition ControllerDefinition { get; }
|
||||
public int Frame { get; set; }
|
||||
public string SystemId => VSystemID.Raw.Libretro;
|
||||
public bool DeterministicEmulation => false;
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
LagCount = 0;
|
||||
IsLagFrame = false;
|
||||
}
|
||||
|
||||
public unsafe RetroDescription CalculateDescription()
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
var descr = new RetroDescription();
|
||||
var sys_info = new LibretroApi.retro_system_info();
|
||||
api.retro_get_system_info((IntPtr)(&sys_info));
|
||||
descr.LibraryName = Mershul.PtrToStringUtf8(sys_info.library_name);
|
||||
descr.LibraryVersion = Mershul.PtrToStringUtf8(sys_info.library_version);
|
||||
descr.ValidExtensions = Mershul.PtrToStringUtf8(sys_info.valid_extensions);
|
||||
descr.NeedsRomAsPath = sys_info.need_fullpath;
|
||||
descr.NeedsArchives = sys_info.block_extract;
|
||||
descr.SupportsNoGame = bridge.LibretroBridge_GetSupportsNoGame(cbHandler);
|
||||
return descr;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroHost : IEmulator
|
||||
{
|
||||
private readonly BasicServiceProvider _serviceProvider;
|
||||
public IEmulatorServiceProvider ServiceProvider => _serviceProvider;
|
||||
|
||||
private LibretroApi.retro_message retro_msg = default;
|
||||
|
||||
private readonly Action<string> _notify;
|
||||
|
||||
private void FrameAdvancePrep(IController controller)
|
||||
{
|
||||
UpdateInput(controller);
|
||||
|
||||
if (controller.IsPressed("Reset"))
|
||||
{
|
||||
api.retro_reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void FrameAdvancePost(bool render, bool renderSound)
|
||||
{
|
||||
if (bridge.LibretroBridge_GetRetroGeometryInfo(cbHandler, ref av_info.geometry))
|
||||
{
|
||||
_vidBuffer = new int[av_info.geometry.max_width * av_info.geometry.max_height];
|
||||
}
|
||||
|
||||
if (bridge.LibretroBridge_GetRetroTimingInfo(cbHandler, ref av_info.timing))
|
||||
{
|
||||
VsyncNumerator = checked((int)(10000000 * av_info.timing.fps));
|
||||
_blipL.SetRates(av_info.timing.sample_rate, 44100);
|
||||
_blipR.SetRates(av_info.timing.sample_rate, 44100);
|
||||
}
|
||||
|
||||
if (render)
|
||||
{
|
||||
UpdateVideoBuffer();
|
||||
}
|
||||
|
||||
ProcessSound();
|
||||
if (!renderSound)
|
||||
{
|
||||
DiscardSamples();
|
||||
}
|
||||
|
||||
bridge.LibretroBridge_GetRetroMessage(cbHandler, out retro_msg);
|
||||
if (retro_msg.frames > 0)
|
||||
{
|
||||
// TODO: pass frames for duration?
|
||||
_notify(Mershul.PtrToStringUtf8(retro_msg.msg));
|
||||
}
|
||||
|
||||
Frame++;
|
||||
}
|
||||
|
||||
public bool FrameAdvance(IController controller, bool render, bool renderSound = true)
|
||||
{
|
||||
FrameAdvancePrep(controller);
|
||||
api.retro_run();
|
||||
FrameAdvancePost(render, renderSound);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static readonly LibretroControllerDef ControllerDef = new();
|
||||
|
||||
public class LibretroControllerDef : ControllerDefinition
|
||||
{
|
||||
private const string CAT_KEYBOARD = "RetroKeyboard";
|
||||
|
||||
public const string PFX_RETROPAD = "RetroPad ";
|
||||
|
||||
public LibretroControllerDef()
|
||||
: base(name: "LibRetro Controls"/*for compatibility*/)
|
||||
{
|
||||
for (var player = 1; player <= 2; player++) foreach (var button in new[] { "Up", "Down", "Left", "Right", "Select", "Start", "Y", "B", "X", "A", "L", "R", "L2", "R2", "L3", "R3", })
|
||||
{
|
||||
BoolButtons.Add($"P{player} {PFX_RETROPAD}{button}");
|
||||
}
|
||||
|
||||
BoolButtons.Add("Pointer Pressed");
|
||||
this.AddXYPair("Pointer {0}", AxisPairOrientation.RightAndUp, (-32767).RangeTo(32767), 0);
|
||||
|
||||
foreach (var s in new[] {
|
||||
"Backspace", "Tab", "Clear", "Return", "Pause", "Escape",
|
||||
"Space", "Exclaim", "QuoteDbl", "Hash", "Dollar", "Ampersand", "Quote", "LeftParen", "RightParen", "Asterisk", "Plus", "Comma", "Minus", "Period", "Slash",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
"Colon", "Semicolon", "Less", "Equals", "Greater", "Question", "At", "LeftBracket", "Backslash", "RightBracket", "Caret", "Underscore", "Backquote",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
"Delete",
|
||||
"KP0", "KP1", "KP2", "KP3", "KP4", "KP5", "KP6", "KP7", "KP8", "KP9",
|
||||
"KP_Period", "KP_Divide", "KP_Multiply", "KP_Minus", "KP_Plus", "KP_Enter", "KP_Equals",
|
||||
"Up", "Down", "Right", "Left", "Insert", "Home", "End", "PageUp", "PageDown",
|
||||
"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15",
|
||||
"NumLock", "CapsLock", "ScrollLock", "RShift", "LShift", "RCtrl", "LCtrl", "RAlt", "LAlt", "RMeta", "LMeta", "LSuper", "RSuper", "Mode", "Compose",
|
||||
"Help", "Print", "SysReq", "Break", "Menu", "Power", "Euro", "Undo"
|
||||
})
|
||||
{
|
||||
var buttonName = $"Key {s}";
|
||||
BoolButtons.Add(buttonName);
|
||||
CategoryLabels[buttonName] = CAT_KEYBOARD;
|
||||
}
|
||||
|
||||
BoolButtons.Add("Reset");
|
||||
|
||||
MakeImmutable();
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<IReadOnlyList<string>> GenOrderedControls()
|
||||
{
|
||||
// all this is to remove the keyboard buttons from P0 and put them in P3 so they appear at the end of the input display
|
||||
var players = base.GenOrderedControls().ToList();
|
||||
List<string> retroKeyboard = new();
|
||||
var p0 = (List<string>) players[0];
|
||||
for (var i = 0; i < p0.Count; /* incremented in body */)
|
||||
{
|
||||
var buttonName = p0[i];
|
||||
if (CategoryLabels.TryGetValue(buttonName, out var v) && v is CAT_KEYBOARD)
|
||||
{
|
||||
retroKeyboard.Add(buttonName);
|
||||
p0.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
players.Add(retroKeyboard);
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
public ControllerDefinition ControllerDefinition { get; }
|
||||
public int Frame { get; set; }
|
||||
public string SystemId => VSystemID.Raw.Libretro;
|
||||
public bool DeterministicEmulation => false;
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
LagCount = 0;
|
||||
IsLagFrame = false;
|
||||
}
|
||||
|
||||
private bool inited = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (inited)
|
||||
{
|
||||
api.retro_unload_game();
|
||||
api.retro_deinit();
|
||||
inited = false;
|
||||
}
|
||||
|
||||
bridge.LibretroBridge_DestroyCallbackHandler(cbHandler);
|
||||
|
||||
_blipL?.Dispose();
|
||||
_blipL = null;
|
||||
_blipR?.Dispose();
|
||||
_blipR = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,248 +1,249 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
using static BizHawk.Emulation.Cores.Libretro.LibretroApi;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : IInputPollable
|
||||
public partial class LibretroHost : IInputPollable
|
||||
{
|
||||
// TBD
|
||||
// we could actually remove IInputPollable here
|
||||
// although that would prevent tastudio use entirely
|
||||
// maybe better overall as libretro has no place for movies
|
||||
|
||||
public int LagCount { get; set; }
|
||||
public bool IsLagFrame { get; set; }
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public IInputCallbackSystem InputCallbacks => throw new NotImplementedException();
|
||||
|
||||
// todo: make this better
|
||||
private readonly short[] _joypad0States = new short[(int)RETRO_DEVICE_ID_JOYPAD.LAST];
|
||||
private readonly short[] _joypad1States = new short[(int)RETRO_DEVICE_ID_JOYPAD.LAST];
|
||||
private readonly short[] _pointerStates = new short[(int)RETRO_DEVICE_ID_POINTER.LAST];
|
||||
private readonly short[] _keyStates = new short[(int)RETRO_KEY.LAST];
|
||||
|
||||
// todo
|
||||
// implement more input types
|
||||
// limit inputs according to user selection / core limitations (something with RETRO_ENVIRONMENT_SET_CONTROLLER_INFO?)
|
||||
void UpdateInput(IController controller)
|
||||
{
|
||||
short[] input = new short[(int)LibretroApi.RETRO_DEVICE_ID_JOYPAD.LAST];
|
||||
// joypad port 0
|
||||
for (uint i = 0; i < input.Length; i++)
|
||||
SetInputs(controller, RETRO_DEVICE.JOYPAD, 0, _joypad0States);
|
||||
SetInputs(controller, RETRO_DEVICE.JOYPAD, 1, _joypad1States);
|
||||
SetInputs(controller, RETRO_DEVICE.POINTER, 0, _pointerStates);
|
||||
SetInputs(controller, RETRO_DEVICE.KEYBOARD, 0, _keyStates);
|
||||
}
|
||||
|
||||
private void SetInputs(IController controller, RETRO_DEVICE device, int port, short[] inputBuffer)
|
||||
{
|
||||
// index is 0 always except for ANALOG devices, which we don't handle yet (impl TBD)
|
||||
for (int i = 0; i < inputBuffer.Length; i++)
|
||||
{
|
||||
input[i] = retro_input_state(controller, 0, (uint)LibretroApi.RETRO_DEVICE.JOYPAD, 0, i);
|
||||
inputBuffer[i] = InputState(controller, port, device, 0, i);
|
||||
}
|
||||
bridge.LibretroBridge_SetInput(cbHandler, LibretroApi.RETRO_DEVICE.JOYPAD, 0, input);
|
||||
// joypad port 1
|
||||
for (uint i = 0; i < input.Length; i++)
|
||||
{
|
||||
input[i] = retro_input_state(controller, 1, (uint)LibretroApi.RETRO_DEVICE.JOYPAD, 0, i);
|
||||
}
|
||||
bridge.LibretroBridge_SetInput(cbHandler, LibretroApi.RETRO_DEVICE.JOYPAD, 1, input);
|
||||
input = new short[(int)LibretroApi.RETRO_DEVICE_ID_POINTER.LAST];
|
||||
// pointer port 0
|
||||
for (uint i = 0; i < input.Length; i++)
|
||||
{
|
||||
input[i] = retro_input_state(controller, 0, (uint)LibretroApi.RETRO_DEVICE.POINTER, 0, i);
|
||||
}
|
||||
bridge.LibretroBridge_SetInput(cbHandler, LibretroApi.RETRO_DEVICE.POINTER, 0, input);
|
||||
input = new short[(int)LibretroApi.RETRO_KEY.LAST];
|
||||
// keyboard port 0
|
||||
for (uint i = 0; i < input.Length; i++)
|
||||
{
|
||||
input[i] = retro_input_state(controller, 0, (uint)LibretroApi.RETRO_DEVICE.KEYBOARD, 0, i);
|
||||
}
|
||||
bridge.LibretroBridge_SetInput(cbHandler, LibretroApi.RETRO_DEVICE.KEYBOARD, 0, input);
|
||||
bridge.LibretroBridge_SetInput(cbHandler, device, port, inputBuffer);
|
||||
}
|
||||
|
||||
//meanings (they are kind of hazy, but once we're done implementing this it will be completely defined by example)
|
||||
//port = console physical port?
|
||||
//device = logical device type
|
||||
//index = sub device index? (multitap?)
|
||||
//index = sub device index? (multitap?) (only actually used for the ANALOG device however?)
|
||||
//id = button id (or key id)
|
||||
private static short retro_input_state(IController controller, uint port, uint device, uint index, uint id)
|
||||
private static short InputState(IController controller, int port, RETRO_DEVICE device, int index, int id)
|
||||
{
|
||||
//helpful debugging
|
||||
//Console.WriteLine("{0} {1} {2} {3}", port, device, index, id);
|
||||
|
||||
switch ((LibretroApi.RETRO_DEVICE)device)
|
||||
switch (device)
|
||||
{
|
||||
case LibretroApi.RETRO_DEVICE.POINTER:
|
||||
case RETRO_DEVICE.POINTER:
|
||||
{
|
||||
return (LibretroApi.RETRO_DEVICE_ID_POINTER)id switch
|
||||
return (RETRO_DEVICE_ID_POINTER)id switch
|
||||
{
|
||||
LibretroApi.RETRO_DEVICE_ID_POINTER.X => (short)controller.AxisValue("Pointer X"),
|
||||
LibretroApi.RETRO_DEVICE_ID_POINTER.Y => (short)controller.AxisValue("Pointer Y"),
|
||||
LibretroApi.RETRO_DEVICE_ID_POINTER.PRESSED => (short)(controller.IsPressed("Pointer Pressed") ? 1 : 0),
|
||||
_ => 0,
|
||||
RETRO_DEVICE_ID_POINTER.X => (short)controller.AxisValue("Pointer X"),
|
||||
RETRO_DEVICE_ID_POINTER.Y => (short)controller.AxisValue("Pointer Y"),
|
||||
RETRO_DEVICE_ID_POINTER.PRESSED => (short)(controller.IsPressed("Pointer Pressed") ? 1 : 0),
|
||||
RETRO_DEVICE_ID_POINTER.COUNT => (short)(controller.IsPressed("Pointer Pressed") ? 1 : 0), // i think this means "number of presses"? we don't support multitouch anyways so
|
||||
_ => throw new InvalidOperationException($"Invalid {nameof(RETRO_DEVICE_ID_POINTER)}")
|
||||
};
|
||||
}
|
||||
|
||||
case LibretroApi.RETRO_DEVICE.KEYBOARD:
|
||||
case RETRO_DEVICE.KEYBOARD:
|
||||
{
|
||||
string button = (LibretroApi.RETRO_KEY)id switch
|
||||
var button = (RETRO_KEY)id switch
|
||||
{
|
||||
LibretroApi.RETRO_KEY.BACKSPACE => "Backspace",
|
||||
LibretroApi.RETRO_KEY.TAB => "Tab",
|
||||
LibretroApi.RETRO_KEY.CLEAR => "Clear",
|
||||
LibretroApi.RETRO_KEY.RETURN => "Return",
|
||||
LibretroApi.RETRO_KEY.PAUSE => "Pause",
|
||||
LibretroApi.RETRO_KEY.ESCAPE => "Escape",
|
||||
LibretroApi.RETRO_KEY.SPACE => "Space",
|
||||
LibretroApi.RETRO_KEY.EXCLAIM => "Exclaim",
|
||||
LibretroApi.RETRO_KEY.QUOTEDBL => "QuoteDbl",
|
||||
LibretroApi.RETRO_KEY.HASH => "Hash",
|
||||
LibretroApi.RETRO_KEY.DOLLAR => "Dollar",
|
||||
LibretroApi.RETRO_KEY.AMPERSAND => "Ampersand",
|
||||
LibretroApi.RETRO_KEY.QUOTE => "Quote",
|
||||
LibretroApi.RETRO_KEY.LEFTPAREN => "LeftParen",
|
||||
LibretroApi.RETRO_KEY.RIGHTPAREN => "RightParen",
|
||||
LibretroApi.RETRO_KEY.ASTERISK => "Asterisk",
|
||||
LibretroApi.RETRO_KEY.PLUS => "Plus",
|
||||
LibretroApi.RETRO_KEY.COMMA => "Comma",
|
||||
LibretroApi.RETRO_KEY.MINUS => "Minus",
|
||||
LibretroApi.RETRO_KEY.PERIOD => "Period",
|
||||
LibretroApi.RETRO_KEY.SLASH => "Slash",
|
||||
LibretroApi.RETRO_KEY._0 => "0",
|
||||
LibretroApi.RETRO_KEY._1 => "1",
|
||||
LibretroApi.RETRO_KEY._2 => "2",
|
||||
LibretroApi.RETRO_KEY._3 => "3",
|
||||
LibretroApi.RETRO_KEY._4 => "4",
|
||||
LibretroApi.RETRO_KEY._5 => "5",
|
||||
LibretroApi.RETRO_KEY._6 => "6",
|
||||
LibretroApi.RETRO_KEY._7 => "7",
|
||||
LibretroApi.RETRO_KEY._8 => "8",
|
||||
LibretroApi.RETRO_KEY._9 => "9",
|
||||
LibretroApi.RETRO_KEY.COLON => "Colon",
|
||||
LibretroApi.RETRO_KEY.SEMICOLON => "Semicolon",
|
||||
LibretroApi.RETRO_KEY.LESS => "Less",
|
||||
LibretroApi.RETRO_KEY.EQUALS => "Equals",
|
||||
LibretroApi.RETRO_KEY.GREATER => "Greater",
|
||||
LibretroApi.RETRO_KEY.QUESTION => "Question",
|
||||
LibretroApi.RETRO_KEY.AT => "At",
|
||||
LibretroApi.RETRO_KEY.LEFTBRACKET => "LeftBracket",
|
||||
LibretroApi.RETRO_KEY.BACKSLASH => "Backslash",
|
||||
LibretroApi.RETRO_KEY.RIGHTBRACKET => "RightBracket",
|
||||
LibretroApi.RETRO_KEY.CARET => "Caret",
|
||||
LibretroApi.RETRO_KEY.UNDERSCORE => "Underscore",
|
||||
LibretroApi.RETRO_KEY.BACKQUOTE => "Backquote",
|
||||
LibretroApi.RETRO_KEY.a => "A",
|
||||
LibretroApi.RETRO_KEY.b => "B",
|
||||
LibretroApi.RETRO_KEY.c => "C",
|
||||
LibretroApi.RETRO_KEY.d => "D",
|
||||
LibretroApi.RETRO_KEY.e => "E",
|
||||
LibretroApi.RETRO_KEY.f => "F",
|
||||
LibretroApi.RETRO_KEY.g => "G",
|
||||
LibretroApi.RETRO_KEY.h => "H",
|
||||
LibretroApi.RETRO_KEY.i => "I",
|
||||
LibretroApi.RETRO_KEY.j => "J",
|
||||
LibretroApi.RETRO_KEY.k => "K",
|
||||
LibretroApi.RETRO_KEY.l => "L",
|
||||
LibretroApi.RETRO_KEY.m => "M",
|
||||
LibretroApi.RETRO_KEY.n => "N",
|
||||
LibretroApi.RETRO_KEY.o => "O",
|
||||
LibretroApi.RETRO_KEY.p => "P",
|
||||
LibretroApi.RETRO_KEY.q => "Q",
|
||||
LibretroApi.RETRO_KEY.r => "R",
|
||||
LibretroApi.RETRO_KEY.s => "S",
|
||||
LibretroApi.RETRO_KEY.t => "T",
|
||||
LibretroApi.RETRO_KEY.u => "U",
|
||||
LibretroApi.RETRO_KEY.v => "V",
|
||||
LibretroApi.RETRO_KEY.w => "W",
|
||||
LibretroApi.RETRO_KEY.x => "X",
|
||||
LibretroApi.RETRO_KEY.y => "Y",
|
||||
LibretroApi.RETRO_KEY.z => "Z",
|
||||
LibretroApi.RETRO_KEY.DELETE => "Delete",
|
||||
LibretroApi.RETRO_KEY.KP0 => "KP0",
|
||||
LibretroApi.RETRO_KEY.KP1 => "KP1",
|
||||
LibretroApi.RETRO_KEY.KP2 => "KP2",
|
||||
LibretroApi.RETRO_KEY.KP3 => "KP3",
|
||||
LibretroApi.RETRO_KEY.KP4 => "KP4",
|
||||
LibretroApi.RETRO_KEY.KP5 => "KP5",
|
||||
LibretroApi.RETRO_KEY.KP6 => "KP6",
|
||||
LibretroApi.RETRO_KEY.KP7 => "KP7",
|
||||
LibretroApi.RETRO_KEY.KP8 => "KP8",
|
||||
LibretroApi.RETRO_KEY.KP9 => "KP9",
|
||||
LibretroApi.RETRO_KEY.KP_PERIOD => "KP_Period",
|
||||
LibretroApi.RETRO_KEY.KP_DIVIDE => "KP_Divide",
|
||||
LibretroApi.RETRO_KEY.KP_MULTIPLY => "KP_Multiply",
|
||||
LibretroApi.RETRO_KEY.KP_MINUS => "KP_Minus",
|
||||
LibretroApi.RETRO_KEY.KP_PLUS => "KP_Plus",
|
||||
LibretroApi.RETRO_KEY.KP_ENTER => "KP_Enter",
|
||||
LibretroApi.RETRO_KEY.KP_EQUALS => "KP_Equals",
|
||||
LibretroApi.RETRO_KEY.UP => "Up",
|
||||
LibretroApi.RETRO_KEY.DOWN => "Down",
|
||||
LibretroApi.RETRO_KEY.RIGHT => "Right",
|
||||
LibretroApi.RETRO_KEY.LEFT => "Left",
|
||||
LibretroApi.RETRO_KEY.INSERT => "Insert",
|
||||
LibretroApi.RETRO_KEY.HOME => "Home",
|
||||
LibretroApi.RETRO_KEY.END => "End",
|
||||
LibretroApi.RETRO_KEY.PAGEUP => "PageUp",
|
||||
LibretroApi.RETRO_KEY.PAGEDOWN => "PageDown",
|
||||
LibretroApi.RETRO_KEY.F1 => "F1",
|
||||
LibretroApi.RETRO_KEY.F2 => "F2",
|
||||
LibretroApi.RETRO_KEY.F3 => "F3",
|
||||
LibretroApi.RETRO_KEY.F4 => "F4",
|
||||
LibretroApi.RETRO_KEY.F5 => "F5",
|
||||
LibretroApi.RETRO_KEY.F6 => "F6",
|
||||
LibretroApi.RETRO_KEY.F7 => "F7",
|
||||
LibretroApi.RETRO_KEY.F8 => "F8",
|
||||
LibretroApi.RETRO_KEY.F9 => "F9",
|
||||
LibretroApi.RETRO_KEY.F10 => "F10",
|
||||
LibretroApi.RETRO_KEY.F11 => "F11",
|
||||
LibretroApi.RETRO_KEY.F12 => "F12",
|
||||
LibretroApi.RETRO_KEY.F13 => "F13",
|
||||
LibretroApi.RETRO_KEY.F14 => "F14",
|
||||
LibretroApi.RETRO_KEY.F15 => "F15",
|
||||
LibretroApi.RETRO_KEY.NUMLOCK => "NumLock",
|
||||
LibretroApi.RETRO_KEY.CAPSLOCK => "CapsLock",
|
||||
LibretroApi.RETRO_KEY.SCROLLOCK => "ScrollLock",
|
||||
LibretroApi.RETRO_KEY.RSHIFT => "RShift",
|
||||
LibretroApi.RETRO_KEY.LSHIFT => "LShift",
|
||||
LibretroApi.RETRO_KEY.RCTRL => "RCtrl",
|
||||
LibretroApi.RETRO_KEY.LCTRL => "LCtrl",
|
||||
LibretroApi.RETRO_KEY.RALT => "RAlt",
|
||||
LibretroApi.RETRO_KEY.LALT => "LAlt",
|
||||
LibretroApi.RETRO_KEY.RMETA => "RMeta",
|
||||
LibretroApi.RETRO_KEY.LMETA => "LMeta",
|
||||
LibretroApi.RETRO_KEY.LSUPER => "LSuper",
|
||||
LibretroApi.RETRO_KEY.RSUPER => "RSuper",
|
||||
LibretroApi.RETRO_KEY.MODE => "Mode",
|
||||
LibretroApi.RETRO_KEY.COMPOSE => "Compose",
|
||||
LibretroApi.RETRO_KEY.HELP => "Help",
|
||||
LibretroApi.RETRO_KEY.PRINT => "Print",
|
||||
LibretroApi.RETRO_KEY.SYSREQ => "SysReq",
|
||||
LibretroApi.RETRO_KEY.BREAK => "Break",
|
||||
LibretroApi.RETRO_KEY.MENU => "Menu",
|
||||
LibretroApi.RETRO_KEY.POWER => "Power",
|
||||
LibretroApi.RETRO_KEY.EURO => "Euro",
|
||||
LibretroApi.RETRO_KEY.UNDO => "Undo",
|
||||
_ => "",
|
||||
RETRO_KEY.BACKSPACE => "Backspace",
|
||||
RETRO_KEY.TAB => "Tab",
|
||||
RETRO_KEY.CLEAR => "Clear",
|
||||
RETRO_KEY.RETURN => "Return",
|
||||
RETRO_KEY.PAUSE => "Pause",
|
||||
RETRO_KEY.ESCAPE => "Escape",
|
||||
RETRO_KEY.SPACE => "Space",
|
||||
RETRO_KEY.EXCLAIM => "Exclaim",
|
||||
RETRO_KEY.QUOTEDBL => "QuoteDbl",
|
||||
RETRO_KEY.HASH => "Hash",
|
||||
RETRO_KEY.DOLLAR => "Dollar",
|
||||
RETRO_KEY.AMPERSAND => "Ampersand",
|
||||
RETRO_KEY.QUOTE => "Quote",
|
||||
RETRO_KEY.LEFTPAREN => "LeftParen",
|
||||
RETRO_KEY.RIGHTPAREN => "RightParen",
|
||||
RETRO_KEY.ASTERISK => "Asterisk",
|
||||
RETRO_KEY.PLUS => "Plus",
|
||||
RETRO_KEY.COMMA => "Comma",
|
||||
RETRO_KEY.MINUS => "Minus",
|
||||
RETRO_KEY.PERIOD => "Period",
|
||||
RETRO_KEY.SLASH => "Slash",
|
||||
RETRO_KEY._0 => "0",
|
||||
RETRO_KEY._1 => "1",
|
||||
RETRO_KEY._2 => "2",
|
||||
RETRO_KEY._3 => "3",
|
||||
RETRO_KEY._4 => "4",
|
||||
RETRO_KEY._5 => "5",
|
||||
RETRO_KEY._6 => "6",
|
||||
RETRO_KEY._7 => "7",
|
||||
RETRO_KEY._8 => "8",
|
||||
RETRO_KEY._9 => "9",
|
||||
RETRO_KEY.COLON => "Colon",
|
||||
RETRO_KEY.SEMICOLON => "Semicolon",
|
||||
RETRO_KEY.LESS => "Less",
|
||||
RETRO_KEY.EQUALS => "Equals",
|
||||
RETRO_KEY.GREATER => "Greater",
|
||||
RETRO_KEY.QUESTION => "Question",
|
||||
RETRO_KEY.AT => "At",
|
||||
RETRO_KEY.LEFTBRACKET => "LeftBracket",
|
||||
RETRO_KEY.BACKSLASH => "Backslash",
|
||||
RETRO_KEY.RIGHTBRACKET => "RightBracket",
|
||||
RETRO_KEY.CARET => "Caret",
|
||||
RETRO_KEY.UNDERSCORE => "Underscore",
|
||||
RETRO_KEY.BACKQUOTE => "Backquote",
|
||||
RETRO_KEY.a => "A",
|
||||
RETRO_KEY.b => "B",
|
||||
RETRO_KEY.c => "C",
|
||||
RETRO_KEY.d => "D",
|
||||
RETRO_KEY.e => "E",
|
||||
RETRO_KEY.f => "F",
|
||||
RETRO_KEY.g => "G",
|
||||
RETRO_KEY.h => "H",
|
||||
RETRO_KEY.i => "I",
|
||||
RETRO_KEY.j => "J",
|
||||
RETRO_KEY.k => "K",
|
||||
RETRO_KEY.l => "L",
|
||||
RETRO_KEY.m => "M",
|
||||
RETRO_KEY.n => "N",
|
||||
RETRO_KEY.o => "O",
|
||||
RETRO_KEY.p => "P",
|
||||
RETRO_KEY.q => "Q",
|
||||
RETRO_KEY.r => "R",
|
||||
RETRO_KEY.s => "S",
|
||||
RETRO_KEY.t => "T",
|
||||
RETRO_KEY.u => "U",
|
||||
RETRO_KEY.v => "V",
|
||||
RETRO_KEY.w => "W",
|
||||
RETRO_KEY.x => "X",
|
||||
RETRO_KEY.y => "Y",
|
||||
RETRO_KEY.z => "Z",
|
||||
RETRO_KEY.DELETE => "Delete",
|
||||
RETRO_KEY.KP0 => "KP0",
|
||||
RETRO_KEY.KP1 => "KP1",
|
||||
RETRO_KEY.KP2 => "KP2",
|
||||
RETRO_KEY.KP3 => "KP3",
|
||||
RETRO_KEY.KP4 => "KP4",
|
||||
RETRO_KEY.KP5 => "KP5",
|
||||
RETRO_KEY.KP6 => "KP6",
|
||||
RETRO_KEY.KP7 => "KP7",
|
||||
RETRO_KEY.KP8 => "KP8",
|
||||
RETRO_KEY.KP9 => "KP9",
|
||||
RETRO_KEY.KP_PERIOD => "KP_Period",
|
||||
RETRO_KEY.KP_DIVIDE => "KP_Divide",
|
||||
RETRO_KEY.KP_MULTIPLY => "KP_Multiply",
|
||||
RETRO_KEY.KP_MINUS => "KP_Minus",
|
||||
RETRO_KEY.KP_PLUS => "KP_Plus",
|
||||
RETRO_KEY.KP_ENTER => "KP_Enter",
|
||||
RETRO_KEY.KP_EQUALS => "KP_Equals",
|
||||
RETRO_KEY.UP => "Up",
|
||||
RETRO_KEY.DOWN => "Down",
|
||||
RETRO_KEY.RIGHT => "Right",
|
||||
RETRO_KEY.LEFT => "Left",
|
||||
RETRO_KEY.INSERT => "Insert",
|
||||
RETRO_KEY.HOME => "Home",
|
||||
RETRO_KEY.END => "End",
|
||||
RETRO_KEY.PAGEUP => "PageUp",
|
||||
RETRO_KEY.PAGEDOWN => "PageDown",
|
||||
RETRO_KEY.F1 => "F1",
|
||||
RETRO_KEY.F2 => "F2",
|
||||
RETRO_KEY.F3 => "F3",
|
||||
RETRO_KEY.F4 => "F4",
|
||||
RETRO_KEY.F5 => "F5",
|
||||
RETRO_KEY.F6 => "F6",
|
||||
RETRO_KEY.F7 => "F7",
|
||||
RETRO_KEY.F8 => "F8",
|
||||
RETRO_KEY.F9 => "F9",
|
||||
RETRO_KEY.F10 => "F10",
|
||||
RETRO_KEY.F11 => "F11",
|
||||
RETRO_KEY.F12 => "F12",
|
||||
RETRO_KEY.F13 => "F13",
|
||||
RETRO_KEY.F14 => "F14",
|
||||
RETRO_KEY.F15 => "F15",
|
||||
RETRO_KEY.NUMLOCK => "NumLock",
|
||||
RETRO_KEY.CAPSLOCK => "CapsLock",
|
||||
RETRO_KEY.SCROLLOCK => "ScrollLock",
|
||||
RETRO_KEY.RSHIFT => "RShift",
|
||||
RETRO_KEY.LSHIFT => "LShift",
|
||||
RETRO_KEY.RCTRL => "RCtrl",
|
||||
RETRO_KEY.LCTRL => "LCtrl",
|
||||
RETRO_KEY.RALT => "RAlt",
|
||||
RETRO_KEY.LALT => "LAlt",
|
||||
RETRO_KEY.RMETA => "RMeta",
|
||||
RETRO_KEY.LMETA => "LMeta",
|
||||
RETRO_KEY.LSUPER => "LSuper",
|
||||
RETRO_KEY.RSUPER => "RSuper",
|
||||
RETRO_KEY.MODE => "Mode",
|
||||
RETRO_KEY.COMPOSE => "Compose",
|
||||
RETRO_KEY.HELP => "Help",
|
||||
RETRO_KEY.PRINT => "Print",
|
||||
RETRO_KEY.SYSREQ => "SysReq",
|
||||
RETRO_KEY.BREAK => "Break",
|
||||
RETRO_KEY.MENU => "Menu",
|
||||
RETRO_KEY.POWER => "Power",
|
||||
RETRO_KEY.EURO => "Euro",
|
||||
RETRO_KEY.UNDO => "Undo",
|
||||
_ => "", // annoyingly a lot of gaps are present in RETRO_KEY, so can't just throw here
|
||||
};
|
||||
|
||||
return (short)(controller.IsPressed("Key " + button) ? 1 : 0);
|
||||
}
|
||||
|
||||
case LibretroApi.RETRO_DEVICE.JOYPAD:
|
||||
case RETRO_DEVICE.JOYPAD:
|
||||
{
|
||||
string button = (LibretroApi.RETRO_DEVICE_ID_JOYPAD)id switch
|
||||
var button = (RETRO_DEVICE_ID_JOYPAD)id switch
|
||||
{
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.A => "A",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.B => "B",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.X => "X",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.Y => "Y",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.UP => "Up",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.DOWN => "Down",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.LEFT => "Left",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.RIGHT => "Right",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.L => "L",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.R => "R",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.SELECT => "Select",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.START => "Start",
|
||||
_ => "",
|
||||
RETRO_DEVICE_ID_JOYPAD.A => "A",
|
||||
RETRO_DEVICE_ID_JOYPAD.B => "B",
|
||||
RETRO_DEVICE_ID_JOYPAD.X => "X",
|
||||
RETRO_DEVICE_ID_JOYPAD.Y => "Y",
|
||||
RETRO_DEVICE_ID_JOYPAD.UP => "Up",
|
||||
RETRO_DEVICE_ID_JOYPAD.DOWN => "Down",
|
||||
RETRO_DEVICE_ID_JOYPAD.LEFT => "Left",
|
||||
RETRO_DEVICE_ID_JOYPAD.RIGHT => "Right",
|
||||
RETRO_DEVICE_ID_JOYPAD.L => "L",
|
||||
RETRO_DEVICE_ID_JOYPAD.R => "R",
|
||||
RETRO_DEVICE_ID_JOYPAD.SELECT => "Select",
|
||||
RETRO_DEVICE_ID_JOYPAD.START => "Start",
|
||||
RETRO_DEVICE_ID_JOYPAD.L2 => "L2",
|
||||
RETRO_DEVICE_ID_JOYPAD.R2 => "R2",
|
||||
RETRO_DEVICE_ID_JOYPAD.L3 => "L3",
|
||||
RETRO_DEVICE_ID_JOYPAD.R3 => "R3",
|
||||
_ => throw new InvalidOperationException($"Invalid {nameof(RETRO_DEVICE_ID_JOYPAD)}"),
|
||||
};
|
||||
|
||||
return (short)(GetButton(controller, port + 1, "RetroPad", button) ? 1 : 0);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
throw new InvalidOperationException($"Invalid or unimplemented {nameof(RETRO_DEVICE)}");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetButton(IController controller, uint pnum, string type, string button)
|
||||
private static bool GetButton(IController controller, int pnum, string type, string button)
|
||||
{
|
||||
string key = $"P{pnum} {type} {button}";
|
||||
var key = $"P{pnum} {type} {button}";
|
||||
return controller.IsPressed(key);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,22 @@ using BizHawk.Emulation.Common;
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator
|
||||
public partial class LibretroHost
|
||||
{
|
||||
private readonly List<MemoryDomain> _memoryDomains = new();
|
||||
private IMemoryDomains MemoryDomains { get; set; }
|
||||
private MemoryDomainList _memoryDomains;
|
||||
|
||||
private static readonly IReadOnlyDictionary<LibretroApi.RETRO_MEMORY, string> _domainNames
|
||||
= new Dictionary<LibretroApi.RETRO_MEMORY, string>()
|
||||
{
|
||||
[LibretroApi.RETRO_MEMORY.SAVE_RAM] = "SaveRAM",
|
||||
[LibretroApi.RETRO_MEMORY.RTC] = "RTC",
|
||||
[LibretroApi.RETRO_MEMORY.SYSTEM_RAM] = "RAM",
|
||||
[LibretroApi.RETRO_MEMORY.VIDEO_RAM] = "VRAM",
|
||||
};
|
||||
|
||||
private void InitMemoryDomains()
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
List<MemoryDomain> md = new();
|
||||
|
||||
foreach (LibretroApi.RETRO_MEMORY m in Enum.GetValues(typeof(LibretroApi.RETRO_MEMORY)))
|
||||
{
|
||||
|
@ -20,8 +28,9 @@ namespace BizHawk.Emulation.Cores.Libretro
|
|||
var sz = api.retro_get_memory_size(m);
|
||||
if (mem != IntPtr.Zero && sz > 0)
|
||||
{
|
||||
var d = new MemoryDomainIntPtr(Enum.GetName(m.GetType(), m), MemoryDomain.Endian.Little, mem, sz, true, 1);
|
||||
_memoryDomains.Add(d);
|
||||
MemoryDomainIntPtr d = new(_domainNames[m], MemoryDomain.Endian.Unknown, mem, sz, true, 1);
|
||||
md.Add(d);
|
||||
|
||||
if (m is LibretroApi.RETRO_MEMORY.SAVE_RAM or LibretroApi.RETRO_MEMORY.RTC)
|
||||
{
|
||||
_saveramAreas.Add(d);
|
||||
|
@ -30,8 +39,19 @@ namespace BizHawk.Emulation.Cores.Libretro
|
|||
}
|
||||
}
|
||||
|
||||
MemoryDomains = new MemoryDomainList(_memoryDomains);
|
||||
_serviceProvider.Register(MemoryDomains);
|
||||
// no domains to register...
|
||||
if (md.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_memoryDomains = new(md);
|
||||
// if RAM is somehow not available, _memoryDomains["RAM"] just returns null (effective no-op)
|
||||
// what is considered main memory then is whatever was added first
|
||||
// priority implicitly then going to RETRO_MEMORY ordering (so SaveRAM->RTC->VRAM)
|
||||
_memoryDomains.MainMemory = _memoryDomains["RAM"];
|
||||
|
||||
_serviceProvider.Register<IMemoryDomains>(_memoryDomains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ using BizHawk.Emulation.Common;
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : IRegionable
|
||||
public partial class LibretroHost : IRegionable
|
||||
{
|
||||
private LibretroApi.RETRO_REGION _region = LibretroApi.RETRO_REGION.NTSC;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ using BizHawk.Emulation.Common;
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : ISaveRam
|
||||
public partial class LibretroHost : ISaveRam
|
||||
{
|
||||
private readonly List<MemoryDomainIntPtr> _saveramAreas = new();
|
||||
private long _saveramSize = 0;
|
||||
|
|
|
@ -4,28 +4,24 @@ using BizHawk.Emulation.Common;
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : ISoundProvider
|
||||
public partial class LibretroHost : ISoundProvider
|
||||
{
|
||||
private BlipBuffer _blipL;
|
||||
private BlipBuffer _blipR;
|
||||
private const int OUT_SAMPLE_RATE = 44100;
|
||||
|
||||
private short[] _inSampBuf = new short[0];
|
||||
private short[] _outSampBuf = new short[0];
|
||||
private BlipBuffer _blipL, _blipR;
|
||||
private int _latchL, _latchR;
|
||||
|
||||
private short[] _inSampBuf = Array.Empty<short>(); // variable size, will grow as needed
|
||||
|
||||
private readonly short[] _outSampBuf = new short[OUT_SAMPLE_RATE * 2]; // big enough
|
||||
private int _outSamps;
|
||||
|
||||
private int _latchL = 0;
|
||||
private int _latchR = 0;
|
||||
|
||||
private void SetupResampler(double fps, double sps)
|
||||
private void SetupResampler(double sps)
|
||||
{
|
||||
Console.WriteLine("FPS {0} SPS {1}", fps, sps);
|
||||
|
||||
_outSampBuf = new short[44100]; // big enough
|
||||
|
||||
_blipL = new BlipBuffer(44100);
|
||||
_blipL.SetRates(sps, 44100);
|
||||
_blipR = new BlipBuffer(44100);
|
||||
_blipR.SetRates(sps, 44100);
|
||||
_blipL = new(OUT_SAMPLE_RATE);
|
||||
_blipL.SetRates(sps, OUT_SAMPLE_RATE);
|
||||
_blipR = new(OUT_SAMPLE_RATE);
|
||||
_blipR.SetRates(sps, OUT_SAMPLE_RATE);
|
||||
}
|
||||
|
||||
private void ProcessSound()
|
||||
|
@ -35,30 +31,44 @@ namespace BizHawk.Emulation.Cores.Libretro
|
|||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// skip resampling if in sample rate == out sample rate
|
||||
if (av_info.timing.sample_rate == OUT_SAMPLE_RATE)
|
||||
{
|
||||
if (len > (OUT_SAMPLE_RATE * 2))
|
||||
{
|
||||
throw new Exception("Audio buffer overflow!");
|
||||
}
|
||||
|
||||
// copy directly to our output buffer
|
||||
bridge.LibretroBridge_GetAudio(cbHandler, out _outSamps, _outSampBuf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > _inSampBuf.Length)
|
||||
{
|
||||
_inSampBuf = new short[len];
|
||||
}
|
||||
var ns = 0;
|
||||
bridge.LibretroBridge_GetAudio(cbHandler, ref ns, _inSampBuf);
|
||||
|
||||
bridge.LibretroBridge_GetAudio(cbHandler, out var ns, _inSampBuf);
|
||||
|
||||
for (uint i = 0; i < ns; i++)
|
||||
{
|
||||
int curr = _inSampBuf[i * 2];
|
||||
int cur = _inSampBuf[i * 2];
|
||||
|
||||
if (curr != _latchL)
|
||||
if (cur != _latchL)
|
||||
{
|
||||
int diff = _latchL - curr;
|
||||
_latchL = curr;
|
||||
int diff = _latchL - cur;
|
||||
_latchL = cur;
|
||||
_blipL.AddDelta(i, diff);
|
||||
}
|
||||
|
||||
curr = _inSampBuf[(i * 2) + 1];
|
||||
cur = _inSampBuf[(i * 2) + 1];
|
||||
|
||||
if (curr != _latchR)
|
||||
if (cur != _latchR)
|
||||
{
|
||||
int diff = _latchR - curr;
|
||||
_latchR = curr;
|
||||
int diff = _latchR - cur;
|
||||
_latchR = cur;
|
||||
_blipR.AddDelta(i, diff);
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +76,12 @@ namespace BizHawk.Emulation.Cores.Libretro
|
|||
_blipL.EndFrame((uint)ns);
|
||||
_blipR.EndFrame((uint)ns);
|
||||
_outSamps = _blipL.SamplesAvailable();
|
||||
|
||||
if (_outSamps > OUT_SAMPLE_RATE)
|
||||
{
|
||||
throw new Exception("Audio buffer overflow!");
|
||||
}
|
||||
|
||||
_blipL.ReadSamplesLeft(_outSampBuf, _outSamps);
|
||||
_blipR.ReadSamplesRight(_outSampBuf, _outSamps);
|
||||
}
|
||||
|
|
|
@ -5,48 +5,55 @@ using BizHawk.Emulation.Common;
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : IStatable
|
||||
// not all Libretro cores implement savestates
|
||||
// we use this so we can optionally register IStatable
|
||||
// todo: this can probably be genericized
|
||||
public class StatableLibretro : IStatable
|
||||
{
|
||||
private byte[] _stateBuf;
|
||||
private long _stateLen;
|
||||
private readonly LibretroHost _host;
|
||||
private readonly LibretroApi _api;
|
||||
private readonly byte[] _stateBuf;
|
||||
|
||||
public StatableLibretro(LibretroHost host, LibretroApi api, int maxSize)
|
||||
{
|
||||
_host = host;
|
||||
_api = api;
|
||||
_stateBuf = new byte[maxSize];
|
||||
}
|
||||
|
||||
public void SaveStateBinary(BinaryWriter writer)
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
_stateLen = api.retro_serialize_size();
|
||||
if (_stateBuf.LongLength != _stateLen)
|
||||
var len = checked((int)_api.retro_serialize_size());
|
||||
if (len > _stateBuf.Length)
|
||||
{
|
||||
_stateBuf = new byte[_stateLen];
|
||||
throw new Exception("Core attempted to grow state size. This is not allowed per the libretro API.");
|
||||
}
|
||||
|
||||
var d = new RetroData(_stateBuf, _stateLen);
|
||||
api.retro_serialize(d.PinnedData, d.Length);
|
||||
writer.Write(_stateBuf.Length);
|
||||
writer.Write(_stateBuf);
|
||||
// other variables
|
||||
writer.Write(Frame);
|
||||
writer.Write(LagCount);
|
||||
writer.Write(IsLagFrame);
|
||||
_api.retro_serialize(_stateBuf, len);
|
||||
writer.Write(len);
|
||||
writer.Write(_stateBuf, 0, len);
|
||||
|
||||
// host variables
|
||||
writer.Write(_host.Frame);
|
||||
writer.Write(_host.LagCount);
|
||||
writer.Write(_host.IsLagFrame);
|
||||
}
|
||||
|
||||
public void LoadStateBinary(BinaryReader reader)
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
var newlen = reader.ReadInt32();
|
||||
if (newlen > _stateBuf.Length)
|
||||
var len = reader.ReadInt32();
|
||||
if (len > _stateBuf.Length)
|
||||
{
|
||||
throw new Exception("Unexpected buffer size");
|
||||
throw new Exception("State buffer size exceeded the core's maximum state size!");
|
||||
}
|
||||
|
||||
reader.Read(_stateBuf, 0, newlen);
|
||||
var d = new RetroData(_stateBuf, _stateLen);
|
||||
api.retro_unserialize(d.PinnedData, d.Length);
|
||||
// other variables
|
||||
Frame = reader.ReadInt32();
|
||||
LagCount = reader.ReadInt32();
|
||||
IsLagFrame = reader.ReadBoolean();
|
||||
reader.Read(_stateBuf, 0, len);
|
||||
_api.retro_unserialize(_stateBuf, len);
|
||||
|
||||
// host variables
|
||||
_host.Frame = reader.ReadInt32();
|
||||
_host.LagCount = reader.ReadInt32();
|
||||
_host.IsLagFrame = reader.ReadBoolean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,26 +2,26 @@ using BizHawk.Emulation.Common;
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : IVideoProvider
|
||||
public partial class LibretroHost : IVideoProvider
|
||||
{
|
||||
private int[] vidBuffer;
|
||||
private int vidWidth = -1, vidHeight = -1;
|
||||
private int[] _vidBuffer;
|
||||
private int _vidWidth, _vidHeight;
|
||||
|
||||
private void InitVideoBuffer(int width, int height, int maxSize)
|
||||
{
|
||||
vidBuffer = new int[maxSize];
|
||||
vidWidth = width;
|
||||
vidHeight = height;
|
||||
_vidBuffer = new int[maxSize];
|
||||
_vidWidth = width;
|
||||
_vidHeight = height;
|
||||
bridge.LibretroBridge_SetVideoSize(cbHandler, maxSize);
|
||||
}
|
||||
|
||||
private void UpdateVideoBuffer()
|
||||
{
|
||||
bridge.LibretroBridge_GetVideo(cbHandler, ref vidWidth, ref vidHeight, vidBuffer);
|
||||
bridge.LibretroBridge_GetVideo(cbHandler, out _vidWidth, out _vidHeight, _vidBuffer);
|
||||
}
|
||||
|
||||
public int BackgroundColor => 0;
|
||||
public int[] GetVideoBuffer() => vidBuffer;
|
||||
public int[] GetVideoBuffer() => _vidBuffer;
|
||||
|
||||
public int VirtualWidth
|
||||
{
|
||||
|
@ -30,13 +30,13 @@ namespace BizHawk.Emulation.Cores.Libretro
|
|||
var dar = av_info.geometry.aspect_ratio;
|
||||
if (dar <= 0)
|
||||
{
|
||||
return vidWidth;
|
||||
return _vidWidth;
|
||||
}
|
||||
if (dar > 1.0f)
|
||||
{
|
||||
return (int)(vidHeight * dar);
|
||||
return (int)(_vidHeight * dar);
|
||||
}
|
||||
return vidWidth;
|
||||
return _vidWidth;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,18 +47,18 @@ namespace BizHawk.Emulation.Cores.Libretro
|
|||
var dar = av_info.geometry.aspect_ratio;
|
||||
if (dar <= 0)
|
||||
{
|
||||
return vidHeight;
|
||||
return _vidHeight;
|
||||
}
|
||||
if (dar < 1.0f)
|
||||
{
|
||||
return (int)(vidWidth / dar);
|
||||
return (int)(_vidWidth / dar);
|
||||
}
|
||||
return vidHeight;
|
||||
return _vidHeight;
|
||||
}
|
||||
}
|
||||
|
||||
public int BufferWidth => vidWidth;
|
||||
public int BufferHeight => vidHeight;
|
||||
public int BufferWidth => _vidWidth;
|
||||
public int BufferHeight => _vidHeight;
|
||||
|
||||
public int VsyncNumerator { get; private set; }
|
||||
public int VsyncDenominator { get; private set; }
|
||||
|
|
|
@ -0,0 +1,278 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.BizInvoke;
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
// nb: multiple libretro cores could theoretically be ran at once
|
||||
// but all of them would need to be different cores, a core itself is single instance
|
||||
[PortedCore(CoreNames.Libretro, "CasualPokePlayer", singleInstance: true, isReleased: false)]
|
||||
[ServiceNotApplicable(new[] { typeof(IDriveLight) })]
|
||||
public partial class LibretroHost
|
||||
{
|
||||
private static readonly LibretroBridge bridge;
|
||||
private static readonly LibretroBridge.retro_procs cb_procs;
|
||||
|
||||
static LibretroHost()
|
||||
{
|
||||
var resolver = new DynamicLibraryImportResolver(
|
||||
OSTailoredCode.IsUnixHost ? "libLibretroBridge.so" : "libLibretroBridge.dll", hasLimitedLifetime: false);
|
||||
|
||||
bridge = BizInvoker.GetInvoker<LibretroBridge>(resolver, CallingConventionAdapters.Native);
|
||||
bridge.LibretroBridge_GetRetroProcs(out cb_procs);
|
||||
}
|
||||
|
||||
private readonly LibretroApi api;
|
||||
private IStatable _stateWrapper;
|
||||
|
||||
private readonly IntPtr cbHandler;
|
||||
private readonly BridgeGuard _guard;
|
||||
|
||||
private class BridgeGuard : IMonitor
|
||||
{
|
||||
private static readonly object _sync = new();
|
||||
private static IntPtr _activeHandler;
|
||||
private static int _refCount;
|
||||
|
||||
private readonly IntPtr _parentHandler;
|
||||
|
||||
public BridgeGuard(IntPtr parentHandler)
|
||||
=> _parentHandler = parentHandler;
|
||||
|
||||
public void Enter()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_activeHandler == IntPtr.Zero)
|
||||
{
|
||||
_activeHandler = _parentHandler;
|
||||
bridge.LibretroBridge_SetGlobalCallbackHandler(_parentHandler);
|
||||
}
|
||||
else if (_activeHandler != _parentHandler)
|
||||
{
|
||||
throw new InvalidOperationException("Multiple callback handlers cannot be active at once!");
|
||||
}
|
||||
|
||||
_refCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public void Exit()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_refCount <= 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid {nameof(_refCount)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_refCount--;
|
||||
if (_refCount == 0)
|
||||
{
|
||||
_activeHandler = IntPtr.Zero;
|
||||
bridge.LibretroBridge_SetGlobalCallbackHandler(IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LibretroHost(CoreComm comm, IGameInfo game, string corePath, bool analysis = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
cbHandler = bridge.LibretroBridge_CreateCallbackHandler();
|
||||
|
||||
if (cbHandler == IntPtr.Zero)
|
||||
{
|
||||
throw new Exception("Failed to create callback handler!");
|
||||
}
|
||||
|
||||
_guard = new(cbHandler);
|
||||
|
||||
api = BizInvoker.GetInvoker<LibretroApi>(
|
||||
new DynamicLibraryImportResolver(corePath, hasLimitedLifetime: false), _guard, CallingConventionAdapters.Native);
|
||||
|
||||
_serviceProvider = new(this);
|
||||
|
||||
if (api.retro_api_version() != 1)
|
||||
{
|
||||
throw new InvalidOperationException("Unsupported Libretro API version (or major error in interop)");
|
||||
}
|
||||
|
||||
bridge.LibretroBridge_SetDirectories(cbHandler,
|
||||
comm.CoreFileProvider.GetRetroSystemPath(game),
|
||||
comm.CoreFileProvider.GetRetroSaveRAMDirectory(game),
|
||||
Path.GetDirectoryName(corePath),
|
||||
Path.GetDirectoryName(corePath));
|
||||
|
||||
ControllerDefinition = ControllerDef;
|
||||
_notify = comm.Notify;
|
||||
|
||||
// check if we're just analysing the core and the core path matches the loaded core path anyways
|
||||
if (analysis && corePath == LoadedCorePath)
|
||||
{
|
||||
Description = CalculateDescription();
|
||||
Description.SupportsNoGame = LoadedCoreSupportsNoGame;
|
||||
}
|
||||
else
|
||||
{
|
||||
api.retro_set_environment(cb_procs.retro_environment_proc);
|
||||
Description = CalculateDescription();
|
||||
}
|
||||
|
||||
if (!analysis)
|
||||
{
|
||||
LoadedCorePath = corePath;
|
||||
LoadedCoreSupportsNoGame = Description.SupportsNoGame;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private class RetroData : IDisposable
|
||||
{
|
||||
private readonly GCHandle _handle;
|
||||
|
||||
public IntPtr PinnedData => _handle.AddrOfPinnedObject();
|
||||
public long Length { get; }
|
||||
|
||||
public RetroData(object o, long len = 0)
|
||||
{
|
||||
_handle = GCHandle.Alloc(o, GCHandleType.Pinned);
|
||||
Length = len;
|
||||
}
|
||||
|
||||
public void Dispose() => _handle.Free();
|
||||
}
|
||||
|
||||
private byte[] RetroString(string managedString)
|
||||
{
|
||||
var ret = Encoding.UTF8.GetBytes(managedString);
|
||||
Array.Resize(ref ret, ret.Length + 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private LibretroApi.retro_system_av_info av_info;
|
||||
|
||||
public RetroDescription Description { get; }
|
||||
|
||||
// single instance hacks
|
||||
private static string LoadedCorePath { get; set; }
|
||||
private static bool LoadedCoreSupportsNoGame { get; set; }
|
||||
|
||||
private enum RETRO_LOAD
|
||||
{
|
||||
DATA,
|
||||
PATH,
|
||||
NO_GAME,
|
||||
}
|
||||
|
||||
public bool LoadData(byte[] data, string id)
|
||||
{
|
||||
using RetroData retroPath = new(RetroString(id));
|
||||
using RetroData retroData = new(data, data.Length);
|
||||
return LoadHandler(RETRO_LOAD.DATA, retroPath, retroData);
|
||||
}
|
||||
|
||||
public bool LoadPath(string path)
|
||||
{
|
||||
using RetroData retroPath = new(RetroString(path));
|
||||
return LoadHandler(RETRO_LOAD.PATH, retroPath);
|
||||
}
|
||||
|
||||
public bool LoadNoGame() => LoadHandler(RETRO_LOAD.NO_GAME);
|
||||
|
||||
private bool LoadHandler(RETRO_LOAD which, RetroData path = null, RetroData data = null)
|
||||
{
|
||||
api.retro_init();
|
||||
bool success;
|
||||
LibretroApi.retro_game_info game;
|
||||
|
||||
switch (which)
|
||||
{
|
||||
case RETRO_LOAD.NO_GAME:
|
||||
success = api.retro_load_no_game();
|
||||
break;
|
||||
case RETRO_LOAD.PATH:
|
||||
game = new() { path = path.PinnedData };
|
||||
success = api.retro_load_game(ref game);
|
||||
break;
|
||||
case RETRO_LOAD.DATA:
|
||||
game = new() { path = path.PinnedData, data = data.PinnedData, size = data.Length };
|
||||
success = api.retro_load_game(ref game);
|
||||
break;
|
||||
default:
|
||||
api.retro_deinit();
|
||||
throw new InvalidOperationException($"Invalid {nameof(RETRO_LOAD)} sent?");
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
api.retro_deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
inited = true;
|
||||
|
||||
av_info = default;
|
||||
api.retro_get_system_av_info(ref av_info);
|
||||
|
||||
api.retro_set_video_refresh(cb_procs.retro_video_refresh_proc);
|
||||
api.retro_set_audio_sample(cb_procs.retro_audio_sample_proc);
|
||||
api.retro_set_audio_sample_batch(cb_procs.retro_audio_sample_batch_proc);
|
||||
api.retro_set_input_poll(cb_procs.retro_input_poll_proc);
|
||||
api.retro_set_input_state(cb_procs.retro_input_state_proc);
|
||||
|
||||
var len = checked((int)api.retro_serialize_size());
|
||||
if (len > 0)
|
||||
{
|
||||
_stateWrapper = new StatableLibretro(this, api, len);
|
||||
_serviceProvider.Register(_stateWrapper);
|
||||
}
|
||||
|
||||
_region = api.retro_get_region();
|
||||
|
||||
// this stuff can only happen after the game is loaded
|
||||
|
||||
// allocate a video buffer which will definitely be large enough (if it isn't, that's the core's fault)
|
||||
InitVideoBuffer((int)av_info.geometry.base_width, (int)av_info.geometry.base_height,
|
||||
(int)(av_info.geometry.max_width * av_info.geometry.max_height));
|
||||
|
||||
// this is no good if fps is >= 215
|
||||
VsyncNumerator = checked((int)(10000000 * av_info.timing.fps));
|
||||
VsyncDenominator = 10000000;
|
||||
|
||||
SetupResampler(av_info.timing.sample_rate);
|
||||
|
||||
Console.WriteLine("FPS {0} SPS {1}", av_info.timing.fps, av_info.timing.sample_rate);
|
||||
|
||||
InitMemoryDomains(); // im going to assume this should happen when a game is loaded
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public RetroDescription CalculateDescription()
|
||||
{
|
||||
var descr = new RetroDescription();
|
||||
api.retro_get_system_info(out var sys_info);
|
||||
descr.LibraryName = Mershul.PtrToStringUtf8(sys_info.library_name);
|
||||
descr.LibraryVersion = Mershul.PtrToStringUtf8(sys_info.library_version);
|
||||
descr.ValidExtensions = Mershul.PtrToStringUtf8(sys_info.valid_extensions);
|
||||
descr.NeedsRomAsPath = sys_info.need_fullpath;
|
||||
descr.NeedsArchives = sys_info.block_extract;
|
||||
descr.SupportsNoGame = bridge.LibretroBridge_GetSupportsNoGame(cbHandler);
|
||||
return descr;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue