ferretro/ferretro_base/src/retro/loading.rs

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)
}
}
}