use std::ffi::CString; use std::os::raw::{c_char, c_void}; use std::path::Path; use libloading; use super::ffi::*; pub type Result = std::result::Result>; 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) -> Result { 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(&mut self, cb: EnvironmentFn) { unsafe { (&self.core_api.retro_set_environment)(cb) } } /// set_video_refresh() must be called before run(). pub fn set_video_refresh(&mut self, cb: VideoRefreshFn) { unsafe { (&self.core_api.retro_set_video_refresh)(cb) } } pub fn set_audio_sample(&mut self, cb: AudioSampleFn) { unsafe { (&self.core_api.retro_set_audio_sample)(cb) } } pub fn set_audio_sample_batch(&mut self, cb: AudioSampleBatchFn) { unsafe { (&self.core_api.retro_set_audio_sample_batch)(cb) } } pub fn set_input_poll(&mut self, cb: InputPollFn) { unsafe { (&self.core_api.retro_set_input_poll)(cb) } } pub fn set_input_state(&mut self, cb: InputStateFn) { unsafe { (&self.core_api.retro_set_input_state)(cb) } } /// set_environment() must be called before init(). pub fn init(&mut self) { // TODO assert!(called retro_set_environment); unsafe { (&self.core_api.retro_init)() } } pub fn deinit(&mut 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(&mut self, port: u32, device: u32) { unsafe { (&self.core_api.retro_set_controller_port_device)(port, device) } } /// Resets the current game. pub fn reset(&mut 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(&mut self) { unsafe { (&self.core_api.retro_run)() } } /// Serializes internal state. pub fn serialize(&mut self) -> Result> { let size: usize = 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("Serialize failed".into()) } } pub fn unserialize(&mut self, data: &[u8]) -> Result<()> { // validate size of the data let size: usize = unsafe { (&self.core_api.retro_serialize_size)() }; if data.len() != size { return Err(format!("Size of data {} doesn't match this core's expected size {}", data.len(), size).into()) } if unsafe { (&self.core_api.retro_unserialize)(data.as_ptr() as *const c_void, data.len()) } { Ok(()) } else { Err("Unserialize failed".into()) } } pub fn cheat_reset(&mut self) { unsafe { (&self.core_api.retro_cheat_reset)() } } pub fn cheat_set(&mut 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( &mut self, path: Option<&Path>, data: Option<&[u8]>, meta: Option<&str>, ) -> Result<()> { 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("Failed to load game".into()) } } /// Unloads the currently loaded game. Called before deinit(). pub fn unload_game(&mut 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) } } }