208 lines
8.7 KiB
Rust
208 lines
8.7 KiB
Rust
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<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(&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::<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(&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<Vec<u8>> {
|
|
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)
|
|
}
|
|
}
|
|
}
|