use std::os::raw::{c_char, c_uint, c_void}; use std::path::Path; use failure::Fallible; use crate::libretro_types::*; use libloading; use std::ffi::OsStr; pub struct LibretroApi<'a> { pub retro_set_environment: libloading::Symbol<'a, unsafe extern "C" fn(arg1: retro_environment_t)>, pub retro_set_video_refresh: libloading::Symbol<'a, unsafe extern "C" fn(arg1: retro_video_refresh_t)>, pub retro_set_audio_sample: libloading::Symbol<'a, unsafe extern "C" fn(arg1: retro_audio_sample_t)>, pub retro_set_audio_sample_batch: libloading::Symbol<'a, unsafe extern "C" fn(arg1: retro_audio_sample_batch_t)>, pub retro_set_input_poll: libloading::Symbol<'a, unsafe extern "C" fn(arg1: retro_input_poll_t)>, pub retro_set_input_state: libloading::Symbol<'a, unsafe extern "C" fn(arg1: retro_input_state_t)>, pub retro_init: libloading::Symbol<'a, unsafe extern "C" fn()>, pub retro_deinit: libloading::Symbol<'a, unsafe extern "C" fn()>, pub retro_api_version: libloading::Symbol<'a, unsafe extern "C" fn() -> c_uint>, pub retro_get_system_info: libloading::Symbol<'a, unsafe extern "C" fn(info: *mut retro_system_info)>, pub retro_get_system_av_info: libloading::Symbol<'a, unsafe extern "C" fn(info: *mut retro_system_av_info)>, pub retro_set_controller_port_device: libloading::Symbol<'a, unsafe extern "C" fn(port: c_uint, device: c_uint)>, pub retro_reset: libloading::Symbol<'a, unsafe extern "C" fn()>, pub retro_run: libloading::Symbol<'a, unsafe extern "C" fn()>, pub retro_serialize_size: libloading::Symbol<'a, unsafe extern "C" fn() -> usize>, pub retro_serialize: libloading::Symbol<'a, unsafe extern "C" fn(data: *mut c_void, size: usize) -> bool>, pub retro_unserialize: libloading::Symbol<'a, unsafe extern "C" fn(data: *const c_void, size: usize) -> bool>, pub retro_cheat_reset: libloading::Symbol<'a, unsafe extern "C" fn()>, pub retro_cheat_set: libloading::Symbol< 'a, unsafe extern "C" fn(index: c_uint, enabled: bool, code: *const c_char), >, pub retro_load_game: libloading::Symbol<'a, unsafe extern "C" fn(game: *const retro_game_info) -> bool>, pub retro_load_game_special: libloading::Symbol< 'a, unsafe extern "C" fn( game_type: c_uint, info: *const retro_game_info, num_info: usize, ) -> bool, >, pub retro_unload_game: libloading::Symbol<'a, unsafe extern "C" fn()>, pub retro_get_region: libloading::Symbol<'a, unsafe extern "C" fn() -> c_uint>, pub retro_get_memory_data: libloading::Symbol<'a, unsafe extern "C" fn(id: c_uint) -> *mut c_void>, pub retro_get_memory_size: libloading::Symbol<'a, unsafe extern "C" fn(id: c_uint) -> usize>, } impl<'a> LibretroApi<'a> { pub fn from_library(lib: &'a libloading::Library) -> Fallible { unsafe { Ok(LibretroApi { retro_set_environment: lib.get(b"retro_set_environment")?, retro_set_video_refresh: lib.get(b"retro_set_video_refresh")?, retro_set_audio_sample: lib.get(b"retro_set_audio_sample")?, retro_set_audio_sample_batch: lib.get(b"retro_set_audio_sample_batch")?, retro_set_input_poll: lib.get(b"retro_set_input_poll")?, retro_set_input_state: lib.get(b"retro_set_input_state")?, retro_init: lib.get(b"retro_init")?, retro_deinit: lib.get(b"retro_deinit")?, retro_api_version: lib.get(b"retro_api_version")?, retro_get_system_info: lib.get(b"retro_get_system_info")?, retro_get_system_av_info: lib.get(b"retro_get_system_av_info")?, retro_set_controller_port_device: lib.get(b"retro_set_controller_port_device")?, retro_reset: lib.get(b"retro_reset")?, retro_run: lib.get(b"retro_run")?, retro_serialize_size: lib.get(b"retro_serialize_size")?, retro_serialize: lib.get(b"retro_serialize")?, retro_unserialize: lib.get(b"retro_unserialize")?, retro_cheat_reset: lib.get(b"retro_cheat_reset")?, retro_cheat_set: lib.get(b"retro_cheat_set")?, retro_load_game: lib.get(b"retro_load_game")?, retro_load_game_special: lib.get(b"retro_load_game_special")?, retro_unload_game: lib.get(b"retro_unload_game")?, retro_get_region: lib.get(b"retro_get_region")?, retro_get_memory_data: lib.get(b"retro_get_memory_data")?, retro_get_memory_size: lib.get(b"retro_get_memory_size")?, }) } } /// retro_set_environment() must be called before retro_init(). pub fn set_environment(&self, cb: retro_environment_t) { unsafe { (&self.retro_set_environment)(cb) } } pub fn set_video_refresh(&self, cb: retro_video_refresh_t) { unsafe { (&self.retro_set_video_refresh)(env) } } pub fn set_audio_sample(&self, cb: retro_audio_sample_t) { unsafe { (&self.retro_set_audio_sample)(env) } } pub fn set_audio_sample_batch(&self, cb: retro_audio_sample_batch_t) { unsafe { (&self.retro_set_audio_sample_batch)(env) } } pub fn set_input_poll(&self, cb: retro_input_poll_t) { unsafe { (&self.retro_set_input_poll)(env) } } pub fn set_input_state(&self, cb: retro_input_state_t) { unsafe { (&self.retro_set_input_state)(env) } } /// retro_set_environment() must be called before retro_init(). pub fn init(&self) { // TODO assert!(called retro_set_environment); unsafe { (&self.retro_init)() } } pub fn deinit(&self) { unsafe { (&self.retro_deinit)() } } /// Must return RETRO_API_VERSION. Used to validate ABI compatibility when the API is revised. pub fn api_version(&self) -> u32 { unsafe { (&self.retro_api_version)() as u32 } } /// Gets statically known system info. Can be called at any time, even before retro_init(). pub fn get_system_info(&self) -> retro_system_info { unsafe { let mut info = ::std::mem::zeroed::(); (&self.retro_get_system_info)(&mut info); info } } /** Gets information about system audio/video timings and geometry. * Can be called only after retro_load_game() has successfully completed. * NOTE: The implementation of this function might not initialize every * variable if needed. * E.g. geom.aspect_ratio might not be initialized if core doesn't * desire a particular aspect ratio. */ pub fn get_system_av_info(&self) -> retro_system_av_info { unsafe { let mut av_info = ::std::mem::zeroed::(); (&self.retro_get_system_av_info)(&mut av_info); av_info } } /** Sets device to be used for player 'port'. * By default, RETRO_DEVICE_JOYPAD is assumed to be plugged into all * available ports. * Setting a particular device type is not a guarantee that libretro cores * will only poll input based on that particular device type. It is only a * hint to the libretro core when a core cannot automatically detect the * appropriate input device type on its own. It is also relevant when a * core can change its behavior depending on device type. * * As part of the core's implementation of retro_set_controller_port_device, * the core should call RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS to notify the * frontend if the descriptions for any controls have changed as a * result of changing the device type. */ pub fn set_controller_port_device(&self, port: u32, device: u32) { unsafe { (&self.retro_set_controller_port_device)(port, device) } } /// Resets the current game. pub fn reset(&self) { unsafe { (&self.retro_reset)() } } /** Runs the game for one video frame. * During retro_run(), input_poll callback must be called at least once. * * If a frame is not rendered for reasons where a game "dropped" a frame, * this still counts as a frame, and retro_run() should explicitly dupe * a frame if GET_CAN_DUPE returns true. * In this case, the video callback can take a NULL argument for data. */ pub fn run(&self) { unsafe { (&self.retro_run)() } } /// Serializes internal state. pub fn serialize(&self) -> Fallible> { let size = unsafe { (&self.retro_serialize_size)() }; let mut vec = Vec::with_capacity(size); vec.resize(size, 0); if unsafe { (&self.retro_serialize)(vec.as_mut_ptr() as *mut c_void, size) } { Ok(vec) } else { Err(failure::err_msg("Serialize failed")) } } pub fn unserialize(&self, data: &[u8]) -> Fallible<()> { if unsafe { (&self.retro_unserialize)(data.as_ptr() as *const c_void, data.len()) } { Ok(()) } else { Err(failure::err_msg("Unserialize failed")) } } pub fn cheat_reset(&self) { unsafe { (&self.retro_cheat_reset)() } } pub fn cheat_set(&self, index: u32, enabled: bool, code: &str) { unsafe { (&self.retro_cheat_set)(index, enabled, code.as_bytes().as_ptr() as *const c_char) } } /// Loads a game. pub fn load_game( &self, path: Option<&Path>, data: Option<&[u8]>, meta: Option<&str>, ) -> Fallible<()> { let mut game = retro_game_info { path: std::ptr::null(), data: std::ptr::null(), size: 0, meta: std::ptr::null(), }; if let Some(p) = path.and_then(Path::to_str) { game.path = p.as_bytes().as_ptr() as *const c_char; } if let Some(d) = data { game.data = d.as_ptr() as *const c_void; game.size = d.len(); } if let Some(m) = meta { game.meta = m.as_bytes().as_ptr() as *const c_char; } if unsafe { (&self.retro_load_game)(&game) } { Ok(()) } else { Err(failure::err_msg("Failed to load game")) } } /// Unloads the currently loaded game. Called before deinit(). pub fn unload_game(&self) { unsafe { (&self.retro_unload_game)() } } pub fn get_region(&self) -> u32 { unsafe { (&self.retro_get_region)() as u32 } } /// Gets region of memory pub fn get_memory(&self, id: u32) -> &mut [u8] { unsafe { let data = (&self.retro_get_memory_data)(id); let size = (&self.retro_get_memory_size)(id); std::slice::from_raw_parts_mut(data as *mut u8, size) } } }