Newer
Older
collections::HashMap,
fmt::{self, Display, Formatter},
os::raw::{c_char, c_float, c_uint, c_void},
slice::from_raw_parts,
};
ppu::{PpuMode, DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_RGB155_SIZE, RGB1555_SIZE},
use consts::{
RETRO_DEVICE_ID_JOYPAD_A, RETRO_DEVICE_ID_JOYPAD_B, RETRO_DEVICE_ID_JOYPAD_DOWN,
RETRO_DEVICE_ID_JOYPAD_L, RETRO_DEVICE_ID_JOYPAD_L2, RETRO_DEVICE_ID_JOYPAD_L3,
RETRO_DEVICE_ID_JOYPAD_LEFT, RETRO_DEVICE_ID_JOYPAD_R, RETRO_DEVICE_ID_JOYPAD_R2,
RETRO_DEVICE_ID_JOYPAD_R3, RETRO_DEVICE_ID_JOYPAD_RIGHT, RETRO_DEVICE_ID_JOYPAD_SELECT,
RETRO_DEVICE_ID_JOYPAD_START, RETRO_DEVICE_ID_JOYPAD_UP, RETRO_DEVICE_ID_JOYPAD_X,
RETRO_DEVICE_ID_JOYPAD_Y, RETRO_DEVICE_JOYPAD,
};
use crate::consts::{REGION_NTSC, RETRO_API_VERSION};
static mut EMULATOR: Option<GameBoy> = None;
static mut KEY_STATES: Option<HashMap<RetroJoypad, bool>> = None;
static mut FRAME_BUFFER: [u8; FRAME_BUFFER_RGB155_SIZE] = [0x00; FRAME_BUFFER_RGB155_SIZE];
static mut ENVIRONMENT_CALLBACK: Option<extern "C" fn(u32, *const c_void) -> bool> = None;
static mut VIDEO_REFRESH_CALLBACK: Option<extern "C" fn(*const u8, c_uint, c_uint, usize)> = None;
static mut AUDIO_SAMPLE_CALLBACK: Option<extern "C" fn(i16, i16)> = None;
static mut AUDIO_SAMPLE_BATCH_CALLBACK: Option<extern "C" fn(*const i16, usize)> = None;
static mut INPUT_POLL_CALLBACK: Option<extern "C" fn()> = None;
static mut INPUT_STATE_CALLBACK: Option<
extern "C" fn(port: u32, device: u32, index: u32, id: u32) -> i16,
> = None;
const KEYS: [RetroJoypad; 8] = [
RetroJoypad::RetroDeviceIdJoypadUp,
RetroJoypad::RetroDeviceIdJoypadDown,
RetroJoypad::RetroDeviceIdJoypadLeft,
RetroJoypad::RetroDeviceIdJoypadRight,
RetroJoypad::RetroDeviceIdJoypadStart,
RetroJoypad::RetroDeviceIdJoypadSelect,
RetroJoypad::RetroDeviceIdJoypadA,
RetroJoypad::RetroDeviceIdJoypadB,
];
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum RetroJoypad {
RetroDeviceIdJoypadB = RETRO_DEVICE_ID_JOYPAD_B,
RetroDeviceIdJoypadY = RETRO_DEVICE_ID_JOYPAD_Y,
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
RetroDeviceIdJoypadSelect = RETRO_DEVICE_ID_JOYPAD_SELECT,
RetroDeviceIdJoypadStart = RETRO_DEVICE_ID_JOYPAD_START,
RetroDeviceIdJoypadUp = RETRO_DEVICE_ID_JOYPAD_UP,
RetroDeviceIdJoypadDown = RETRO_DEVICE_ID_JOYPAD_DOWN,
RetroDeviceIdJoypadLeft = RETRO_DEVICE_ID_JOYPAD_LEFT,
RetroDeviceIdJoypadRight = RETRO_DEVICE_ID_JOYPAD_RIGHT,
RetroDeviceIdJoypadA = RETRO_DEVICE_ID_JOYPAD_A,
RetroDeviceIdJoypadX = RETRO_DEVICE_ID_JOYPAD_X,
RetroDeviceIdJoypadL = RETRO_DEVICE_ID_JOYPAD_L,
RetroDeviceIdJoypadR = RETRO_DEVICE_ID_JOYPAD_R,
RetroDeviceIdJoypadL2 = RETRO_DEVICE_ID_JOYPAD_L2,
RetroDeviceIdJoypadR2 = RETRO_DEVICE_ID_JOYPAD_R2,
RetroDeviceIdJoypadL3 = RETRO_DEVICE_ID_JOYPAD_L3,
RetroDeviceIdJoypadR3 = RETRO_DEVICE_ID_JOYPAD_R3,
}
impl RetroJoypad {
pub fn description(&self) -> &'static str {
match self {
RetroJoypad::RetroDeviceIdJoypadY => "Y",
RetroJoypad::RetroDeviceIdJoypadB => "B",
RetroJoypad::RetroDeviceIdJoypadSelect => "Select",
RetroJoypad::RetroDeviceIdJoypadStart => "Start",
RetroJoypad::RetroDeviceIdJoypadUp => "Up",
RetroJoypad::RetroDeviceIdJoypadDown => "Down",
RetroJoypad::RetroDeviceIdJoypadLeft => "Left",
RetroJoypad::RetroDeviceIdJoypadRight => "Right",
RetroJoypad::RetroDeviceIdJoypadA => "A",
RetroJoypad::RetroDeviceIdJoypadX => "X",
RetroJoypad::RetroDeviceIdJoypadL => "L",
RetroJoypad::RetroDeviceIdJoypadR => "R",
RetroJoypad::RetroDeviceIdJoypadL2 => "L2",
RetroJoypad::RetroDeviceIdJoypadR2 => "R2",
RetroJoypad::RetroDeviceIdJoypadL3 => "L3",
RetroJoypad::RetroDeviceIdJoypadR3 => "R3",
}
}
}
impl Display for RetroJoypad {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
#[repr(C)]
pub struct RetroGameInfo {
pub path: *const c_char,
pub data: *const c_void,
pub size: usize,
pub meta: *const c_char,
}
pub library_name: *const c_char,
pub library_version: *const c_char,
pub valid_extensions: *const c_char,
pub need_fullpath: bool,
pub block_extract: bool,
}
#[repr(C)]
pub struct RetroGameGeometry {
pub base_width: c_uint,
pub base_height: c_uint,
pub max_width: c_uint,
pub max_height: c_uint,
pub aspect_ratio: c_float,
}
#[repr(C)]
pub struct RetroSystemAvInfo {
geometry: RetroGameGeometry,
timing: RetroSystemTiming,
}
#[repr(C)]
pub struct RetroSystemTiming {
fps: f64,
sample_rate: f64,
}
#[no_mangle]
pub extern "C" fn retro_api_version() -> c_uint {
println!("retro_api_version()");
RETRO_API_VERSION
}
unsafe {
EMULATOR = Some(GameBoy::new(None));
KEY_STATES = Some(HashMap::new());
pub extern "C" fn retro_deinit() {
println!("retro_deinit()");
pub extern "C" fn retro_reset() {
println!("retro_reset()");
let emulator = unsafe { EMULATOR.as_mut().unwrap() };
emulator.reload();
/// # Safety
///
/// This function should not be called only within Lib Retro context.
pub unsafe extern "C" fn retro_get_system_info(info: *mut RetroSystemInfo) {
println!("retro_get_system_info()");
(*info).library_name = "Boytacean\0".as_ptr() as *const c_char;
(*info).library_version = "v0.9.6\0".as_ptr() as *const c_char;
(*info).valid_extensions = "gb|gbc\0".as_ptr() as *const c_char;
(*info).need_fullpath = false;
(*info).block_extract = false;
/// # Safety
///
/// This function should not be called only within Lib Retro context.
pub unsafe extern "C" fn retro_get_system_av_info(info: *mut RetroSystemAvInfo) {
(*info).geometry.base_width = DISPLAY_WIDTH as u32;
(*info).geometry.base_height = DISPLAY_HEIGHT as u32;
(*info).geometry.max_width = DISPLAY_WIDTH as u32 * 64;
(*info).geometry.max_height = DISPLAY_HEIGHT as u32 * 64;
(*info).geometry.aspect_ratio = DISPLAY_WIDTH as f32 / DISPLAY_HEIGHT as f32;
(*info).timing.fps = GameBoy::VISUAL_FREQ as f64;
(*info).timing.sample_rate = EMULATOR.as_ref().unwrap().audio_sampling_rate() as f64;
pub extern "C" fn retro_set_environment(
callback: Option<extern "C" fn(u32, *const c_void) -> bool>,
) {
unsafe {
ENVIRONMENT_CALLBACK = callback;
}
}
#[no_mangle]
pub extern "C" fn retro_set_controller_port_device() {
println!("retro_set_controller_port_device()");
}
#[no_mangle]
pub extern "C" fn retro_run() {
let emulator = unsafe { EMULATOR.as_mut().unwrap() };
let video_refresh_cb = unsafe { VIDEO_REFRESH_CALLBACK.as_ref().unwrap() };
let sample_batch_cb = unsafe { AUDIO_SAMPLE_BATCH_CALLBACK.as_ref().unwrap() };
let input_poll_cb = unsafe { INPUT_POLL_CALLBACK.as_ref().unwrap() };
let input_state_cb = unsafe { INPUT_STATE_CALLBACK.as_ref().unwrap() };
let key_states = unsafe { KEY_STATES.as_mut().unwrap() };
let channels = emulator.audio_channels();
let mut last_frame = emulator.ppu_frame();
let mut counter_cycles = 0_u32;
let cycle_limit = (GameBoy::CPU_FREQ as f32 * emulator.multiplier() as f32
/ GameBoy::VISUAL_FREQ)
.round() as u32;
loop {
// limits the number of ticks to the typical number
// of cycles expected for the current logic cycle
if counter_cycles >= cycle_limit {
//pending_cycles = counter_cycles - cycle_limit;
break;
}
// runs the Game Boy clock, this operation should
// include the advance of both the CPU, PPU, APU
// and any other frequency based component of the system
counter_cycles += emulator.clock() as u32;
// in case a new frame is available in the emulator
// then the frame must be pushed into display
if emulator.ppu_frame() != last_frame {
let frame_buffer = emulator.frame_buffer_rgb1555();
unsafe {
FRAME_BUFFER.copy_from_slice(&frame_buffer);
video_refresh_cb(
FRAME_BUFFER.as_ptr(),
DISPLAY_WIDTH as u32,
DISPLAY_HEIGHT as u32,
DISPLAY_WIDTH * RGB1555_SIZE,
);
}
// obtains the index of the current PPU frame, this value
// is going to be used to detect for new frame presence
last_frame = emulator.ppu_frame();
}
// obtains the audio buffer reference and queues it
// in a batch manner using the audio callback at the
// the end of the operation clears the buffer
let audio_buffer = emulator
.audio_buffer()
.iter()
.map(|v| *v as i16 * 256)
.collect::<Vec<i16>>();
sample_batch_cb(
audio_buffer.as_ptr(),
audio_buffer.len() / channels as usize,
);
emulator.clear_audio_buffer();
input_poll_cb();
for key in KEYS {
let key_pad = retro_key_to_pad(key).unwrap();
let current = input_state_cb(0, RETRO_DEVICE_JOYPAD as u32, 0, key as u32) > 0;
let previous = key_states.get(&key).unwrap_or(&false);
if current != *previous {
if current {
emulator.key_press(key_pad);
} else {
emulator.key_lift(key_pad);
pub extern "C" fn retro_get_region() -> u32 {
println!("retro_get_region()");
REGION_NTSC
}
/// # Safety
///
/// This function should not be called only within Lib Retro context.
pub unsafe extern "C" fn retro_load_game(game: *const RetroGameInfo) -> bool {
let instance = EMULATOR.as_mut().unwrap();
let data_buffer = from_raw_parts((*game).data as *const u8, (*game).size);
let rom = Cartridge::from_data(data_buffer);
let mode = rom.gb_mode();
instance.set_mode(mode);
instance.reset();
instance.load(true);
#[no_mangle]
pub extern "C" fn retro_load_game_special(
_system: u32,
_info: *const RetroGameInfo,
_num_info: usize,
) -> bool {
println!("retro_load_game_special()");
false
}
#[no_mangle]
pub extern "C" fn retro_unload_game() {
println!("retro_unload_game()");
}
#[no_mangle]
pub extern "C" fn retro_get_memory_data(_memory_id: u32) -> *mut c_void {
pub extern "C" fn retro_get_memory_size(_memory_id: u32) -> usize {
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
}
#[no_mangle]
pub extern "C" fn retro_serialize_size() {
println!("retro_serialize_size()");
}
#[no_mangle]
pub extern "C" fn retro_serialize() {
println!("retro_serialize()");
}
#[no_mangle]
pub extern "C" fn retro_unserialize() {
println!("retro_unserialize()");
}
#[no_mangle]
pub extern "C" fn retro_cheat_reset() {
println!("retro_cheat_reset()");
}
#[no_mangle]
pub extern "C" fn retro_cheat_set() {
println!("retro_cheat_set()");
}
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
#[no_mangle]
pub extern "C" fn retro_set_video_refresh(
callback: Option<extern "C" fn(*const u8, c_uint, c_uint, usize)>,
) {
println!("retro_set_video_refresh()");
unsafe {
VIDEO_REFRESH_CALLBACK = callback;
}
}
#[no_mangle]
pub extern "C" fn retro_set_audio_sample(callback: Option<extern "C" fn(i16, i16)>) {
println!("retro_set_audio_sample()");
unsafe {
AUDIO_SAMPLE_CALLBACK = callback;
}
}
#[no_mangle]
pub extern "C" fn retro_set_audio_sample_batch(callback: Option<extern "C" fn(*const i16, usize)>) {
println!("retro_set_audio_sample_batch()");
unsafe {
AUDIO_SAMPLE_BATCH_CALLBACK = callback;
}
}
#[no_mangle]
pub extern "C" fn retro_set_input_poll(callback: Option<extern "C" fn()>) {
println!("retro_set_input_poll()");
unsafe {
INPUT_POLL_CALLBACK = callback;
}
}
#[no_mangle]
pub extern "C" fn retro_set_input_state(
callback: Option<extern "C" fn(port: u32, device: u32, index: u32, id: u32) -> i16>,
) {
println!("retro_set_input_state()");
unsafe {
INPUT_STATE_CALLBACK = callback;
}
}
fn retro_key_to_pad(retro_key: RetroJoypad) -> Option<PadKey> {
match retro_key {
RetroJoypad::RetroDeviceIdJoypadUp => Some(PadKey::Up),
RetroJoypad::RetroDeviceIdJoypadDown => Some(PadKey::Down),
RetroJoypad::RetroDeviceIdJoypadLeft => Some(PadKey::Left),
RetroJoypad::RetroDeviceIdJoypadRight => Some(PadKey::Right),
RetroJoypad::RetroDeviceIdJoypadStart => Some(PadKey::Start),
RetroJoypad::RetroDeviceIdJoypadSelect => Some(PadKey::Select),
RetroJoypad::RetroDeviceIdJoypadA => Some(PadKey::A),
RetroJoypad::RetroDeviceIdJoypadB => Some(PadKey::B),
_ => None,
}
}