use std::ffi::CString; use std::os::raw::{c_char, c_void}; use std::path::Path; use libloading; use crate::prelude::SystemInfo2; use super::ffi::*; pub type Result = std::result::Result>; pub struct LibretroApi { _lib: Option, // for tying our lifetime to its own pub core_api: CoreAPI, loaded_game: bool, environment_set: bool, } 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: Some(lib), core_api, loaded_game: false, environment_set: false }) } } #[cfg(feature = "static")] pub fn from_static() -> Self { use std::os::raw::c_uint; extern "C" { fn retro_set_environment(callback: EnvironmentFn); fn retro_set_video_refresh(callback: VideoRefreshFn); fn retro_set_audio_sample(callback: AudioSampleFn); fn retro_set_audio_sample_batch(callback: AudioSampleBatchFn); fn retro_set_input_poll(callback: InputPollFn); fn retro_set_input_state(callback: InputStateFn); fn retro_init(); fn retro_deinit(); fn retro_api_version() -> c_uint; fn retro_get_system_info(info: *mut SystemInfo); fn retro_get_system_av_info(info: *mut SystemAvInfo); fn retro_set_controller_port_device(port: c_uint, device: c_uint); fn retro_reset(); fn retro_run(); fn retro_serialize_size() -> usize; fn retro_serialize(data: *mut c_void, size: usize); fn retro_unserialize(data: *const c_void, size: usize) -> bool; fn retro_cheat_reset(); fn retro_cheat_set(index: c_uint, enabled: bool, code: *const c_char); fn retro_load_game(game: *const GameInfo) -> bool; fn retro_load_game_special(game_type: c_uint, info: *const GameInfo, num_info: usize) -> bool; fn retro_unload_game(); fn retro_get_region() -> c_uint; fn retro_get_memory_data(id: c_uint) -> *mut c_void; fn retro_get_memory_size(id: c_uint) -> usize; } let core_api = CoreAPI { retro_set_environment, retro_set_video_refresh, retro_set_audio_sample, retro_set_audio_sample_batch, retro_set_input_poll, retro_set_input_state, retro_init, retro_deinit, retro_api_version, retro_get_system_info, retro_get_system_av_info, retro_set_controller_port_device, retro_reset, retro_run, retro_serialize_size, retro_serialize, retro_unserialize, retro_cheat_reset, retro_cheat_set, retro_load_game, retro_load_game_special, retro_unload_game, retro_get_region, retro_get_memory_data, retro_get_memory_size, }; LibretroApi { _lib: None, core_api, loaded_game: false, environment_set: false } } /// set_environment() must be called before init(). pub fn set_environment(&mut self, cb: EnvironmentFn) { self.environment_set = true; 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) { assert!(self.environment_set, "Must call set_environment(EnvironmentFn) before init()"); unsafe { (&self.core_api.retro_init)() } } pub fn deinit(&mut self) { assert!(!self.loaded_game, "Must call unload_game() before deinit()"); 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) -> SystemInfo2 { unsafe { let mut info = ::std::mem::zeroed::(); (&self.core_api.retro_get_system_info)(&mut info); SystemInfo2::from(&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 { assert!(self.loaded_game, "Must call load_game(..) before get_system_av_info()"); 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![0; size]; // 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: impl AsRef<[u8]>) -> Result<()> { let data = data.as_ref(); // 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) } { self.loaded_game = true; Ok(()) } else { Err("Failed to load game".into()) } } /// Unloads the currently loaded game. Called before deinit(). pub fn unload_game(&mut self) { self.loaded_game = false; 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 access to a region of memory pub fn get_memory(&self, id: u32) -> &[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(data as *const u8, size) } } /// Gets write access to a region of memory pub fn mut_memory(&mut 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) } } }