use std::ffi::CString; use std::os::raw::{c_char, c_void}; use std::path::Path; use failure::Fallible; use libloading; use super::ffi::*; pub struct LibretroApi { _lib: libloading::Library, // for tying our lifetime to its own pub core_api: CoreAPI, } impl LibretroApi { pub fn from_library(lib: libloading::Library) -> Fallible { unsafe { let core_api = CoreAPI { 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")?, }; Ok(LibretroApi { _lib: lib, core_api }) } } /// set_environment() must be called before init(). pub fn set_environment(&self, cb: EnvironmentFn) { unsafe { (&self.core_api.retro_set_environment)(cb) } } /// video_refresh() must be called before run(). pub fn set_video_refresh(&self, cb: VideoRefreshFn) { unsafe { (&self.core_api.retro_set_video_refresh)(cb) } } pub fn set_audio_sample(&self, cb: AudioSampleFn) { unsafe { (&self.core_api.retro_set_audio_sample)(cb) } } pub fn set_audio_sample_batch(&self, cb: AudioSampleBatchFn) { unsafe { (&self.core_api.retro_set_audio_sample_batch)(cb) } } pub fn set_input_poll(&self, cb: InputPollFn) { unsafe { (&self.core_api.retro_set_input_poll)(cb) } } pub fn set_input_state(&self, cb: InputStateFn) { unsafe { (&self.core_api.retro_set_input_state)(cb) } } /// set_environment() must be called before init(). pub fn init(&self) { // TODO assert!(called retro_set_environment); unsafe { (&self.core_api.retro_init)() } } pub fn deinit(&self) { unsafe { (&self.core_api.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.core_api.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) -> SystemInfo { unsafe { let mut info = ::std::mem::zeroed::(); (&self.core_api.retro_get_system_info)(&mut info); info } } /** Gets information about system audio/video timings and geometry. * Can be called only after 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) -> SystemAvInfo { unsafe { let mut av_info = ::std::mem::zeroed::(); (&self.core_api.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.core_api.retro_set_controller_port_device)(port, device) } } /// Resets the current game. pub fn reset(&self) { unsafe { (&self.core_api.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.core_api.retro_run)() } } /// Serializes internal state. pub fn serialize(&self) -> Fallible> { let size = unsafe { (&self.core_api.retro_serialize_size)() }; let mut vec = Vec::with_capacity(size); vec.resize(size, 0); // FIXME: libretro-sys incorrectly says retro_serialize is a void(), not a bool() if unsafe { (&self.core_api.retro_serialize)(vec.as_mut_ptr() as *mut c_void, size); true } { Ok(vec) } else { Err(failure::err_msg("Serialize failed")) } } pub fn unserialize(&self, data: &[u8]) -> Fallible<()> { if unsafe { (&self.core_api.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.core_api.retro_cheat_reset)() } } pub fn cheat_set(&self, index: u32, enabled: bool, code: &str) { unsafe { (&self.core_api.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 = GameInfo { path: std::ptr::null(), data: std::ptr::null(), size: 0, meta: std::ptr::null(), }; // scope for lifetimes to outlive the call to retro_load_game (pointer safety) let c_path; let c_meta; if let Some(p) = path { c_path = CString::new(p.to_string_lossy().as_bytes().to_vec())?; game.path = c_path.as_ptr(); } if let Some(d) = data { game.data = d.as_ptr() as *const c_void; game.size = d.len(); } if let Some(m) = meta { c_meta = CString::new(m.as_bytes().to_vec())?; game.meta = c_meta.as_ptr(); } if unsafe { (&self.core_api.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.core_api.retro_unload_game)() } } pub fn get_region(&self) -> Region { unsafe { std::mem::transmute((&self.core_api.retro_get_region)()) } } /// Gets (read/write access to) a region of memory pub fn get_memory(&self, id: u32) -> &mut [u8] { unsafe { let data = (&self.core_api.retro_get_memory_data)(id); let size = (&self.core_api.retro_get_memory_size)(id); std::slice::from_raw_parts_mut(data as *mut u8, size) } } }