some stuff. don't use
This commit is contained in:
parent
394650aae6
commit
a07ef9f011
|
@ -95,6 +95,8 @@
|
|||
<Compile Include="Interfaces\ISoundProvider.cs" />
|
||||
<Compile Include="Interfaces\ISyncSoundProvider.cs" />
|
||||
<Compile Include="Interfaces\IVideoProvider.cs" />
|
||||
<Compile Include="LibRetro.cs" />
|
||||
<Compile Include="LibRetroEmulator.cs" />
|
||||
<Compile Include="MemoryDomain.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\svnrev.cs" />
|
||||
|
|
|
@ -0,0 +1,557 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BizHawk.Emulation.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// libretro related shims
|
||||
/// </summary>
|
||||
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.U8)]
|
||||
public bool need_fullpath;
|
||||
[MarshalAs(UnmanagedType.U8)]
|
||||
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;
|
||||
}
|
||||
|
||||
// standard callbacks
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[return:MarshalAs(UnmanagedType.U8)]
|
||||
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);
|
||||
|
||||
// entry points
|
||||
[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.U8)]
|
||||
public delegate bool epretro_serialize(IntPtr data, uint size);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[return: MarshalAs(UnmanagedType.U8)]
|
||||
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.U8)]bool enabled, string code);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[return: MarshalAs(UnmanagedType.U8)]
|
||||
public delegate bool epretro_load_game(ref retro_game_info game);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[return: MarshalAs(UnmanagedType.U8)]
|
||||
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);
|
||||
|
||||
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;
|
||||
/// <summary>
|
||||
/// Dispose() calls this, so you shouldn't
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
private static Dictionary<IntPtr, LibRetro> AttachedCores = new Dictionary<IntPtr, LibRetro>();
|
||||
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<FieldInfo> GetAllEntryPoints()
|
||||
{
|
||||
var fields = typeof(LibRetro).GetFields();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (field.FieldType.Name.StartsWith("epretro"))
|
||||
{
|
||||
// this is one of the entry point delegates
|
||||
yield return field;
|
||||
}
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,516 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BizHawk.Emulation.Common
|
||||
{
|
||||
public unsafe class LibRetroEmulator : IEmulator, IVideoProvider
|
||||
{
|
||||
#region callbacks
|
||||
|
||||
bool retro_environment(LibRetro.RETRO_ENVIRONMENT cmd, IntPtr data)
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_ROTATION:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.GET_OVERSCAN:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.GET_CAN_DUPE:
|
||||
return true;
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_MESSAGE:
|
||||
{
|
||||
LibRetro.retro_message msg = new LibRetro.retro_message();
|
||||
Marshal.PtrToStructure(data, msg);
|
||||
if (!string.IsNullOrEmpty(msg.msg))
|
||||
Console.WriteLine("LibRetro Message: {0}", msg.msg);
|
||||
return true;
|
||||
}
|
||||
case LibRetro.RETRO_ENVIRONMENT.SHUTDOWN:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_PERFORMANCE_LEVEL:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.GET_SYSTEM_DIRECTORY:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_PIXEL_FORMAT:
|
||||
{
|
||||
LibRetro.RETRO_PIXEL_FORMAT fmt = 0;
|
||||
Marshal.PtrToStructure(data, fmt);
|
||||
switch (fmt)
|
||||
{
|
||||
case LibRetro.RETRO_PIXEL_FORMAT.RGB565:
|
||||
case LibRetro.RETRO_PIXEL_FORMAT.XRGB1555:
|
||||
case LibRetro.RETRO_PIXEL_FORMAT.XRGB8888:
|
||||
pixelfmt = fmt;
|
||||
Console.WriteLine("New pixel format set: {0}", pixelfmt);
|
||||
return true;
|
||||
default:
|
||||
Console.WriteLine("Unrecognized pixel format: {0}", (int)pixelfmt);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_INPUT_DESCRIPTORS:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_KEYBOARD_CALLBACK:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_DISK_CONTROL_INTERFACE:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_HW_RENDER:
|
||||
// this can be done in principle, but there's no reason to right now
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.GET_VARIABLE:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_VARIABLES:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.GET_VARIABLE_UPDATE:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_SUPPORT_NO_GAME:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.GET_LIBRETRO_PATH:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_AUDIO_CALLBACK:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.SET_FRAME_TIME_CALLBACK:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.GET_RUMBLE_INTERFACE:
|
||||
return false;
|
||||
case LibRetro.RETRO_ENVIRONMENT.GET_INPUT_DEVICE_CAPABILITIES:
|
||||
return false;
|
||||
default:
|
||||
Console.WriteLine("Unknkown retro_environment command {0}", (int)cmd);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void retro_input_poll()
|
||||
{
|
||||
IsLagFrame = false;
|
||||
}
|
||||
short retro_input_state(uint port, uint device, uint index, uint id)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LibRetro.retro_environment_t retro_environment_cb;
|
||||
LibRetro.retro_video_refresh_t retro_video_refresh_cb;
|
||||
LibRetro.retro_audio_sample_t retro_audio_sample_cb;
|
||||
LibRetro.retro_audio_sample_batch_t retro_audio_sample_batch_cb;
|
||||
LibRetro.retro_input_poll_t retro_input_poll_cb;
|
||||
LibRetro.retro_input_state_t retro_input_state_cb;
|
||||
|
||||
#endregion
|
||||
|
||||
private LibRetro retro;
|
||||
|
||||
public LibRetroEmulator(CoreComm nextComm, string modulename)
|
||||
{
|
||||
retro_environment_cb = new LibRetro.retro_environment_t(retro_environment);
|
||||
retro_video_refresh_cb = new LibRetro.retro_video_refresh_t(retro_video_refresh);
|
||||
retro_audio_sample_cb = new LibRetro.retro_audio_sample_t(retro_audio_sample);
|
||||
retro_audio_sample_batch_cb = new LibRetro.retro_audio_sample_batch_t(retro_audio_sample_batch);
|
||||
retro_input_poll_cb = new LibRetro.retro_input_poll_t(retro_input_poll);
|
||||
retro_input_state_cb = new LibRetro.retro_input_state_t(retro_input_state);
|
||||
|
||||
retro = new LibRetro(modulename);
|
||||
CoreComm = nextComm;
|
||||
|
||||
retro.retro_set_environment(retro_environment_cb);
|
||||
retro.retro_init();
|
||||
retro.retro_set_video_refresh(retro_video_refresh_cb);
|
||||
retro.retro_set_audio_sample(retro_audio_sample_cb);
|
||||
retro.retro_set_audio_sample_batch(retro_audio_sample_batch_cb);
|
||||
retro.retro_set_input_poll(retro_input_poll_cb);
|
||||
retro.retro_set_input_state(retro_input_state_cb);
|
||||
}
|
||||
|
||||
public bool Load(byte[] data)
|
||||
{
|
||||
LibRetro.retro_system_info sys = new LibRetro.retro_system_info();
|
||||
retro.retro_get_system_info(ref sys);
|
||||
|
||||
if (sys.need_fullpath)
|
||||
throw new ArgumentException("This libretro core needs filepaths");
|
||||
if (sys.block_extract)
|
||||
throw new ArgumentException("This libretro needs non-blocked extract");
|
||||
|
||||
LibRetro.retro_game_info gi = new LibRetro.retro_game_info();
|
||||
fixed (byte* p = &data[0])
|
||||
{
|
||||
gi.data = (IntPtr)p;
|
||||
gi.meta = "";
|
||||
gi.path = "";
|
||||
gi.size = (uint)data.Length;
|
||||
if (!retro.retro_load_game(ref gi))
|
||||
{
|
||||
Console.WriteLine("retro_load_game() failed");
|
||||
return false;
|
||||
}
|
||||
savebuff = new byte[retro.retro_serialize_size()];
|
||||
}
|
||||
|
||||
LibRetro.retro_system_av_info av = new LibRetro.retro_system_av_info();
|
||||
retro.retro_get_system_av_info(ref av);
|
||||
|
||||
BufferWidth = (int)av.geometry.base_width;
|
||||
BufferHeight = (int)av.geometry.base_height;
|
||||
vidbuff = new int[av.geometry.max_width * av.geometry.max_height];
|
||||
dar = av.geometry.aspect_ratio;
|
||||
|
||||
// TODO: more precise
|
||||
CoreComm.VsyncNum = (int)(10000000 * av.timing.fps);
|
||||
CoreComm.VsyncDen = 10000000;
|
||||
|
||||
SetupResampler(av.timing.fps, av.timing.sample_rate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public ControllerDefinition ControllerDefinition
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public IController Controller
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void FrameAdvance(bool render, bool rendersound = true)
|
||||
{
|
||||
IsLagFrame = true;
|
||||
Frame++;
|
||||
retro.retro_run();
|
||||
}
|
||||
|
||||
public int Frame { get; private set; }
|
||||
public int LagCount { get; set; }
|
||||
public bool IsLagFrame { get; private set; }
|
||||
|
||||
public string SystemId
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public bool DeterministicEmulation
|
||||
{
|
||||
// who knows
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public string BoardName
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
#region saveram
|
||||
|
||||
byte[] saverambuff = new byte[0];
|
||||
|
||||
public byte[] ReadSaveRam()
|
||||
{
|
||||
int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM);
|
||||
if (saverambuff.Length != size)
|
||||
saverambuff = new byte[size];
|
||||
|
||||
IntPtr src = retro.retro_get_memory_data(LibRetro.RETRO_MEMORY.SAVE_RAM);
|
||||
if (src == IntPtr.Zero)
|
||||
throw new Exception("retro_get_memory_data(RETRO_MEMORY_SAVE_RAM) returned NULL");
|
||||
|
||||
Marshal.Copy(src, saverambuff, 0, size);
|
||||
return saverambuff;
|
||||
}
|
||||
|
||||
public void StoreSaveRam(byte[] data)
|
||||
{
|
||||
int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM);
|
||||
if (data.Length != size)
|
||||
throw new Exception("Passed saveram does not match retro_get_memory_size(RETRO_MEMORY_SAVE_RAM");
|
||||
|
||||
IntPtr dst = retro.retro_get_memory_data(LibRetro.RETRO_MEMORY.SAVE_RAM);
|
||||
if (dst == IntPtr.Zero)
|
||||
throw new Exception("retro_get_memory_data(RETRO_MEMORY_SAVE_RAM) returned NULL");
|
||||
|
||||
Marshal.Copy(data, 0, dst, size);
|
||||
}
|
||||
|
||||
public void ClearSaveRam()
|
||||
{
|
||||
// this is sort of wrong, because we should be clearing saveram to whatever the default state is
|
||||
// which may or may not be 0-fill
|
||||
int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM);
|
||||
IntPtr dst = retro.retro_get_memory_data(LibRetro.RETRO_MEMORY.SAVE_RAM);
|
||||
if (dst == IntPtr.Zero)
|
||||
throw new Exception("retro_get_memory_data(RETRO_MEMORY_SAVE_RAM) returned NULL");
|
||||
|
||||
byte* p = (byte*)dst;
|
||||
for (int i = 0; i < size; i++)
|
||||
p[i] = 0;
|
||||
}
|
||||
|
||||
public bool SaveRamModified
|
||||
{
|
||||
get { return true; }
|
||||
set { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
LagCount = 0;
|
||||
IsLagFrame = false;
|
||||
}
|
||||
|
||||
#region savestates
|
||||
|
||||
private byte[] savebuff;
|
||||
|
||||
public void SaveStateText(System.IO.TextWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void LoadStateText(System.IO.TextReader reader)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SaveStateBinary(System.IO.BinaryWriter writer)
|
||||
{
|
||||
fixed (byte* ptr = &savebuff[0])
|
||||
{
|
||||
if (!retro.retro_serialize((IntPtr)ptr, (uint)savebuff.Length))
|
||||
throw new Exception("retro_serialize() failed");
|
||||
}
|
||||
writer.Write(savebuff.Length);
|
||||
writer.Write(savebuff);
|
||||
// other variables
|
||||
writer.Write(Frame);
|
||||
writer.Write(LagCount);
|
||||
writer.Write(IsLagFrame);
|
||||
}
|
||||
|
||||
public void LoadStateBinary(System.IO.BinaryReader reader)
|
||||
{
|
||||
int newlen = reader.ReadInt32();
|
||||
if (newlen > savebuff.Length)
|
||||
throw new Exception("Unexpected buffer size");
|
||||
reader.Read(savebuff, 0, newlen);
|
||||
fixed (byte* ptr = &savebuff[0])
|
||||
{
|
||||
if (!retro.retro_unserialize((IntPtr)ptr, (uint)newlen))
|
||||
throw new Exception("retro_unserialize() failed");
|
||||
}
|
||||
// other variables
|
||||
Frame = reader.ReadInt32();
|
||||
LagCount = reader.ReadInt32();
|
||||
IsLagFrame = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
public byte[] SaveStateBinary()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool BinarySaveStatesPreferred { get { return true; } }
|
||||
|
||||
#endregion
|
||||
|
||||
public CoreComm CoreComm
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
#region memory access
|
||||
|
||||
void SetupDebuggingStuff()
|
||||
{
|
||||
MemoryDomains = MemoryDomainList.GetDummyList();
|
||||
_cpufake = new List<KeyValuePair<string, int>>();
|
||||
}
|
||||
|
||||
public MemoryDomainList MemoryDomains { get; private set; }
|
||||
|
||||
List<KeyValuePair<string, int>> _cpufake;
|
||||
|
||||
public List<KeyValuePair<string, int>> GetCpuFlagsAndRegisters()
|
||||
{
|
||||
return _cpufake;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (resampler != null)
|
||||
{
|
||||
resampler.Dispose();
|
||||
resampler = null;
|
||||
}
|
||||
if (retro != null)
|
||||
{
|
||||
retro.Dispose();
|
||||
retro = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region ISoundProvider
|
||||
|
||||
public ISoundProvider SoundProvider { get { return null; } }
|
||||
public ISyncSoundProvider SyncSoundProvider { get { return resampler; } }
|
||||
public bool StartAsyncSound() { return false; }
|
||||
public void EndAsyncSound() { }
|
||||
|
||||
SpeexResampler resampler;
|
||||
|
||||
short[] sampbuff = new short[0];
|
||||
|
||||
void SetupResampler(double fps, double sps)
|
||||
{
|
||||
// todo: more precise?
|
||||
uint spsnum = (uint)sps * 1000;
|
||||
uint spsden = (uint)1000;
|
||||
|
||||
resampler = new SpeexResampler(5, 44100 * spsden, spsnum, (uint)sps, 44100, null, null);
|
||||
}
|
||||
|
||||
void retro_audio_sample(short left, short right)
|
||||
{
|
||||
resampler.EnqueueSample(left, right);
|
||||
}
|
||||
|
||||
uint retro_audio_sample_batch(IntPtr data, uint frames)
|
||||
{
|
||||
if (sampbuff.Length < frames * 2)
|
||||
sampbuff = new short[frames * 2];
|
||||
Marshal.Copy(data, sampbuff, 0, (int)(frames * 2));
|
||||
resampler.EnqueueSamples(sampbuff, (int)frames);
|
||||
// what is the return from this used for?
|
||||
return frames;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IVideoProvider
|
||||
|
||||
public IVideoProvider VideoProvider { get { return this; } }
|
||||
|
||||
float dar;
|
||||
int[] vidbuff;
|
||||
LibRetro.RETRO_PIXEL_FORMAT pixelfmt = LibRetro.RETRO_PIXEL_FORMAT.XRGB1555;
|
||||
|
||||
void Blit555(short* src, int* dst, int width, int height, int pitch)
|
||||
{
|
||||
for (int j = 0; j < height; j++)
|
||||
{
|
||||
short* row = src;
|
||||
for (int i = 0; i < width; i++)
|
||||
{
|
||||
short ci = *row;
|
||||
int r = ci & 0x001f;
|
||||
int g = ci & 0x03e0;
|
||||
int b = ci & 0x7c00;
|
||||
|
||||
r = (r << 3) | (r >> 2);
|
||||
g = (g >> 2) | (g >> 7);
|
||||
b = (b >> 7) | (b >> 12);
|
||||
int co = r | g | b | unchecked((int)0xff000000);
|
||||
|
||||
*dst = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
src += pitch;
|
||||
}
|
||||
}
|
||||
|
||||
void Blit565(short* src, int* dst, int width, int height, int pitch)
|
||||
{
|
||||
for (int j = 0; j < height; j++)
|
||||
{
|
||||
short* row = src;
|
||||
for (int i = 0; i < width; i++)
|
||||
{
|
||||
short ci = *row;
|
||||
int r = ci & 0x001f;
|
||||
int g = ci & 0x07e0;
|
||||
int b = ci & 0xf800;
|
||||
|
||||
r = (r << 3) | (r >> 2);
|
||||
g = (g >> 3) | (g >> 9);
|
||||
b = (b >> 8) | (b >> 13);
|
||||
int co = r | g | b | unchecked((int)0xff000000);
|
||||
|
||||
*dst = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
src += pitch;
|
||||
}
|
||||
}
|
||||
|
||||
void Blit888(int* src, int* dst, int width, int height, int pitch)
|
||||
{
|
||||
for (int j = 0; j < height; j++)
|
||||
{
|
||||
int* row = src;
|
||||
for (int i = 0; i < width; i++)
|
||||
{
|
||||
int ci = *row;
|
||||
int co = ci | unchecked((int)0xff000000);
|
||||
*dst = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
src += pitch;
|
||||
}
|
||||
}
|
||||
|
||||
void retro_video_refresh(IntPtr data, uint width, uint height, uint pitch)
|
||||
{
|
||||
if (data == IntPtr.Zero) // dup frame
|
||||
return;
|
||||
if (width * height > vidbuff.Length)
|
||||
{
|
||||
Console.WriteLine("Unexpected libretro video buffer overrun?");
|
||||
return;
|
||||
}
|
||||
fixed (int* dst = &vidbuff[0])
|
||||
{
|
||||
if (pixelfmt == LibRetro.RETRO_PIXEL_FORMAT.XRGB8888)
|
||||
Blit888((int*)data, dst, (int)width, (int)height, (int)pitch / 4);
|
||||
else if (pixelfmt == LibRetro.RETRO_PIXEL_FORMAT.RGB565)
|
||||
Blit565((short*)data, dst, (int)width, (int)height, (int)pitch / 2);
|
||||
else
|
||||
Blit555((short*)data, dst, (int)width, (int)height, (int)pitch / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int[] GetVideoBuffer()
|
||||
{
|
||||
return vidbuff;
|
||||
}
|
||||
|
||||
public int VirtualWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
if (dar < 0.1f || dar > 3.0f)
|
||||
return BufferWidth;
|
||||
else
|
||||
return (int)(BufferWidth * dar);
|
||||
}
|
||||
}
|
||||
public int BufferWidth { get; private set; }
|
||||
public int BufferHeight { get; private set; }
|
||||
public int BackgroundColor { get { return unchecked((int)0xff000000); } }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue