use std::{ffi::c_void, ptr}; use anyhow::{anyhow, Result}; #[repr(C)] struct VB { _data: [u8; 0], } #[allow(non_camel_case_types)] type c_int = i32; type OnFrame = extern "C" fn(sim: *mut VB) -> c_int; #[link(name = "vb")] extern "C" { #[link_name = "vbEmulate"] fn vb_emulate(sim: *mut VB, cycles: *mut u32) -> c_int; #[link_name = "vbGetCartROM"] fn vb_get_cart_rom(sim: *mut VB, size: *mut u32) -> *mut c_void; #[link_name = "vbGetPixels"] fn vb_get_pixels( sim: *mut VB, left: *mut c_void, left_stride_x: c_int, left_stride_y: c_int, right: *mut c_void, right_stride_x: c_int, right_stride_y: c_int, ); #[link_name = "vbGetUserData"] fn vb_get_user_data(sim: *mut VB) -> *mut c_void; #[link_name = "vbInit"] fn vb_init(sim: *mut VB) -> *mut VB; #[link_name = "vbReset"] fn vb_reset(sim: *mut VB); #[link_name = "vbSetCartROM"] fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int; #[link_name = "vbSetKeys"] fn vb_set_keys(sim: *mut VB, keys: u16) -> u16; #[link_name = "vbSetFrameCallback"] fn vb_set_frame_callback(sim: *mut VB, on_frame: OnFrame); #[link_name = "vbSetUserData"] fn vb_set_user_data(sim: *mut VB, tag: *mut c_void); #[link_name = "vbSizeOf"] fn vb_size_of() -> usize; } extern "C" fn on_frame(sim: *mut VB) -> i32 { // SAFETY: the *mut VB owns its userdata. // There is no way for the userdata to be null or otherwise invalid. let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() }; data.frame_seen = true; 1 } struct VBState { frame_seen: bool, } pub struct CoreVB { sim: *mut VB, } // SAFETY: the memory pointed to by sim is valid unsafe impl Send for CoreVB {} impl CoreVB { pub fn new() -> Self { // init the VB instance itself let size = unsafe { vb_size_of() }; // allocate a vec of u64 so that this memory is 8-byte aligned let memory = vec![0u64; size.div_ceil(4)]; let sim: *mut VB = Box::into_raw(memory.into_boxed_slice()).cast(); unsafe { vb_init(sim) }; unsafe { vb_reset(sim) }; // set up userdata let state = VBState { frame_seen: false }; unsafe { vb_set_user_data(sim, Box::into_raw(Box::new(state)).cast()) }; unsafe { vb_set_frame_callback(sim, on_frame) }; CoreVB { sim } } pub fn load_rom(&mut self, rom: Vec) -> Result<()> { self.unload_rom(); let size = rom.len() as u32; let rom = Box::into_raw(rom.into_boxed_slice()).cast(); let status = unsafe { vb_set_cart_rom(self.sim, rom, size) }; if status == 0 { Ok(()) } else { let _: Vec = unsafe { Vec::from_raw_parts(rom.cast(), size as usize, size as usize) }; Err(anyhow!("Invalid ROM size of {} bytes", size)) } } fn unload_rom(&mut self) -> Option> { let mut size = 0; let rom = unsafe { vb_get_cart_rom(self.sim, &mut size) }; if rom.is_null() { return None; } unsafe { vb_set_cart_rom(self.sim, ptr::null_mut(), 0) }; let vec = unsafe { Vec::from_raw_parts(rom.cast(), size as usize, size as usize) }; Some(vec) } pub fn emulate_frame(&mut self) { let mut cycles = 20_000_000; unsafe { vb_emulate(self.sim, &mut cycles) }; } pub fn read_pixels(&mut self, buffers: &mut [u8]) -> bool { // SAFETY: the *mut VB owns its userdata. // There is no way for the userdata to be null or otherwise invalid. let data: &mut VBState = unsafe { &mut *vb_get_user_data(self.sim).cast() }; if !data.frame_seen { return false; } data.frame_seen = false; // the buffer must be big enough for our data assert!(buffers.len() >= 384 * 224 * 2); unsafe { vb_get_pixels( self.sim, buffers.as_mut_ptr().cast(), 2, 384 * 2, buffers.as_mut_ptr().offset(1).cast(), 2, 384 * 2, ); }; true } pub fn set_keys(&mut self, keys: u16) { unsafe { vb_set_keys(self.sim, keys) }; } } impl Drop for CoreVB { fn drop(&mut self) { // SAFETY: the *mut VB owns its userdata. // There is no way for the userdata to be null or otherwise invalid. let ptr: *mut VBState = unsafe { vb_get_user_data(self.sim).cast() }; // SAFETY: we made this pointer ourselves, we can for sure free it unsafe { drop(Box::from_raw(ptr)) }; let len = unsafe { vb_size_of() }.div_ceil(4); // SAFETY: the sim's memory originally came from a Vec let bytes: Vec = unsafe { Vec::from_raw_parts(self.sim.cast(), len, len) }; drop(bytes); } }