252 lines
7.6 KiB
Rust
252 lines
7.6 KiB
Rust
use std::{ffi::c_void, ptr, slice};
|
|
|
|
use anyhow::{anyhow, Result};
|
|
use bitflags::bitflags;
|
|
use num_derive::{FromPrimitive, ToPrimitive};
|
|
|
|
#[repr(C)]
|
|
struct VB {
|
|
_data: [u8; 0],
|
|
}
|
|
|
|
#[allow(non_camel_case_types)]
|
|
type c_int = i32;
|
|
#[allow(non_camel_case_types)]
|
|
type c_uint = u32;
|
|
|
|
#[repr(u32)]
|
|
#[derive(FromPrimitive, ToPrimitive)]
|
|
enum VBDataType {
|
|
S8 = 0,
|
|
U8 = 1,
|
|
S16 = 2,
|
|
U16 = 3,
|
|
S32 = 4,
|
|
F32 = 5,
|
|
}
|
|
|
|
bitflags! {
|
|
#[repr(transparent)]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
pub struct VBKey: u16 {
|
|
const PWR = 0x0001;
|
|
const SGN = 0x0002;
|
|
const A = 0x0004;
|
|
const B = 0x0008;
|
|
const RT = 0x0010;
|
|
const LT = 0x0020;
|
|
const RU = 0x0040;
|
|
const RR = 0x0080;
|
|
const LR = 0x0100;
|
|
const LL = 0x0200;
|
|
const LD = 0x0400;
|
|
const LU = 0x0800;
|
|
const STA = 0x1000;
|
|
const SEL = 0x2000;
|
|
const RL = 0x4000;
|
|
const RD = 0x8000;
|
|
}
|
|
}
|
|
|
|
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 = "vbGetSamples"]
|
|
fn vb_get_samples(
|
|
sim: *mut VB,
|
|
typ_: *mut VBDataType,
|
|
capacity: *mut c_uint,
|
|
position: *mut c_uint,
|
|
) -> *mut c_void;
|
|
#[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 = "vbSetSamples"]
|
|
fn vb_set_samples(
|
|
sim: *mut VB,
|
|
samples: *mut c_void,
|
|
typ_: VBDataType,
|
|
capacity: c_uint,
|
|
) -> c_int;
|
|
#[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
|
|
}
|
|
|
|
const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4;
|
|
const AUDIO_CAPACITY_FLOATS: usize = AUDIO_CAPACITY_SAMPLES * 2;
|
|
|
|
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) };
|
|
|
|
// set up audio buffer
|
|
let audio_buffer = vec![0.0f32; AUDIO_CAPACITY_FLOATS];
|
|
let samples: *mut c_void = Box::into_raw(audio_buffer.into_boxed_slice()).cast();
|
|
unsafe { vb_set_samples(sim, samples, VBDataType::F32, AUDIO_CAPACITY_SAMPLES as u32) };
|
|
|
|
CoreVB { sim }
|
|
}
|
|
|
|
pub fn reset(&mut self) {
|
|
unsafe { vb_reset(self.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))
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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 read_samples(&mut self, samples: &mut Vec<f32>) {
|
|
let mut position = 0;
|
|
let ptr =
|
|
unsafe { vb_get_samples(self.sim, ptr::null_mut(), ptr::null_mut(), &mut position) };
|
|
// SAFETY: position is an offset in a buffer of (f32, f32). so, position * 2 is an offset in a buffer of f32.
|
|
let read_samples: &mut [f32] =
|
|
unsafe { slice::from_raw_parts_mut(ptr.cast(), position as usize * 2) };
|
|
samples.extend_from_slice(read_samples);
|
|
|
|
unsafe {
|
|
vb_set_samples(
|
|
self.sim,
|
|
ptr,
|
|
VBDataType::F32,
|
|
AUDIO_CAPACITY_SAMPLES as u32,
|
|
)
|
|
};
|
|
}
|
|
|
|
pub fn set_keys(&mut self, keys: VBKey) {
|
|
unsafe { vb_set_keys(self.sim, keys.bits()) };
|
|
}
|
|
}
|
|
|
|
impl Drop for CoreVB {
|
|
fn drop(&mut self) {
|
|
let ptr =
|
|
unsafe { vb_get_samples(self.sim, ptr::null_mut(), ptr::null_mut(), ptr::null_mut()) };
|
|
// SAFETY: the audio buffer originally came from a Vec<u32>
|
|
let floats: Vec<f32> = unsafe {
|
|
Vec::from_raw_parts(ptr.cast(), AUDIO_CAPACITY_FLOATS, AUDIO_CAPACITY_FLOATS)
|
|
};
|
|
drop(floats);
|
|
|
|
// 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);
|
|
}
|
|
}
|