2021-06-04 21:29:56 +10:00
|
|
|
use std::ffi::c_void;
|
2021-04-20 07:21:44 +10:00
|
|
|
use std::ffi::CStr;
|
|
|
|
use std::ffi::CString;
|
2021-08-02 02:50:06 +10:00
|
|
|
use std::os::raw::c_char;
|
2021-04-20 07:21:44 +10:00
|
|
|
|
|
|
|
#[allow(
|
|
|
|
non_upper_case_globals,
|
|
|
|
dead_code,
|
|
|
|
non_camel_case_types,
|
|
|
|
non_snake_case
|
|
|
|
)]
|
|
|
|
mod bindings {
|
2021-05-23 14:06:35 +10:00
|
|
|
include!(concat!(env!("OUT_DIR"), "/runner-bindings.rs"));
|
2021-04-20 07:21:44 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct MGBA {
|
|
|
|
mgba: *mut bindings::MGBA,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct VideoBuffer {
|
|
|
|
width: u32,
|
|
|
|
height: u32,
|
|
|
|
buffer: *mut u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VideoBuffer {
|
|
|
|
pub fn get_size(&self) -> (u32, u32) {
|
|
|
|
(self.width, self.height)
|
|
|
|
}
|
|
|
|
pub fn get_pixel(&self, x: u32, y: u32) -> u32 {
|
|
|
|
let offset = (y * self.width + x) as isize;
|
|
|
|
assert!(x < self.width, "x must be in range 0 to {}", self.width);
|
|
|
|
assert!(y < self.height, "y must be in range 0 to {}", self.height);
|
|
|
|
unsafe { *self.buffer.offset(offset) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MGBA {
|
2021-07-04 02:33:26 +10:00
|
|
|
pub fn new(filename: &str) -> Result<Self, anyhow::Error> {
|
2021-04-20 07:21:44 +10:00
|
|
|
let c_str = CString::new(filename).expect("should be able to make cstring from filename");
|
2021-08-02 02:50:06 +10:00
|
|
|
let mgba = unsafe { bindings::new_runner(c_str.as_ptr() as *mut c_char) };
|
2021-07-04 02:33:26 +10:00
|
|
|
if mgba.is_null() {
|
|
|
|
Err(anyhow::anyhow!("could not create core"))
|
|
|
|
} else {
|
|
|
|
Ok(MGBA { mgba })
|
2021-04-20 07:21:44 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_video_buffer(&self) -> VideoBuffer {
|
|
|
|
let c_video_buffer = unsafe { bindings::get_video_buffer(self.mgba) };
|
|
|
|
VideoBuffer {
|
|
|
|
width: c_video_buffer.width,
|
|
|
|
height: c_video_buffer.height,
|
|
|
|
buffer: c_video_buffer.buffer,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn advance_frame(&mut self) {
|
|
|
|
unsafe { bindings::advance_frame(self.mgba) }
|
|
|
|
}
|
2021-06-04 21:29:56 +10:00
|
|
|
pub fn set_logger(&mut self, mut logger: impl FnMut(&str)) {
|
|
|
|
unsafe {
|
2021-08-02 02:50:06 +10:00
|
|
|
let callback = generate_c_callback(move |message: *mut c_char| {
|
2021-06-04 21:29:56 +10:00
|
|
|
logger(
|
|
|
|
CStr::from_ptr(message)
|
|
|
|
.to_str()
|
|
|
|
.expect("should be able to convert logging message to rust String"),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
bindings::set_logger(self.mgba, callback)
|
|
|
|
}
|
|
|
|
}
|
2021-04-20 07:21:44 +10:00
|
|
|
}
|
|
|
|
|
2021-06-04 21:29:56 +10:00
|
|
|
unsafe fn generate_c_callback<F>(f: F) -> bindings::callback
|
|
|
|
where
|
2021-08-02 02:50:06 +10:00
|
|
|
F: FnMut(*mut c_char),
|
2021-06-04 21:29:56 +10:00
|
|
|
{
|
|
|
|
let data = Box::into_raw(Box::new(f));
|
2021-04-20 07:21:44 +10:00
|
|
|
|
2021-06-04 21:29:56 +10:00
|
|
|
bindings::callback {
|
|
|
|
callback: Some(call_closure::<F>),
|
|
|
|
data: data as *mut _,
|
|
|
|
destroy: Some(drop_box::<F>),
|
2021-04-20 07:21:44 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-02 02:50:06 +10:00
|
|
|
extern "C" fn call_closure<F>(data: *mut c_void, message: *mut c_char)
|
2021-06-04 21:29:56 +10:00
|
|
|
where
|
2021-08-02 02:50:06 +10:00
|
|
|
F: FnMut(*mut c_char),
|
2021-06-04 21:29:56 +10:00
|
|
|
{
|
|
|
|
let callback_ptr = data as *mut F;
|
|
|
|
let callback = unsafe { &mut *callback_ptr };
|
|
|
|
callback(message);
|
2021-04-20 07:21:44 +10:00
|
|
|
}
|
|
|
|
|
2021-06-04 21:29:56 +10:00
|
|
|
extern "C" fn drop_box<T>(data: *mut c_void) {
|
2021-04-20 07:21:44 +10:00
|
|
|
unsafe {
|
2021-06-04 21:29:56 +10:00
|
|
|
Box::from_raw(data as *mut T);
|
2021-04-20 07:21:44 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for MGBA {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe { bindings::free_runner(self.mgba) }
|
|
|
|
}
|
|
|
|
}
|