2025-01-05 18:54:53 +00:00
|
|
|
use std::{ffi::c_void, ptr, slice};
|
2024-11-03 16:32:53 +00:00
|
|
|
|
|
|
|
use anyhow::{anyhow, Result};
|
2024-11-05 05:07:48 +00:00
|
|
|
use bitflags::bitflags;
|
2024-11-04 14:59:58 +00:00
|
|
|
use num_derive::{FromPrimitive, ToPrimitive};
|
2024-12-12 04:44:14 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-11-02 20:18:41 +00:00
|
|
|
|
2025-01-13 05:30:47 +00:00
|
|
|
use super::address_set::AddressSet;
|
|
|
|
|
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;
|
2024-11-04 14:59:58 +00:00
|
|
|
#[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,
|
|
|
|
}
|
|
|
|
|
2024-11-11 05:50:57 +00:00
|
|
|
#[repr(i32)]
|
|
|
|
#[derive(FromPrimitive, ToPrimitive)]
|
|
|
|
enum VBOption {
|
|
|
|
PseudoHalt = 0,
|
|
|
|
}
|
|
|
|
|
2024-11-05 05:07:48 +00:00
|
|
|
bitflags! {
|
2024-11-10 19:05:10 +00:00
|
|
|
#[repr(transparent)]
|
2024-12-12 04:44:14 +00:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
2024-11-05 05:07:48 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-02 02:48:33 +00:00
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
|
|
pub enum VBRegister {
|
|
|
|
Program(u32),
|
|
|
|
System(u32),
|
|
|
|
PC,
|
|
|
|
}
|
|
|
|
|
2025-01-13 05:30:47 +00:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
|
|
pub enum VBWatchpointType {
|
|
|
|
Read,
|
|
|
|
Write,
|
|
|
|
Access,
|
|
|
|
}
|
|
|
|
|
2025-01-05 05:47:58 +00:00
|
|
|
type OnExecute =
|
|
|
|
extern "C" fn(sim: *mut VB, address: u32, code: *const u16, length: c_int) -> c_int;
|
2025-01-13 05:30:47 +00:00
|
|
|
type OnFrame = extern "C" fn(sim: *mut VB) -> c_int;
|
|
|
|
type OnRead = extern "C" fn(
|
|
|
|
sim: *mut VB,
|
|
|
|
address: u32,
|
|
|
|
type_: VBDataType,
|
|
|
|
value: *mut i32,
|
|
|
|
cycles: *mut u32,
|
|
|
|
) -> c_int;
|
|
|
|
type OnWrite = extern "C" fn(
|
|
|
|
sim: *mut VB,
|
|
|
|
address: u32,
|
|
|
|
type_: VBDataType,
|
|
|
|
value: *mut i32,
|
|
|
|
cycles: *mut u32,
|
|
|
|
cancel: *mut c_int,
|
|
|
|
) -> c_int;
|
2024-11-02 20:18:41 +00:00
|
|
|
|
|
|
|
#[link(name = "vb")]
|
|
|
|
extern "C" {
|
2024-11-16 02:50:21 +00:00
|
|
|
#[link_name = "vbEmulate"]
|
|
|
|
fn vb_emulate(sim: *mut VB, cycles: *mut u32) -> c_int;
|
2024-11-11 05:50:57 +00:00
|
|
|
#[link_name = "vbEmulateEx"]
|
|
|
|
fn vb_emulate_ex(sims: *mut *mut VB, count: c_uint, cycles: *mut u32) -> c_int;
|
2024-12-07 05:00:40 +00:00
|
|
|
#[link_name = "vbGetCartRAM"]
|
|
|
|
fn vb_get_cart_ram(sim: *mut VB, size: *mut u32) -> *mut c_void;
|
2024-11-03 16:32:53 +00:00
|
|
|
#[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,
|
|
|
|
);
|
2025-01-02 02:48:33 +00:00
|
|
|
#[link_name = "vbGetProgramCounter"]
|
|
|
|
fn vb_get_program_counter(sim: *mut VB) -> u32;
|
|
|
|
#[link_name = "vbGetProgramRegister"]
|
|
|
|
fn vb_get_program_register(sim: *mut VB, index: c_uint) -> i32;
|
2024-11-04 14:59:58 +00:00
|
|
|
#[link_name = "vbGetSamples"]
|
|
|
|
fn vb_get_samples(
|
|
|
|
sim: *mut VB,
|
|
|
|
typ_: *mut VBDataType,
|
|
|
|
capacity: *mut c_uint,
|
|
|
|
position: *mut c_uint,
|
|
|
|
) -> *mut c_void;
|
2025-01-02 02:48:33 +00:00
|
|
|
#[link_name = "vbGetSystemRegister"]
|
|
|
|
fn vb_get_system_register(sim: *mut VB, index: c_uint) -> i32;
|
2024-11-03 16:32:53 +00:00
|
|
|
#[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;
|
2025-01-02 06:10:19 +00:00
|
|
|
#[link_name = "vbRead"]
|
|
|
|
fn vb_read(sim: *mut VB, address: u32, typ_: VBDataType) -> i32;
|
2024-11-03 16:32:53 +00:00
|
|
|
#[link_name = "vbReset"]
|
|
|
|
fn vb_reset(sim: *mut VB);
|
2024-12-07 05:00:40 +00:00
|
|
|
#[link_name = "vbSetCartRAM"]
|
|
|
|
fn vb_set_cart_ram(sim: *mut VB, sram: *mut c_void, size: u32) -> c_int;
|
2024-11-03 16:32:53 +00:00
|
|
|
#[link_name = "vbSetCartROM"]
|
|
|
|
fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int;
|
2025-01-05 05:47:58 +00:00
|
|
|
#[link_name = "vbSetExecuteCallback"]
|
2025-01-13 05:30:47 +00:00
|
|
|
fn vb_set_execute_callback(sim: *mut VB, callback: Option<OnExecute>) -> Option<OnExecute>;
|
2024-11-03 16:32:53 +00:00
|
|
|
#[link_name = "vbSetFrameCallback"]
|
2025-01-13 05:30:47 +00:00
|
|
|
fn vb_set_frame_callback(sim: *mut VB, callback: Option<OnFrame>) -> Option<OnFrame>;
|
2024-11-11 05:50:57 +00:00
|
|
|
#[link_name = "vbSetKeys"]
|
|
|
|
fn vb_set_keys(sim: *mut VB, keys: u16) -> u16;
|
|
|
|
#[link_name = "vbSetOption"]
|
|
|
|
fn vb_set_option(sim: *mut VB, key: VBOption, value: c_int);
|
2024-11-15 02:54:13 +00:00
|
|
|
#[link_name = "vbSetPeer"]
|
|
|
|
fn vb_set_peer(sim: *mut VB, peer: *mut VB);
|
2025-01-18 05:20:57 +00:00
|
|
|
#[link_name = "vbSetProgramCounter"]
|
|
|
|
fn vb_set_program_counter(sim: *mut VB, value: u32) -> u32;
|
|
|
|
#[link_name = "vbSetProgramRegister"]
|
|
|
|
fn vb_set_program_register(sim: *mut VB, index: c_uint, value: i32) -> i32;
|
2025-01-13 05:30:47 +00:00
|
|
|
#[link_name = "vbSetReadCallback"]
|
|
|
|
fn vb_set_read_callback(sim: *mut VB, callback: Option<OnRead>) -> Option<OnRead>;
|
2024-11-04 14:59:58 +00:00
|
|
|
#[link_name = "vbSetSamples"]
|
|
|
|
fn vb_set_samples(
|
|
|
|
sim: *mut VB,
|
|
|
|
samples: *mut c_void,
|
|
|
|
typ_: VBDataType,
|
|
|
|
capacity: c_uint,
|
|
|
|
) -> c_int;
|
2025-01-18 05:20:57 +00:00
|
|
|
#[link_name = "vbSetSystemRegister"]
|
|
|
|
fn vb_set_system_register(sim: *mut VB, index: c_uint, value: u32) -> u32;
|
2024-11-03 16:32:53 +00:00
|
|
|
#[link_name = "vbSetUserData"]
|
|
|
|
fn vb_set_user_data(sim: *mut VB, tag: *mut c_void);
|
2025-01-13 05:30:47 +00:00
|
|
|
#[link_name = "vbSetWriteCallback"]
|
|
|
|
fn vb_set_write_callback(sim: *mut VB, callback: Option<OnWrite>) -> Option<OnWrite>;
|
2024-11-02 20:18:41 +00:00
|
|
|
#[link_name = "vbSizeOf"]
|
|
|
|
fn vb_size_of() -> usize;
|
2025-01-18 06:03:22 +00:00
|
|
|
#[link_name = "vbWrite"]
|
|
|
|
fn vb_write(sim: *mut VB, address: u32, _type: VBDataType, value: i32) -> i32;
|
2024-11-03 16:32:53 +00:00
|
|
|
}
|
|
|
|
|
2025-01-18 18:57:19 +00:00
|
|
|
#[no_mangle]
|
2025-01-05 05:47:58 +00:00
|
|
|
extern "C" fn on_frame(sim: *mut VB) -> c_int {
|
2024-11-03 16:32:53 +00:00
|
|
|
// 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
|
|
|
|
2025-01-18 06:21:45 +00:00
|
|
|
#[no_mangle]
|
2025-01-05 05:47:58 +00:00
|
|
|
extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length: c_int) -> c_int {
|
|
|
|
// 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() };
|
|
|
|
|
2025-01-13 05:30:47 +00:00
|
|
|
let mut stopped = data.stop_reason.is_some();
|
2025-01-05 18:44:59 +00:00
|
|
|
if data.step_from.is_some_and(|s| s != address) {
|
|
|
|
data.step_from = None;
|
|
|
|
data.stop_reason = Some(StopReason::Stepped);
|
2025-01-13 05:30:47 +00:00
|
|
|
stopped = true;
|
2025-01-05 18:44:59 +00:00
|
|
|
}
|
|
|
|
if data.breakpoints.binary_search(&address).is_ok() {
|
|
|
|
data.stop_reason = Some(StopReason::Breakpoint);
|
2025-01-13 05:30:47 +00:00
|
|
|
stopped = true;
|
2025-01-05 05:47:58 +00:00
|
|
|
}
|
|
|
|
|
2025-01-13 05:30:47 +00:00
|
|
|
if stopped {
|
|
|
|
1
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-18 06:21:45 +00:00
|
|
|
#[no_mangle]
|
2025-01-13 05:30:47 +00:00
|
|
|
extern "C" fn on_read(
|
|
|
|
sim: *mut VB,
|
|
|
|
address: u32,
|
|
|
|
_type: VBDataType,
|
|
|
|
_value: *mut i32,
|
|
|
|
_cycles: *mut u32,
|
|
|
|
) -> c_int {
|
|
|
|
// 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() };
|
|
|
|
|
2025-01-18 23:51:25 +00:00
|
|
|
if let Some(start) = data.read_watchpoints.start_of_range_containing(address) {
|
2025-01-13 05:30:47 +00:00
|
|
|
let watch = if data.write_watchpoints.contains(address) {
|
|
|
|
VBWatchpointType::Access
|
|
|
|
} else {
|
|
|
|
VBWatchpointType::Read
|
|
|
|
};
|
2025-01-18 23:51:25 +00:00
|
|
|
data.stop_reason = Some(StopReason::Watchpoint(watch, start));
|
2025-01-13 05:30:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Don't stop here, the debugger expects us to break after the memory access.
|
|
|
|
// We'll stop in on_execute instead.
|
|
|
|
0
|
|
|
|
}
|
|
|
|
|
2025-01-18 06:21:45 +00:00
|
|
|
#[no_mangle]
|
2025-01-13 05:30:47 +00:00
|
|
|
extern "C" fn on_write(
|
|
|
|
sim: *mut VB,
|
|
|
|
address: u32,
|
|
|
|
_type: VBDataType,
|
|
|
|
_value: *mut i32,
|
|
|
|
_cycles: *mut u32,
|
|
|
|
_cancel: *mut c_int,
|
|
|
|
) -> c_int {
|
|
|
|
// 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() };
|
|
|
|
|
2025-01-18 23:51:25 +00:00
|
|
|
if let Some(start) = data.write_watchpoints.start_of_range_containing(address) {
|
2025-01-13 05:30:47 +00:00
|
|
|
let watch = if data.read_watchpoints.contains(address) {
|
|
|
|
VBWatchpointType::Access
|
|
|
|
} else {
|
|
|
|
VBWatchpointType::Write
|
|
|
|
};
|
2025-01-18 23:51:25 +00:00
|
|
|
data.stop_reason = Some(StopReason::Watchpoint(watch, start));
|
2025-01-13 05:30:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Don't stop here, the debugger expects us to break after the memory access.
|
|
|
|
// We'll stop in on_execute instead.
|
|
|
|
0
|
2025-01-05 05:47:58 +00:00
|
|
|
}
|
|
|
|
|
2024-11-04 14:59:58 +00:00
|
|
|
const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4;
|
|
|
|
const AUDIO_CAPACITY_FLOATS: usize = AUDIO_CAPACITY_SAMPLES * 2;
|
2024-11-24 01:17:28 +00:00
|
|
|
pub const EXPECTED_FRAME_SIZE: usize = 834 * 2;
|
2024-11-04 14:59:58 +00:00
|
|
|
|
2024-11-03 16:32:53 +00:00
|
|
|
struct VBState {
|
|
|
|
frame_seen: bool,
|
2025-01-05 05:47:58 +00:00
|
|
|
stop_reason: Option<StopReason>,
|
2025-01-05 18:44:59 +00:00
|
|
|
step_from: Option<u32>,
|
2025-01-05 05:47:58 +00:00
|
|
|
breakpoints: Vec<u32>,
|
2025-01-13 05:30:47 +00:00
|
|
|
read_watchpoints: AddressSet,
|
|
|
|
write_watchpoints: AddressSet,
|
2024-11-02 20:18:41 +00:00
|
|
|
}
|
|
|
|
|
2025-01-13 05:30:47 +00:00
|
|
|
impl VBState {
|
|
|
|
fn needs_execute_callback(&self) -> bool {
|
|
|
|
self.step_from.is_some()
|
|
|
|
|| !self.breakpoints.is_empty()
|
|
|
|
|| !self.read_watchpoints.is_empty()
|
|
|
|
|| !self.write_watchpoints.is_empty()
|
|
|
|
}
|
2024-11-02 20:18:41 +00:00
|
|
|
}
|
|
|
|
|
2025-01-05 05:47:58 +00:00
|
|
|
pub enum StopReason {
|
|
|
|
Breakpoint,
|
2025-01-13 05:30:47 +00:00
|
|
|
Watchpoint(VBWatchpointType, u32),
|
2025-01-05 18:44:59 +00:00
|
|
|
Stepped,
|
2025-01-05 05:47:58 +00:00
|
|
|
}
|
2024-11-03 16:32:53 +00:00
|
|
|
|
2025-01-13 05:30:47 +00:00
|
|
|
#[repr(transparent)]
|
|
|
|
pub struct Sim {
|
|
|
|
sim: *mut VB,
|
|
|
|
}
|
|
|
|
|
2024-11-11 05:50:57 +00:00
|
|
|
impl Sim {
|
2024-11-02 20:18:41 +00:00
|
|
|
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) };
|
2025-01-06 03:08:37 +00:00
|
|
|
// pseudohalt is disabled due to breaking red alarm
|
|
|
|
unsafe { vb_set_option(sim, VBOption::PseudoHalt, 0) };
|
2025-01-05 18:44:59 +00:00
|
|
|
unsafe { vb_set_keys(sim, VBKey::SGN.bits()) };
|
2024-11-03 16:32:53 +00:00
|
|
|
unsafe { vb_reset(sim) };
|
|
|
|
|
|
|
|
// set up userdata
|
2025-01-05 05:47:58 +00:00
|
|
|
let state = VBState {
|
|
|
|
frame_seen: false,
|
|
|
|
stop_reason: None,
|
2025-01-05 18:44:59 +00:00
|
|
|
step_from: None,
|
2025-01-05 05:47:58 +00:00
|
|
|
breakpoints: vec![],
|
2025-01-13 05:30:47 +00:00
|
|
|
read_watchpoints: AddressSet::new(),
|
|
|
|
write_watchpoints: AddressSet::new(),
|
2025-01-05 05:47:58 +00:00
|
|
|
};
|
2024-11-03 16:32:53 +00:00
|
|
|
unsafe { vb_set_user_data(sim, Box::into_raw(Box::new(state)).cast()) };
|
2025-01-05 05:47:58 +00:00
|
|
|
unsafe { vb_set_frame_callback(sim, Some(on_frame)) };
|
2024-11-03 16:32:53 +00:00
|
|
|
|
2024-11-04 14:59:58 +00:00
|
|
|
// 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) };
|
|
|
|
|
2024-11-11 05:50:57 +00:00
|
|
|
Sim { sim }
|
2024-11-03 16:32:53 +00:00
|
|
|
}
|
|
|
|
|
2024-11-05 03:18:57 +00:00
|
|
|
pub fn reset(&mut self) {
|
|
|
|
unsafe { vb_reset(self.sim) };
|
|
|
|
}
|
|
|
|
|
2024-12-07 05:00:40 +00:00
|
|
|
pub fn load_cart(&mut self, mut rom: Vec<u8>, mut sram: Vec<u8>) -> Result<()> {
|
|
|
|
self.unload_cart();
|
|
|
|
|
|
|
|
rom.shrink_to_fit();
|
|
|
|
sram.shrink_to_fit();
|
2024-11-03 16:32:53 +00:00
|
|
|
|
|
|
|
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) };
|
2024-12-07 05:00:40 +00:00
|
|
|
if status != 0 {
|
2024-11-03 16:32:53 +00:00
|
|
|
let _: Vec<u8> =
|
|
|
|
unsafe { Vec::from_raw_parts(rom.cast(), size as usize, size as usize) };
|
2024-12-07 05:00:40 +00:00
|
|
|
return Err(anyhow!("Invalid ROM size of {} bytes", size));
|
|
|
|
}
|
|
|
|
|
|
|
|
let size = sram.len() as u32;
|
|
|
|
let sram = Box::into_raw(sram.into_boxed_slice()).cast();
|
|
|
|
let status = unsafe { vb_set_cart_ram(self.sim, sram, size) };
|
|
|
|
if status != 0 {
|
|
|
|
let _: Vec<u8> =
|
|
|
|
unsafe { Vec::from_raw_parts(sram.cast(), size as usize, size as usize) };
|
|
|
|
return Err(anyhow!("Invalid SRAM size of {} bytes", size));
|
2024-11-02 20:18:41 +00:00
|
|
|
}
|
2024-12-07 05:00:40 +00:00
|
|
|
|
|
|
|
Ok(())
|
2024-11-02 20:18:41 +00:00
|
|
|
}
|
|
|
|
|
2024-12-07 05:00:40 +00:00
|
|
|
fn unload_cart(&mut self) {
|
2024-11-11 05:50:57 +00:00
|
|
|
let mut size = 0;
|
|
|
|
let rom = unsafe { vb_get_cart_rom(self.sim, &mut size) };
|
2024-12-07 05:00:40 +00:00
|
|
|
unsafe { vb_set_cart_rom(self.sim, ptr::null_mut(), 0) };
|
|
|
|
if !rom.is_null() {
|
|
|
|
let _: Vec<u8> =
|
|
|
|
unsafe { Vec::from_raw_parts(rom.cast(), size as usize, size as usize) };
|
|
|
|
}
|
|
|
|
|
|
|
|
let sram = unsafe { vb_get_cart_ram(self.sim, &mut size) };
|
|
|
|
unsafe { vb_set_cart_ram(self.sim, ptr::null_mut(), 0) };
|
|
|
|
if !sram.is_null() {
|
|
|
|
let _: Vec<u8> =
|
|
|
|
unsafe { Vec::from_raw_parts(sram.cast(), size as usize, size as usize) };
|
2024-11-11 05:50:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-07 05:00:40 +00:00
|
|
|
pub fn read_sram(&mut self, buffer: &mut [u8]) {
|
2024-11-03 16:32:53 +00:00
|
|
|
let mut size = 0;
|
2024-12-07 05:00:40 +00:00
|
|
|
let sram = unsafe { vb_get_cart_ram(self.sim, &mut size) };
|
|
|
|
if sram.is_null() {
|
|
|
|
return;
|
2024-11-03 16:32:53 +00:00
|
|
|
}
|
2024-12-07 05:00:40 +00:00
|
|
|
let bytes = unsafe { slice::from_raw_parts(sram.cast(), size as usize) };
|
|
|
|
buffer.copy_from_slice(bytes);
|
2024-11-02 20:18:41 +00:00
|
|
|
}
|
2024-11-03 16:32:53 +00:00
|
|
|
|
2024-11-15 02:54:13 +00:00
|
|
|
pub fn link(&mut self, peer: &mut Sim) {
|
|
|
|
unsafe { vb_set_peer(self.sim, peer.sim) };
|
|
|
|
}
|
|
|
|
|
2024-11-16 02:50:21 +00:00
|
|
|
pub fn unlink(&mut self) {
|
2024-11-17 19:00:08 +00:00
|
|
|
unsafe { vb_set_peer(self.sim, ptr::null_mut()) };
|
2024-11-16 02:50:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn emulate(&mut self) {
|
|
|
|
let mut cycles = 20_000_000;
|
|
|
|
unsafe { vb_emulate(self.sim, &mut cycles) };
|
|
|
|
}
|
|
|
|
|
2024-11-11 05:50:57 +00:00
|
|
|
pub fn emulate_many(sims: &mut [Sim]) {
|
2024-11-03 16:32:53 +00:00
|
|
|
let mut cycles = 20_000_000;
|
2024-11-11 05:50:57 +00:00
|
|
|
let count = sims.len() as c_uint;
|
|
|
|
let sims = sims.as_mut_ptr().cast();
|
|
|
|
unsafe { vb_emulate_ex(sims, count, &mut cycles) };
|
2024-11-03 16:32:53 +00:00
|
|
|
}
|
|
|
|
|
2024-11-03 18:25:20 +00:00
|
|
|
pub fn read_pixels(&mut self, buffers: &mut [u8]) -> bool {
|
2025-01-05 05:47:58 +00:00
|
|
|
let data = self.get_state();
|
2024-11-03 16:32:53 +00:00
|
|
|
if !data.frame_seen {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
data.frame_seen = false;
|
|
|
|
|
2024-11-03 18:25:20 +00:00
|
|
|
// the buffer must be big enough for our data
|
|
|
|
assert!(buffers.len() >= 384 * 224 * 2);
|
2024-11-03 16:32:53 +00:00
|
|
|
unsafe {
|
|
|
|
vb_get_pixels(
|
|
|
|
self.sim,
|
2024-11-03 18:25:20 +00:00
|
|
|
buffers.as_mut_ptr().cast(),
|
|
|
|
2,
|
|
|
|
384 * 2,
|
|
|
|
buffers.as_mut_ptr().offset(1).cast(),
|
|
|
|
2,
|
|
|
|
384 * 2,
|
2024-11-03 16:32:53 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2024-11-11 05:50:57 +00:00
|
|
|
pub fn read_samples(&mut self, samples: &mut Vec<f32>, weight: f32) {
|
2024-11-04 14:59:58 +00:00
|
|
|
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.
|
2024-11-11 05:50:57 +00:00
|
|
|
let read_samples: &[f32] =
|
|
|
|
unsafe { slice::from_raw_parts(ptr.cast(), position as usize * 2) };
|
|
|
|
samples.resize(read_samples.len(), 0.0);
|
|
|
|
for (index, sample) in read_samples.iter().enumerate() {
|
|
|
|
samples[index] += sample * weight;
|
|
|
|
}
|
2024-11-04 14:59:58 +00:00
|
|
|
|
|
|
|
unsafe {
|
|
|
|
vb_set_samples(
|
|
|
|
self.sim,
|
|
|
|
ptr,
|
|
|
|
VBDataType::F32,
|
|
|
|
AUDIO_CAPACITY_SAMPLES as u32,
|
|
|
|
)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-11-05 05:07:48 +00:00
|
|
|
pub fn set_keys(&mut self, keys: VBKey) {
|
|
|
|
unsafe { vb_set_keys(self.sim, keys.bits()) };
|
2024-11-03 16:32:53 +00:00
|
|
|
}
|
2025-01-02 02:48:33 +00:00
|
|
|
|
|
|
|
pub fn read_register(&mut self, register: VBRegister) -> u32 {
|
|
|
|
match register {
|
|
|
|
VBRegister::Program(index) => unsafe {
|
|
|
|
vb_get_program_register(self.sim, index) as u32
|
|
|
|
},
|
|
|
|
VBRegister::System(index) => unsafe { vb_get_system_register(self.sim, index) as u32 },
|
|
|
|
VBRegister::PC => unsafe { vb_get_program_counter(self.sim) },
|
|
|
|
}
|
|
|
|
}
|
2025-01-02 06:10:19 +00:00
|
|
|
|
2025-01-18 05:20:57 +00:00
|
|
|
pub fn write_register(&mut self, register: VBRegister, value: u32) {
|
|
|
|
match register {
|
|
|
|
VBRegister::Program(index) => unsafe {
|
|
|
|
vb_set_program_register(self.sim, index, value as i32);
|
|
|
|
},
|
|
|
|
VBRegister::System(index) => unsafe {
|
|
|
|
vb_set_system_register(self.sim, index, value);
|
|
|
|
},
|
|
|
|
VBRegister::PC => unsafe {
|
|
|
|
vb_set_program_counter(self.sim, value);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-05 18:54:53 +00:00
|
|
|
pub fn read_memory(&mut self, start: u32, length: usize, into: &mut Vec<u8>) {
|
|
|
|
let mut address = start;
|
|
|
|
for _ in 0..length {
|
2025-01-02 06:10:19 +00:00
|
|
|
let byte = unsafe { vb_read(self.sim, address, VBDataType::U8) };
|
|
|
|
into.push(byte as u8);
|
2025-01-05 18:54:53 +00:00
|
|
|
address = address.wrapping_add(1);
|
2025-01-02 06:10:19 +00:00
|
|
|
}
|
|
|
|
}
|
2025-01-05 05:47:58 +00:00
|
|
|
|
2025-01-18 06:03:22 +00:00
|
|
|
pub fn write_memory(&mut self, start: u32, buffer: &[u8]) {
|
|
|
|
let mut address = start;
|
|
|
|
for byte in buffer {
|
|
|
|
unsafe { vb_write(self.sim, address, VBDataType::U8, *byte as i32) };
|
|
|
|
address = address.wrapping_add(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-05 05:47:58 +00:00
|
|
|
pub fn add_breakpoint(&mut self, address: u32) {
|
|
|
|
let data = self.get_state();
|
|
|
|
if let Err(index) = data.breakpoints.binary_search(&address) {
|
|
|
|
data.breakpoints.insert(index, address);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
vb_set_execute_callback(self.sim, Some(on_execute));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_breakpoint(&mut self, address: u32) {
|
|
|
|
let data = self.get_state();
|
|
|
|
if let Ok(index) = data.breakpoints.binary_search(&address) {
|
|
|
|
data.breakpoints.remove(index);
|
2025-01-13 05:30:47 +00:00
|
|
|
if !data.needs_execute_callback() {
|
|
|
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_watchpoint(&mut self, address: u32, length: usize, watch: VBWatchpointType) {
|
|
|
|
match watch {
|
|
|
|
VBWatchpointType::Read => self.add_read_watchpoint(address, length),
|
|
|
|
VBWatchpointType::Write => self.add_write_watchpoint(address, length),
|
|
|
|
VBWatchpointType::Access => {
|
|
|
|
self.add_read_watchpoint(address, length);
|
|
|
|
self.add_write_watchpoint(address, length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_read_watchpoint(&mut self, address: u32, length: usize) {
|
|
|
|
let state = self.get_state();
|
|
|
|
state.read_watchpoints.add(address, length);
|
|
|
|
if !state.read_watchpoints.is_empty() {
|
|
|
|
unsafe { vb_set_read_callback(self.sim, Some(on_read)) };
|
|
|
|
unsafe { vb_set_execute_callback(self.sim, Some(on_execute)) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_write_watchpoint(&mut self, address: u32, length: usize) {
|
|
|
|
let state = self.get_state();
|
|
|
|
state.write_watchpoints.add(address, length);
|
|
|
|
if !state.write_watchpoints.is_empty() {
|
|
|
|
unsafe { vb_set_write_callback(self.sim, Some(on_write)) };
|
|
|
|
unsafe { vb_set_execute_callback(self.sim, Some(on_execute)) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_watchpoint(&mut self, address: u32, length: usize, watch: VBWatchpointType) {
|
|
|
|
match watch {
|
|
|
|
VBWatchpointType::Read => self.remove_read_watchpoint(address, length),
|
|
|
|
VBWatchpointType::Write => self.remove_write_watchpoint(address, length),
|
|
|
|
VBWatchpointType::Access => {
|
|
|
|
self.remove_read_watchpoint(address, length);
|
|
|
|
self.remove_write_watchpoint(address, length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn remove_read_watchpoint(&mut self, address: u32, length: usize) {
|
|
|
|
let state = self.get_state();
|
|
|
|
state.read_watchpoints.remove(address, length);
|
|
|
|
let needs_execute = state.needs_execute_callback();
|
|
|
|
if state.read_watchpoints.is_empty() {
|
|
|
|
unsafe { vb_set_read_callback(self.sim, None) };
|
|
|
|
if !needs_execute {
|
|
|
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn remove_write_watchpoint(&mut self, address: u32, length: usize) {
|
|
|
|
let state = self.get_state();
|
|
|
|
state.write_watchpoints.remove(address, length);
|
|
|
|
let needs_execute = state.needs_execute_callback();
|
|
|
|
if state.write_watchpoints.is_empty() {
|
|
|
|
unsafe { vb_set_write_callback(self.sim, None) };
|
|
|
|
if !needs_execute {
|
2025-01-05 05:47:58 +00:00
|
|
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-05 18:44:59 +00:00
|
|
|
pub fn step(&mut self) {
|
|
|
|
let current_pc = unsafe { vb_get_program_counter(self.sim) };
|
2025-01-05 05:47:58 +00:00
|
|
|
let data = self.get_state();
|
2025-01-05 18:44:59 +00:00
|
|
|
data.step_from = Some(current_pc);
|
|
|
|
unsafe {
|
|
|
|
vb_set_execute_callback(self.sim, Some(on_execute));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear_debug_state(&mut self) {
|
|
|
|
let data = self.get_state();
|
|
|
|
data.step_from = None;
|
2025-01-05 05:47:58 +00:00
|
|
|
data.breakpoints.clear();
|
2025-01-13 05:30:47 +00:00
|
|
|
data.read_watchpoints.clear();
|
|
|
|
data.write_watchpoints.clear();
|
|
|
|
unsafe { vb_set_read_callback(self.sim, None) };
|
|
|
|
unsafe { vb_set_write_callback(self.sim, None) };
|
2025-01-05 05:47:58 +00:00
|
|
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn stop_reason(&mut self) -> Option<StopReason> {
|
|
|
|
let data = self.get_state();
|
2025-01-05 18:44:59 +00:00
|
|
|
let reason = data.stop_reason.take();
|
2025-01-13 05:30:47 +00:00
|
|
|
if !data.needs_execute_callback() {
|
2025-01-05 18:44:59 +00:00
|
|
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
|
|
|
}
|
|
|
|
reason
|
2025-01-05 05:47:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_state(&mut self) -> &mut VBState {
|
|
|
|
// SAFETY: the *mut VB owns its userdata.
|
|
|
|
// There is no way for the userdata to be null or otherwise invalid.
|
|
|
|
unsafe { &mut *vb_get_user_data(self.sim).cast() }
|
|
|
|
}
|
2024-11-03 16:32:53 +00:00
|
|
|
}
|
|
|
|
|
2024-11-11 05:50:57 +00:00
|
|
|
impl Drop for Sim {
|
2024-11-03 16:32:53 +00:00
|
|
|
fn drop(&mut self) {
|
2024-11-04 14:59:58 +00:00
|
|
|
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);
|
|
|
|
|
2024-11-03 16:32:53 +00:00
|
|
|
// 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)) };
|
|
|
|
|
2024-11-17 19:00:08 +00:00
|
|
|
// If we're linked to another sim, unlink from them.
|
|
|
|
unsafe { vb_set_peer(self.sim, ptr::null_mut()) };
|
2024-11-15 02:54:13 +00:00
|
|
|
|
2024-11-03 16:32:53 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|