use ferretro::retro; use ferretro::retro::ffi::{GameGeometry, SystemInfo, SystemAvInfo, MemoryMap}; use ferretro::retro::constants::{InputIndex, JoypadButton, AnalogAxis, DeviceType}; use ferretro::retro::wrapped_types::{ControllerDescription2, InputDescriptor2, InputDeviceId, SubsystemInfo2, Variable2}; use ferretro::retro::wrapper::LibretroWrapper; use crate::sync::game::{SyncableGame, KnownGames, read_game_name, create_game}; use std::ffi::CStr; use std::io::Read; use std::pin::Pin; use std::path::{Path, PathBuf}; use std::time::{Duration, Instant}; use std::convert::TryFrom; use sdl2::audio::{AudioCallback, AudioFormat, AudioSpec, AudioSpecDesired, AudioDevice}; use sdl2::controller::{GameController, Button, Axis}; use sdl2::event::Event; use sdl2::keyboard::Keycode; use sdl2::rect::Rect; use sdl2::render::WindowCanvas; pub struct MyEmulator { retro: retro::wrapper::LibretroWrapper, core_path: PathBuf, sys_path: Option, preferred_pad: Option, sys_info: SystemInfo, av_info: SystemAvInfo, sdl_context: sdl2::Sdl, // video bits canvas: WindowCanvas, pixel_format: sdl2::pixels::PixelFormatEnum, // audio bits audio_buffer: Vec, audio_spec: AudioSpec, audio_device: AudioDevice, audio_sender: crossbeam_channel::Sender>, // input bits gamepad_subsys: sdl2::GameControllerSubsystem, gamepads: Vec, pressed_keys: Vec, pub memory_map: Option, synced_game: Option>, } impl MyEmulator { pub fn new(core_path: impl AsRef, sys_path: &Option>) -> Pin> { let core_path = PathBuf::from(core_path.as_ref()); let lib = libloading::Library::new(&core_path).unwrap(); let raw_retro = retro::loading::LibretroApi::from_library(lib).unwrap(); let retro = retro::wrapper::LibretroWrapper::from(raw_retro); let sys_info = retro.get_system_info(); let title = format!( "{} - rust libretro", unsafe { CStr::from_ptr(sys_info.library_name) }.to_string_lossy() ); let mut av_info = retro.get_system_av_info(); let pixel_format = sdl2::pixels::PixelFormatEnum::ARGB1555; // HACK: some cores don't report this 'til we get an environ call to set_system_av_info... // which is too late for this constructor to pass along to SDL. if av_info.timing.sample_rate == 0.0 { av_info.timing.sample_rate = 32040.0; } let sdl_context = sdl2::init().unwrap(); let window = sdl_context .video() .unwrap() .window(title.as_str(), av_info.geometry.base_width, av_info.geometry.base_height) .opengl() .build() .unwrap(); let canvas = window.into_canvas().build().unwrap(); let (audio_sender, audio_receiver) = crossbeam_channel::bounded(2); let audio = sdl_context.audio().unwrap(); let desired_spec = AudioSpecDesired { freq: Some(av_info.timing.sample_rate.round() as i32), channels: Some(2), samples: None, }; let mut audio_spec = None; let audio_device = audio .open_playback(None, &desired_spec, |spec| { if spec.format != AudioFormat::S16LSB { println!("unsupported audio format {:?}", spec.format); } audio_spec = Some(spec.clone()); MySdlAudio { audio_spec: spec, audio_receiver, } }) .unwrap(); let gamepad_subsys = sdl_context.game_controller().unwrap(); let mut gamepads = Vec::new(); for i in 0..gamepad_subsys.num_joysticks().unwrap() { gamepads.extend(gamepad_subsys.open(i).into_iter()); } let pressed_keys = Vec::new(); /* let memory_map = std::default::Default::default(); let which_game = read_game_name("whatever"); let synced_pokemonrb = SyncedPokemonRedBlue::create(memory_map); */ let emu = MyEmulator { retro, core_path, sys_path: sys_path.as_ref().map(|p| p.as_ref().to_path_buf()), preferred_pad: None, av_info, sys_info, sdl_context, canvas, pixel_format, audio_buffer: Default::default(), audio_spec: audio_spec.unwrap(), audio_device, audio_sender, gamepad_subsys, gamepads, pressed_keys, memory_map: None, synced_game: None, }; let mut pin_emu = Box::pin(emu); retro::wrapper::set_handler(pin_emu.as_mut()); pin_emu.retro.init(); pin_emu } pub fn begin_sync(&mut self, synced_game: Box){ self.synced_game = Some(synced_game); } pub fn run(&mut self) { self.audio_device.resume(); let mut event_pump = self.sdl_context.event_pump().unwrap(); 'running: loop { let frame_begin = Instant::now(); for event in event_pump.poll_iter() { match event { Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => break 'running, _ => {} } } self.update_key_state(&event_pump.keyboard_state()); match &mut self.synced_game { Some(g) => { match g.handle_inbound_messages() { Ok(_) => (), Err(e) => { println!("Error while handling inbound messages: {:?}", e); } } }, None => () } // The rest of the game loop goes here... self.retro.run(); self.canvas.present(); // similar hack to the sample rate, make sure we don't divide by zero. let mut spf = 1.0 / self.av_info.timing.fps; if spf.is_nan() || spf.is_infinite() { spf = 1.0 / 60.0; } match &mut self.synced_game { Some(g) => { g.update_state(); g.send_state_messages(); }, None => () } //println!("{}", self.synced_pokemonrb); Duration::from_secs_f64(spf) .checked_sub(frame_begin.elapsed()) .map(std::thread::sleep); } } pub fn load_game(&self, rom: impl AsRef) { let path = rom.as_ref(); let mut data = None; let mut v = Vec::new(); if !self.sys_info.need_fullpath { if let Ok(mut f) = std::fs::File::open(path) { if f.read_to_end(&mut v).is_ok() { data = Some(v.as_ref()); } } } self.retro .load_game(Some(path), data, None) .unwrap(); if let Some(device) = self.preferred_pad { for port in 0..self.gamepads.len() as u32 { self.retro.set_controller_port_device(port, device); } } } pub fn load_state(&self, state: impl AsRef) { let path = state.as_ref(); let mut v = Vec::new(); if let Ok(mut f) = std::fs::File::open(path) { if f.read_to_end(&mut v).is_ok() { let data = v.as_ref(); self.retro .unserialize(data) .unwrap(); } } } pub fn update_key_state<'a>(&mut self, keyboard_state: &sdl2::keyboard::KeyboardState<'a>){ let keys: Vec = keyboard_state.pressed_scancodes().filter_map(Keycode::from_scancode).collect(); self.pressed_keys = keys; } fn send_audio_samples(&mut self) { let stereo_samples = self.audio_spec.samples as usize * 2; while self.audio_buffer.len() >= stereo_samples { let remainder = self.audio_buffer.split_off(stereo_samples); let msg = std::mem::replace(&mut self.audio_buffer, remainder); let _ = self.audio_sender.try_send(msg); } } fn input_state_gamepad(&mut self, port: u32, device: &InputDeviceId, index: &InputIndex) -> i16 { match self.gamepads.get(port as usize) { Some(gamepad) => { match device { InputDeviceId::Joypad(button) => { match button_map(&button) { Some(x) => gamepad.button(x) as i16, None => match button { JoypadButton::L2 => gamepad.axis(Axis::TriggerLeft), JoypadButton::R2 => gamepad.axis(Axis::TriggerRight), _ => 0, } } } InputDeviceId::Analog(axis) => gamepad.axis(axis_map(index, axis)), _ => 0, } } None => 0, } } fn input_state_keyboard(&mut self, port: u32, device: &InputDeviceId, index: &InputIndex) -> i16 { if port != 0 { // Keyboard only controls the first port. return 0; } match device { InputDeviceId::Joypad(button) => { match keyboard_map(&button) { Some(x) => return if self.pressed_keys.contains(&x) {1} else {0}, None => 0 } }, _ => 0 } } } impl Drop for MyEmulator { fn drop(&mut self) { retro::wrapper::unset_handler(); } } impl retro::wrapper::Handler for MyEmulator { fn libretro_core(&mut self) -> &mut LibretroWrapper { &mut self.retro } fn video_refresh(&mut self, data: &[u8], width: u32, height: u32, pitch: u32) { let rect = Rect::new(0, 0, width, height); if let Ok(mut tex) = self.canvas .texture_creator() .create_texture_static(self.pixel_format, width, height) { if tex.update(rect, data, pitch as usize).is_ok() { self.canvas.clear(); self.canvas.copy(&tex, None, None).unwrap(); } } } fn audio_sample(&mut self, left: i16, right: i16) { self.audio_buffer.push(left); self.audio_buffer.push(right); self.send_audio_samples() } fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize { self.audio_buffer.extend(stereo_pcm); self.send_audio_samples(); stereo_pcm.len() } fn input_poll(&mut self) { self.gamepad_subsys.update(); } fn input_state(&mut self, port: u32, device: InputDeviceId, index: InputIndex) -> i16 { let gamepad_state = self.input_state_gamepad(port, &device, &index); if gamepad_state > 0 { return gamepad_state; } return self.input_state_keyboard(port, &device, &index); } fn get_can_dupe(&mut self) -> Option { Some(true) } fn get_system_directory(&mut self) -> Option { self.sys_path.clone() } fn set_pixel_format(&mut self, pix_fmt: retro::ffi::PixelFormat) -> bool { self.pixel_format = match pix_fmt { retro::ffi::PixelFormat::ARGB1555 => sdl2::pixels::PixelFormatEnum::RGB555, retro::ffi::PixelFormat::ARGB8888 => sdl2::pixels::PixelFormatEnum::ARGB8888, retro::ffi::PixelFormat::RGB565 => sdl2::pixels::PixelFormatEnum::RGB565, }; true } fn get_variable(&mut self, key: &str) -> Option { match key { "beetle_saturn_analog_stick_deadzone" => Some("15%".to_string()), "parallel-n64-gfxplugin" => Some("angrylion".to_string()), "parallel-n64-astick-deadzone" => Some("15%".to_string()), _ => None, } } fn set_variables(&mut self, variables: Vec) -> bool { for v in variables { eprintln!("{:?}", v); } true } fn get_libretro_path(&mut self) -> Option { Some(self.core_path.clone()) } fn get_input_device_capabilities(&mut self) -> Option { let bits = (1 << (DeviceType::Joypad as u32)) | (1 << (DeviceType::Analog as u32)); Some(bits as u64) } fn get_save_directory(&mut self) -> Option { Some(std::env::temp_dir()) } fn set_system_av_info(&mut self, av_info: SystemAvInfo) -> bool { self.set_geometry(av_info.geometry.clone()); self.av_info = av_info; true } fn set_subsystem_info(&mut self, subsystem_info: Vec) -> bool { println!("subsystem info: {:?}", subsystem_info); true } fn set_controller_info(&mut self, controller_info: Vec) -> bool { for ci in controller_info { // so we can have analog support in beetle/mednafen saturn if ci.name.as_str() == "3D Control Pad" { self.preferred_pad = Some(ci.device_id()); break; } } true } fn set_input_descriptors(&mut self, descriptors: Vec) -> bool { for id in descriptors { println!("{:?}", id); } true } fn set_geometry(&mut self, geom: GameGeometry) -> bool { let _ = self.canvas.window_mut().set_size(geom.base_width, geom.base_height); let _ = self.canvas.set_logical_size(geom.base_width, geom.base_height); self.av_info.geometry = geom; true } fn log_print(&mut self, level: retro::ffi::LogLevel, msg: &str) { eprint!("[{:?}] {}", level, msg); } fn set_memory_maps(&mut self, memory_map: MemoryMap) -> bool { self.memory_map = Some(memory_map); true } } struct MySdlAudio { audio_spec: AudioSpec, audio_receiver: crossbeam_channel::Receiver>, } impl AudioCallback for MySdlAudio { type Channel = i16; fn callback(&mut self, out: &mut [Self::Channel]) { if self.audio_spec.format == AudioFormat::S16LSB { if let Ok(samples) = self.audio_receiver.recv() { out.copy_from_slice(&samples[..out.len()]); } } } } fn button_map(retro_button: &JoypadButton) -> Option