213 lines
9.1 KiB
Rust
213 lines
9.1 KiB
Rust
use std::ffi::CString;
|
|
use std::os::raw::{c_char, c_void};
|
|
use std::path::Path;
|
|
|
|
use libloading;
|
|
|
|
use super::ffi::*;
|
|
|
|
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
|
|
|
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<Self> {
|
|
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::<SystemInfo>();
|
|
(&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::<SystemAvInfo>();
|
|
(&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<Vec<u8>> {
|
|
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)
|
|
}
|
|
}
|
|
}
|