2024-11-03 16:32:53 +00:00
|
|
|
use std::{ffi::c_void, ptr};
|
|
|
|
|
|
|
|
use anyhow::{anyhow, Result};
|
2024-11-02 20:18:41 +00:00
|
|
|
|
|
|
|
#[repr(C)]
|
2024-11-03 16:32:53 +00:00
|
|
|
struct VB {
|
|
|
|
_data: [u8; 0],
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(non_camel_case_types)]
|
|
|
|
type c_int = i32;
|
|
|
|
type OnFrame = extern "C" fn(sim: *mut VB) -> c_int;
|
2024-11-02 20:18:41 +00:00
|
|
|
|
|
|
|
#[link(name = "vb")]
|
|
|
|
extern "C" {
|
2024-11-03 16:32:53 +00:00
|
|
|
#[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;
|
2024-11-02 20:18:41 +00:00
|
|
|
#[link_name = "vbInit"]
|
|
|
|
fn vb_init(sim: *mut VB) -> *mut VB;
|
2024-11-03 16:32:53 +00:00
|
|
|
#[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);
|
2024-11-02 20:18:41 +00:00
|
|
|
#[link_name = "vbSizeOf"]
|
|
|
|
fn vb_size_of() -> usize;
|
2024-11-03 16:32:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2024-11-02 20:18:41 +00:00
|
|
|
|
2024-11-03 16:32:53 +00:00
|
|
|
struct VBState {
|
|
|
|
frame_seen: bool,
|
2024-11-02 20:18:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct CoreVB {
|
|
|
|
sim: *mut VB,
|
|
|
|
}
|
|
|
|
|
2024-11-03 16:32:53 +00:00
|
|
|
// SAFETY: the memory pointed to by sim is valid
|
|
|
|
unsafe impl Send for CoreVB {}
|
|
|
|
|
2024-11-02 20:18:41 +00:00
|
|
|
impl CoreVB {
|
|
|
|
pub fn new() -> Self {
|
2024-11-03 16:32:53 +00:00
|
|
|
// init the VB instance itself
|
2024-11-02 20:18:41 +00:00
|
|
|
let size = unsafe { vb_size_of() };
|
2024-11-03 16:32:53 +00:00
|
|
|
// 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();
|
2024-11-02 20:18:41 +00:00
|
|
|
unsafe { vb_init(sim) };
|
2024-11-03 16:32:53 +00:00
|
|
|
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<u8>) -> 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<u8> =
|
|
|
|
unsafe { Vec::from_raw_parts(rom.cast(), size as usize, size as usize) };
|
|
|
|
Err(anyhow!("Invalid ROM size of {} bytes", size))
|
2024-11-02 20:18:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-03 16:32:53 +00:00
|
|
|
fn unload_rom(&mut self) -> Option<Vec<u8>> {
|
|
|
|
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)
|
2024-11-02 20:18:41 +00:00
|
|
|
}
|
2024-11-03 16:32:53 +00:00
|
|
|
|
|
|
|
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 [Vec<u8>; 2]) -> 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;
|
|
|
|
|
|
|
|
assert_eq!(buffers[0].len(), 384 * 224);
|
|
|
|
assert_eq!(buffers[1].len(), 384 * 224);
|
|
|
|
unsafe {
|
|
|
|
vb_get_pixels(
|
|
|
|
self.sim,
|
|
|
|
buffers[0].as_mut_ptr().cast(),
|
|
|
|
1,
|
|
|
|
384,
|
|
|
|
buffers[1].as_mut_ptr().cast(),
|
|
|
|
1,
|
|
|
|
384,
|
|
|
|
);
|
|
|
|
};
|
|
|
|
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<u64>
|
|
|
|
let bytes: Vec<u64> = unsafe { Vec::from_raw_parts(self.sim.cast(), len, len) };
|
|
|
|
drop(bytes);
|
|
|
|
}
|
|
|
|
}
|