using System; using System.IO; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using Newtonsoft.Json; using BizHawk.Common; using BizHawk.Emulation.Common; using BizHawk.Common.BufferExtensions; namespace BizHawk.Emulation.Cores { [CoreAttributes("Libretro", "natt&zeromus")] public unsafe class LibRetroEmulator : IEmulator, ISettable, ISaveRam, IStatable, IVideoProvider, IInputPollable { #region Settings Settings _Settings = new Settings(); SyncSettings _SyncSettings; public class SyncSettings { public SyncSettings Clone() { return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)); } public SyncSettings() { } } public class Settings { public void Validate() { } public Settings() { SettingsUtil.SetDefaultValues(this); } public Settings Clone() { return (Settings)MemberwiseClone(); } } public Settings GetSettings() { return _Settings.Clone(); } public SyncSettings GetSyncSettings() { return _SyncSettings.Clone(); } public bool PutSettings(Settings o) { _Settings.Validate(); _Settings = o; //TODO - store settings into core? or we can just keep doing it before frameadvance return false; } public bool PutSyncSettings(SyncSettings o) { bool reboot = false; //we could do it this way roughly if we need to //if(JsonConvert.SerializeObject(o.FIOConfig) != JsonConvert.SerializeObject(_SyncSettings.FIOConfig) _SyncSettings = o; return reboot; } #endregion #region callbacks unsafe void retro_log_printf(LibRetro.RETRO_LOG_LEVEL level, string fmt, IntPtr a0, IntPtr a1, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, IntPtr a7, IntPtr a8, IntPtr a9, IntPtr a10, IntPtr a11, IntPtr a12, IntPtr a13, IntPtr a14, IntPtr a15) { //avert your eyes, these things were not meant to be seen in c# //I'm not sure this is a great idea. It would suck for silly logging to be unstable. But.. I dont think this is unstable. The sprintf might just print some garbledy stuff. var args = new IntPtr[] { a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 }; int idx = 0; Console.Write(Sprintf.sprintf(fmt, () => args[idx++])); } unsafe bool retro_environment(LibRetro.RETRO_ENVIRONMENT cmd, IntPtr data) { Console.WriteLine(cmd); 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: //please write an example of a core that crashes without this (fmsx malfunctions..) //"this is optional, but many cores will silently malfunction without it as they can't load their firmware files" //an alternative (alongside where the saverams and such will go?) //*((IntPtr*)data.ToPointer()) = unmanagedResources.StringToHGlobalAnsi(CoreComm.CoreFileProvider.GetGameBasePath()); *((IntPtr*)data.ToPointer()) = SystemDirectoryAtom; return false; case LibRetro.RETRO_ENVIRONMENT.SET_PIXEL_FORMAT: { LibRetro.RETRO_PIXEL_FORMAT fmt = 0; int[] tmp = new int[1]; Marshal.Copy(data, tmp, 0, 1); fmt = (LibRetro.RETRO_PIXEL_FORMAT)tmp[0]; 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: { void** variables = (void**)data.ToPointer(); IntPtr pKey = new IntPtr(*variables++); string key = Marshal.PtrToStringAnsi(pKey); Console.WriteLine("Requesting variable: {0}", key); //always return default //TODO: cache settings atoms if(!Description.Variables.ContainsKey(key)) return false; //HACK: return pointer for desmume mouse, i want to implement that first if (key == "desmume_pointer_type") { *variables = unmanagedResources.StringToHGlobalAnsi("touch").ToPointer(); return true; } *variables = unmanagedResources.StringToHGlobalAnsi(Description.Variables[key].DefaultOption).ToPointer(); return true; } case LibRetro.RETRO_ENVIRONMENT.SET_VARIABLES: { void** variables = (void**)data.ToPointer(); for (; ; ) { IntPtr pKey = new IntPtr(*variables++); IntPtr pValue = new IntPtr(*variables++); if(pKey == IntPtr.Zero) break; string key = Marshal.PtrToStringAnsi(pKey); string value = Marshal.PtrToStringAnsi(pValue); var vd = new VariableDescription() { Name = key}; var parts = value.Split(';'); vd.Description = parts[0]; vd.Options = parts[1].TrimStart(' ').Split('|'); Description.Variables[vd.Name] = vd; } } return false; case LibRetro.RETRO_ENVIRONMENT.GET_VARIABLE_UPDATE: return false; case LibRetro.RETRO_ENVIRONMENT.SET_SUPPORT_NO_GAME: environmentInfo.SupportNoGame = true; 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; case LibRetro.RETRO_ENVIRONMENT.GET_LOG_INTERFACE: *(IntPtr*)data = Marshal.GetFunctionPointerForDelegate(retro_log_printf_cb); return true; case LibRetro.RETRO_ENVIRONMENT.GET_PERF_INTERFACE: //some builds of fmsx core crash without this set Marshal.StructureToPtr(retro_perf_callback, data, false); return true; case LibRetro.RETRO_ENVIRONMENT.GET_LOCATION_INTERFACE: return false; case LibRetro.RETRO_ENVIRONMENT.GET_CORE_ASSETS_DIRECTORY: return false; case LibRetro.RETRO_ENVIRONMENT.GET_SAVE_DIRECTORY: //supposedly optional like everything else here, but without it ?? crashes (please write which case) //this will suffice for now. if we find evidence later it's needed we can stash a string with //unmanagedResources and CoreFileProvider //*((IntPtr*)data.ToPointer()) = IntPtr.Zero; return false; case LibRetro.RETRO_ENVIRONMENT.SET_CONTROLLER_INFO: return true; default: Console.WriteLine("Unknkown retro_environment command {0}", (int)cmd); return false; } } void retro_input_poll() { IsLagFrame = false; } private bool GetButton(uint pnum, string type, string button) { string key = string.Format("P{0} {1} {2}", pnum, type, button); bool b = Controller[key]; if (b == true) { return true; //debugging placeholder } else return false; } //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?) //id = button id short retro_input_state(uint port, uint device, uint index, uint id) { //helpful debugging //Console.WriteLine("{0} {1} {2} {3}", port, device, index, id); switch ((LibRetro.RETRO_DEVICE)device) { case LibRetro.RETRO_DEVICE.POINTER: { switch ((LibRetro.RETRO_DEVICE_ID_POINTER)id) { case LibRetro.RETRO_DEVICE_ID_POINTER.X: return (short)Controller.GetFloat("Pointer X"); case LibRetro.RETRO_DEVICE_ID_POINTER.Y: return (short)Controller.GetFloat("Pointer Y"); case LibRetro.RETRO_DEVICE_ID_POINTER.PRESSED: return (short)(Controller["Pointer Pressed"] ? 1 : 0); } return 0; } case LibRetro.RETRO_DEVICE.JOYPAD: { //The JOYPAD is sometimes called RetroPad (and we'll call it that in user-facing stuff cos retroarch does) //It is essentially a Super Nintendo controller, but with additional L2/R2/L3/R3 buttons, similar to a PS1 DualShock. string button = ""; switch ((LibRetro.RETRO_DEVICE_ID_JOYPAD)id) { case LibRetro.RETRO_DEVICE_ID_JOYPAD.A: button = "A"; break; case LibRetro.RETRO_DEVICE_ID_JOYPAD.B: button = "B"; break; case LibRetro.RETRO_DEVICE_ID_JOYPAD.X: button = "X"; break; case LibRetro.RETRO_DEVICE_ID_JOYPAD.Y: button = "Y"; break; case LibRetro.RETRO_DEVICE_ID_JOYPAD.UP: button = "Up"; break; case LibRetro.RETRO_DEVICE_ID_JOYPAD.DOWN: button = "Down"; break; case LibRetro.RETRO_DEVICE_ID_JOYPAD.LEFT: button = "Left"; break; case LibRetro.RETRO_DEVICE_ID_JOYPAD.RIGHT: button = "Right"; break; case LibRetro.RETRO_DEVICE_ID_JOYPAD.L: button = "L"; break; case LibRetro.RETRO_DEVICE_ID_JOYPAD.R: button = "R"; break; case LibRetro.RETRO_DEVICE_ID_JOYPAD.SELECT: button = "Select"; break; case LibRetro.RETRO_DEVICE_ID_JOYPAD.START: button = "Start"; break; } return (short)(GetButton(port+1, "RetroPad", button) ? 1 : 0); } default: 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; LibRetro.retro_log_printf_t retro_log_printf_cb; LibRetro.retro_perf_callback retro_perf_callback = new LibRetro.retro_perf_callback(); #endregion class RetroEnvironmentInfo { public bool SupportNoGame; } //disposable resources private LibRetro retro; private UnmanagedResourceHeap unmanagedResources = new UnmanagedResourceHeap(); /// /// Cached information sent to the frontend by environment calls /// RetroEnvironmentInfo environmentInfo = new RetroEnvironmentInfo(); public class RetroDescription { /// /// String containing a friendly display name for the core, but we probably shouldn't use this. I decided it's better to get the user used to using filenames as core 'codenames' instead. /// public string LibraryName; /// /// String containing a friendly version number for the core library /// public string LibraryVersion; /// /// List of extensions as "sfc|smc|fig" which this core accepts. /// public string ValidExtensions; /// /// Whether the core needs roms to be specified as paths (can't take rom data buffersS) /// public bool NeedsRomAsPath; /// /// Whether the core needs roms stored as archives (e.g. arcade roms). We probably shouldn't employ the dearchiver prompts when opening roms for these cores. /// public bool NeedsArchives; /// /// Whether the core can be run without a game provided (e.g. stand-alone games, like 2048) /// public bool SupportsNoGame; /// /// Variables defined by the core /// public Dictionary Variables = new Dictionary(); } public class VariableDescription { public string Name; public string Description; public string[] Options; public string DefaultOption { get { return Options[0]; } } public override string ToString() { return string.Format("{0} ({1}) = ({2})", Name, Description, string.Join("|", Options)); } } public readonly RetroDescription Description = new RetroDescription(); //path configuration string CoresDirectory; string SystemDirectory; IntPtr SystemDirectoryAtom; public LibRetroEmulator(CoreComm nextComm, string modulename) { CoresDirectory = Path.GetDirectoryName(new FileInfo(modulename).FullName); SystemDirectory = Path.Combine(CoresDirectory, "System"); SystemDirectoryAtom = unmanagedResources.StringToHGlobalAnsi(SystemDirectory); ServiceProvider = new BasicServiceProvider(this); _SyncSettings = new SyncSettings(); 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_log_printf_cb = new LibRetro.retro_log_printf_t(retro_log_printf); //no way (need new mechanism) to check for SSSE3, MMXEXT, SSE4, SSE42 retro_perf_callback.get_cpu_features = new LibRetro.retro_get_cpu_features_t(() => (ulong)( (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsXMMIAvailable) ? LibRetro.RETRO_SIMD.SSE : 0) | (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsXMMI64Available) ? LibRetro.RETRO_SIMD.SSE2 : 0) | (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsSSE3Available) ? LibRetro.RETRO_SIMD.SSE3 : 0) | (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsMMXAvailable) ? LibRetro.RETRO_SIMD.MMX : 0) ) ); retro_perf_callback.get_perf_counter = new LibRetro.retro_perf_get_counter_t(() => System.Diagnostics.Stopwatch.GetTimestamp()); retro_perf_callback.get_time_usec = new LibRetro.retro_perf_get_time_usec_t(() => DateTime.Now.Ticks / 10); retro_perf_callback.perf_log = new LibRetro.retro_perf_log_t( () => {} ); retro_perf_callback.perf_register = new LibRetro.retro_perf_register_t((ref LibRetro.retro_perf_counter counter) => { }); retro_perf_callback.perf_start = new LibRetro.retro_perf_start_t((ref LibRetro.retro_perf_counter counter) => { }); retro_perf_callback.perf_stop = new LibRetro.retro_perf_stop_t((ref LibRetro.retro_perf_counter counter) => { }); retro = new LibRetro(modulename); try { CoreComm = nextComm; //this series of steps may be mystical. LibRetro.retro_system_info system_info = new LibRetro.retro_system_info(); retro.retro_get_system_info(ref system_info); 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); //compile descriptive information Description.NeedsArchives = system_info.block_extract; Description.NeedsRomAsPath = system_info.need_fullpath; Description.LibraryName = system_info.library_name; Description.LibraryVersion = system_info.library_version; Description.ValidExtensions = system_info.valid_extensions; Description.SupportsNoGame = environmentInfo.SupportNoGame; //variables need to be done ahead of time, when theyre set through the environment //some retro_init (for example, desmume) will continue to use variables (and maybe other parts of the environment) from within retro_init } catch { retro.Dispose(); retro = null; throw; } } public IEmulatorServiceProvider ServiceProvider { get; private set; } public bool LoadData(byte[] data) { 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; return LoadWork(ref gi); } } public bool LoadPath(string path) { LibRetro.retro_game_info gi = new LibRetro.retro_game_info(); gi.path = path; //is this the right encoding? seems to be ok return LoadWork(ref gi); } public bool LoadNoGame() { LibRetro.retro_game_info gi = new LibRetro.retro_game_info(); return LoadWork(ref gi); } bool LoadWork(ref LibRetro.retro_game_info gi) { if (!retro.retro_load_game(ref gi)) { Console.WriteLine("retro_load_game() failed"); return false; } //TODO - libretro cores can return a varying serialize size over time. I tried to get them to write it in the docs... savebuff = new byte[retro.retro_serialize_size()]; savebuff2 = new byte[savebuff.Length + 13]; 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); ControllerDefinition = CreateControllerDefinition(_SyncSettings); return true; } public static ControllerDefinition CreateControllerDefinition(SyncSettings syncSettings) { ControllerDefinition definition = new ControllerDefinition(); definition.Name = "LibRetro Controls"; // <-- for compatibility foreach(var item in new[] { "P1 {0} Up", "P1 {0} Down", "P1 {0} Left", "P1 {0} Right", "P1 {0} Select", "P1 {0} Start", "P1 {0} Y", "P1 {0} B", "P1 {0} X", "P1 {0} A", "P1 {0} L", "P1 {0} R", "P2 {0} Up", "P2 {0} Down", "P2 {0} Left", "P2 {0} Right", "P2 {0} Select", "P2 {0} Start", "P2 {0} Y", "P2 {0} B", "P2 {0} X", "P2 {0} A", "P2 {0} L", "P2 {0} R", }) definition.BoolButtons.Add(string.Format(item,"RetroPad")); definition.BoolButtons.Add("Pointer Pressed"); definition.FloatControls.Add("Pointer X"); definition.FloatControls.Add("Pointer Y"); definition.FloatRanges.Add(new ControllerDefinition.FloatRange(-32767, 0, 32767)); definition.FloatRanges.Add(new ControllerDefinition.FloatRange(-32767, 0, 32767)); return definition; } public ControllerDefinition ControllerDefinition { get; private set; } public IController Controller { get; set; } public void FrameAdvance(bool render, bool rendersound = true) { //TODO - consider changing directory and using Libretro subdir of bizhawk as a kind of sandbox, for the duration of the run? IsLagFrame = true; Frame++; nsamprecv = 0; retro.retro_run(); //Console.WriteLine("[{0}]", nsamprecv); } public int Frame { get; private set; } public string SystemId { get { return "Libretro"; } } public bool DeterministicEmulation { // who knows get { return true; } } public string BoardName { get { return null; } } #region ISaveRam //TODO - terrible things will happen if this changes at runtime byte[] saverambuff = new byte[0]; public byte[] CloneSaveRam() { 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) return null; Marshal.Copy(src, saverambuff, 0, size); return (byte[])saverambuff.Clone(); } public void StoreSaveRam(byte[] data) { int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM); if (size == 0) return; 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 bool SaveRamModified { [FeatureNotImplemented] get { //if we dont have saveram, it isnt modified. otherwise, assume iti s int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM); if (size == 0) return false; return true; } [FeatureNotImplemented] set { throw new NotImplementedException(); } } #endregion public void ResetCounters() { Frame = 0; LagCount = 0; IsLagFrame = false; } #region savestates private byte[] savebuff; private byte[] savebuff2; public void SaveStateText(System.IO.TextWriter writer) { var temp = SaveStateBinary(); temp.SaveAsHex(writer); } public void LoadStateText(System.IO.TextReader reader) { string hex = reader.ReadLine(); byte[] state = new byte[hex.Length / 2]; state.ReadFromHex(hex); LoadStateBinary(new BinaryReader(new MemoryStream(state))); } public void SaveStateBinary(System.IO.BinaryWriter writer) { //is this the only way we know of to detect unavailable savestates? if (savebuff.Length > 0) { 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); if (savebuff.Length > 0) { 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() { var ms = new System.IO.MemoryStream(savebuff2, true); var bw = new System.IO.BinaryWriter(ms); SaveStateBinary(bw); bw.Flush(); ms.Close(); return savebuff2; } public bool BinarySaveStatesPreferred { get { return true; } } #endregion public CoreComm CoreComm { get; private set; } #region memory access void SetupDebuggingStuff() { } public MemoryDomainList MemoryDomains { get; private set; } #endregion public void Dispose() { if (resampler != null) { resampler.Dispose(); resampler = null; } if (retro != null) { retro.Dispose(); retro = null; } unmanagedResources.Dispose(); unmanagedResources = 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]; // debug int nsamprecv = 0; void SetupResampler(double fps, double sps) { Console.WriteLine("FPS {0} SPS {1}", fps, 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); nsamprecv++; } 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); nsamprecv += (int)frames; // what is the return from this used for? return frames; } #endregion #region IVideoProvider 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)>>5; int b = (ci & 0xf800)>>11; r = (r << 3) | (r >> 2); g = (g << 2) | (g >> 4); b = (b << 3) | (b >> 2); int co = (b<<16) | (g<<8) | r; *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) return BufferWidth; else if (dar > 1.0f) return (int)(BufferHeight * dar); else return BufferWidth; } } public int VirtualHeight { get { if(dar==0) return BufferHeight; if (dar < 1.0f) return (int)(BufferWidth / dar); else return BufferHeight; } } public int BufferWidth { get; private set; } public int BufferHeight { get; private set; } public int BackgroundColor { get { return unchecked((int)0xff000000); } } #endregion #region IInputPollable public int LagCount { get; set; } public bool IsLagFrame { get; private set; } public IInputCallbackSystem InputCallbacks { [FeatureNotImplemented] get { throw new NotImplementedException(); } } #endregion } }