use std::ffi::c_void; use std::ffi::CStr; use std::ffi::CString; use std::os::raw::c_char; #[allow( non_upper_case_globals, dead_code, non_camel_case_types, non_snake_case )] mod bindings { include!(concat!(env!("OUT_DIR"), "/runner-bindings.rs")); } 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 { pub fn new(filename: &str) -> Result { let c_str = CString::new(filename).expect("should be able to make cstring from filename"); let mgba = unsafe { bindings::new_runner(c_str.as_ptr() as *mut c_char) }; if mgba.is_null() { Err(anyhow::anyhow!("could not create core")) } else { Ok(MGBA { mgba }) } } 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) } } pub fn set_logger(&mut self, mut logger: impl FnMut(&str)) { unsafe { let callback = generate_c_callback(move |message: *mut c_char| { logger( CStr::from_ptr(message) .to_str() .expect("should be able to convert logging message to rust String"), ); }); bindings::set_logger(self.mgba, callback) } } } unsafe fn generate_c_callback(f: F) -> bindings::callback where F: FnMut(*mut c_char), { let data = Box::into_raw(Box::new(f)); bindings::callback { callback: Some(call_closure::), data: data as *mut _, destroy: Some(drop_box::), } } extern "C" fn call_closure(data: *mut c_void, message: *mut c_char) where F: FnMut(*mut c_char), { let callback_ptr = data as *mut F; let callback = unsafe { &mut *callback_ptr }; callback(message); } extern "C" fn drop_box(data: *mut c_void) { unsafe { Box::from_raw(data as *mut T); } } impl Drop for MGBA { fn drop(&mut self) { unsafe { bindings::free_runner(self.mgba) } } }