using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.Reflection; namespace BizHawk.Emulation.Cores { /// /// libretro related shims /// public class LibRetro : IDisposable { public const int RETRO_API_VERSION = 1; public enum RETRO_DEVICE { NONE = 0, JOYPAD = 1, MOUSE = 2, KEYBOARD = 3, LIGHTGUN = 4, ANALOG = 5, POINTER = 6, SENSOR_ACCELEROMETER = 7 }; public enum RETRO_DEVICE_ID_JOYPAD { B = 0, Y = 1, SELECT = 2, START = 3, UP = 4, DOWN = 5, LEFT = 6, RIGHT = 7, A = 8, X = 9, L = 10, R = 11, L2 = 12, R2 = 13, L3 = 14, R3 = 15 }; public enum RETRO_DEVICE_ID_ANALOG { // LEFT / RIGHT? X = 0, Y = 1 }; public enum RETRO_DEVICE_ID_MOUSE { X = 0, Y = 1, LEFT = 2, RIGHT = 3 }; public enum RETRO_DEVICE_ID_LIGHTGUN { X = 0, Y = 1, TRIGGER = 2, CURSOR = 3, TURBO = 4, PAUSE = 5, START = 6 }; public enum RETRO_DEVICE_ID_POINTER { X = 0, Y = 1, PRESSED = 2 }; public enum RETRO_DEVICE_ID_SENSOR_ACCELEROMETER { X = 0, Y = 1, Z = 2 }; public enum RETRO_REGION { NTSC = 0, PAL = 1 }; public enum RETRO_MEMORY { SAVE_RAM = 0, RTC = 1, SYSTEM_RAM = 2, VIDEO_RAM = 3, }; public enum RETRO_KEY { UNKNOWN = 0, FIRST = 0, BACKSPACE = 8, TAB = 9, CLEAR = 12, RETURN = 13, PAUSE = 19, ESCAPE = 27, SPACE = 32, EXCLAIM = 33, QUOTEDBL = 34, HASH = 35, DOLLAR = 36, AMPERSAND = 38, QUOTE = 39, LEFTPAREN = 40, RIGHTPAREN = 41, ASTERISK = 42, PLUS = 43, COMMA = 44, MINUS = 45, PERIOD = 46, SLASH = 47, _0 = 48, _1 = 49, _2 = 50, _3 = 51, _4 = 52, _5 = 53, _6 = 54, _7 = 55, _8 = 56, _9 = 57, COLON = 58, SEMICOLON = 59, LESS = 60, EQUALS = 61, GREATER = 62, QUESTION = 63, AT = 64, LEFTBRACKET = 91, BACKSLASH = 92, RIGHTBRACKET = 93, CARET = 94, UNDERSCORE = 95, BACKQUOTE = 96, a = 97, b = 98, c = 99, d = 100, e = 101, f = 102, g = 103, h = 104, i = 105, j = 106, k = 107, l = 108, m = 109, n = 110, o = 111, p = 112, q = 113, r = 114, s = 115, t = 116, u = 117, v = 118, w = 119, x = 120, y = 121, z = 122, DELETE = 127, KP0 = 256, KP1 = 257, KP2 = 258, KP3 = 259, KP4 = 260, KP5 = 261, KP6 = 262, KP7 = 263, KP8 = 264, KP9 = 265, KP_PERIOD = 266, KP_DIVIDE = 267, KP_MULTIPLY = 268, KP_MINUS = 269, KP_PLUS = 270, KP_ENTER = 271, KP_EQUALS = 272, UP = 273, DOWN = 274, RIGHT = 275, LEFT = 276, INSERT = 277, HOME = 278, END = 279, PAGEUP = 280, PAGEDOWN = 281, F1 = 282, F2 = 283, F3 = 284, F4 = 285, F5 = 286, F6 = 287, F7 = 288, F8 = 289, F9 = 290, F10 = 291, F11 = 292, F12 = 293, F13 = 294, F14 = 295, F15 = 296, NUMLOCK = 300, CAPSLOCK = 301, SCROLLOCK = 302, RSHIFT = 303, LSHIFT = 304, RCTRL = 305, LCTRL = 306, RALT = 307, LALT = 308, RMETA = 309, LMETA = 310, LSUPER = 311, RSUPER = 312, MODE = 313, COMPOSE = 314, HELP = 315, PRINT = 316, SYSREQ = 317, BREAK = 318, MENU = 319, POWER = 320, EURO = 321, UNDO = 322, LAST }; [Flags] public enum RETRO_MOD { NONE = 0, SHIFT = 1, CTRL = 2, ALT = 4, META = 8, NUMLOCK = 16, CAPSLOCK = 32, SCROLLLOCK = 64 }; public enum RETRO_ENVIRONMENT { SET_ROTATION = 1, GET_OVERSCAN = 2, GET_CAN_DUPE = 3, SET_MESSAGE = 6, SHUTDOWN = 7, SET_PERFORMANCE_LEVEL = 8, GET_SYSTEM_DIRECTORY = 9, SET_PIXEL_FORMAT = 10, SET_INPUT_DESCRIPTORS = 11, SET_KEYBOARD_CALLBACK = 12, SET_DISK_CONTROL_INTERFACE = 13, SET_HW_RENDER = 14, GET_VARIABLE = 15, SET_VARIABLES = 16, GET_VARIABLE_UPDATE = 17, SET_SUPPORT_NO_GAME = 18, GET_LIBRETRO_PATH = 19, SET_AUDIO_CALLBACK = 22, SET_FRAME_TIME_CALLBACK = 21, GET_RUMBLE_INTERFACE = 23, GET_INPUT_DEVICE_CAPABILITIES = 24, }; public enum RETRO_PIXEL_FORMAT { XRGB1555 = 0, XRGB8888 = 1, RGB565 = 2 }; public struct retro_message { public string msg; public uint frames; } public struct retro_input_descriptor { public uint port; public uint device; public uint index; public uint id; } public struct retro_system_info { public string library_name; public string library_version; public string valid_extensions; [MarshalAs(UnmanagedType.U1)] public bool need_fullpath; [MarshalAs(UnmanagedType.U1)] public bool block_extract; } public struct retro_game_geometry { public uint base_width; public uint base_height; public uint max_width; public uint max_height; public float aspect_ratio; } public struct retro_system_timing { public double fps; public double sample_rate; } public struct retro_system_av_info { public retro_game_geometry geometry; public retro_system_timing timing; } public struct retro_variable { public string key; public string value; } public struct retro_game_info { public string path; public IntPtr data; public uint size; public string meta; } #region callback prototypes [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return:MarshalAs(UnmanagedType.U1)] public delegate bool retro_environment_t(RETRO_ENVIRONMENT cmd, IntPtr data); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void retro_video_refresh_t(IntPtr data, uint width, uint height, uint pitch); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void retro_audio_sample_t(short left, short right); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate uint retro_audio_sample_batch_t(IntPtr data, uint frames); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void retro_input_poll_t(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate short retro_input_state_t(uint port, uint device, uint index, uint id); #endregion #region entry point prototypes [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_set_environment(retro_environment_t cb); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_set_video_refresh(retro_video_refresh_t cb); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_set_audio_sample(retro_audio_sample_t cb); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_set_audio_sample_batch(retro_audio_sample_batch_t cb); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_set_input_poll(retro_input_poll_t cb); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_set_input_state(retro_input_state_t cb); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_init(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_deinit(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate uint epretro_api_version(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_get_system_info(ref retro_system_info info); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_get_system_av_info(ref retro_system_av_info info); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_set_controller_port_device(uint port, uint device); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_reset(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_run(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate uint epretro_serialize_size(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.U1)] public delegate bool epretro_serialize(IntPtr data, uint size); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.U1)] public delegate bool epretro_unserialize(IntPtr data, uint size); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_cheat_reset(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_cheat_set(uint index, [MarshalAs(UnmanagedType.U1)]bool enabled, string code); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.U1)] public delegate bool epretro_load_game(ref retro_game_info game); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.U1)] public delegate bool epretro_load_game_special(uint game_type, ref retro_game_info info, uint num_info); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void epretro_unload_game(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate uint epretro_get_region(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate IntPtr epretro_get_memory_data(RETRO_MEMORY id); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate uint epretro_get_memory_size(RETRO_MEMORY id); #endregion #region entry points // these are all hooked up by reflection on dll load public epretro_set_environment retro_set_environment; public epretro_set_video_refresh retro_set_video_refresh; public epretro_set_audio_sample retro_set_audio_sample; public epretro_set_audio_sample_batch retro_set_audio_sample_batch; public epretro_set_input_poll retro_set_input_poll; public epretro_set_input_state retro_set_input_state; public epretro_init retro_init; /// /// Dispose() calls this, so you shouldn't /// public epretro_deinit retro_deinit; public epretro_api_version retro_api_version; public epretro_get_system_info retro_get_system_info; public epretro_get_system_av_info retro_get_system_av_info; public epretro_set_controller_port_device retro_set_controller_port_device; public epretro_reset retro_reset; public epretro_run retro_run; public epretro_serialize_size retro_serialize_size; public epretro_serialize retro_serialize; public epretro_unserialize retro_unserialize; public epretro_cheat_reset retro_cheat_reset; public epretro_cheat_set retro_cheat_set; public epretro_load_game retro_load_game; public epretro_load_game_special retro_load_game_special; public epretro_unload_game retro_unload_game; public epretro_get_region retro_get_region; public epretro_get_memory_data retro_get_memory_data; public epretro_get_memory_size retro_get_memory_size; #endregion private static Dictionary AttachedCores = new Dictionary(); private IntPtr hModule = IntPtr.Zero; public void Dispose() { // like many other emu cores, libretros are in general single instance, so we track some things lock (AttachedCores) { if (hModule != IntPtr.Zero) { retro_deinit(); ClearAllEntryPoints(); Win32.FreeLibrary(hModule); AttachedCores.Remove(hModule); hModule = IntPtr.Zero; } } } public LibRetro(string modulename) { // like many other emu cores, libretros are in general single instance, so we track some things lock (AttachedCores) { IntPtr newmodule = Win32.LoadLibrary(modulename); if (newmodule == IntPtr.Zero) throw new Exception(string.Format("LoadLibrary(\"{0}\") returned NULL", modulename)); if (AttachedCores.ContainsKey(newmodule)) { // this core is already loaded, so we must detatch the old instance LibRetro martyr = AttachedCores[newmodule]; martyr.retro_deinit(); martyr.ClearAllEntryPoints(); martyr.hModule = IntPtr.Zero; Win32.FreeLibrary(newmodule); // decrease ref count by 1 } AttachedCores[newmodule] = this; hModule = newmodule; if (!ConnectAllEntryPoints()) { ClearAllEntryPoints(); Win32.FreeLibrary(hModule); hModule = IntPtr.Zero; throw new Exception("ConnectAllEntryPoints() failed. The console may contain more details."); } } } private static class Win32 { [DllImport("kernel32.dll")] public static extern IntPtr LoadLibrary(string dllToLoad); [DllImport("kernel32.dll")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); [DllImport("kernel32.dll")] public static extern bool FreeLibrary(IntPtr hModule); } private static IEnumerable GetAllEntryPoints() { return typeof(LibRetro).GetFields().Where((field) => field.FieldType.Name.StartsWith("epretro")); } private void ClearAllEntryPoints() { foreach (var field in GetAllEntryPoints()) { field.SetValue(this, null); } } private bool ConnectAllEntryPoints() { bool succeed = true; foreach (var field in GetAllEntryPoints()) { string fieldname = field.Name; IntPtr entry = Win32.GetProcAddress(hModule, fieldname); if (entry != IntPtr.Zero) { field.SetValue(this, Marshal.GetDelegateForFunctionPointer(entry, field.FieldType)); } else { Console.WriteLine("Couldn't bind libretro entry point {0}", fieldname); succeed = false; } } return succeed; } } }