shrooms-vb-native/src/shrooms_vb_core.rs

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);
}
}