extern crate crossbeam_channel; extern crate rustro; extern crate sdl2; use rustro::retro; use rustro::retro::ffi::{GameGeometry, SystemAvInfo}; use rustro::retro::constants::{Input, DeviceIndex, JoypadButton, AnalogAxis}; use std::ffi::CStr; use std::io::Read; use std::path::{Path, PathBuf}; use std::pin::Pin; use std::time::{Duration, Instant}; use structopt::StructOpt; 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::{TextureCreator, WindowCanvas}; use sdl2::video::WindowContext; struct MyEmulator { retro: retro::wrapper::LibretroWrapper, av_info: SystemAvInfo, sdl_context: sdl2::Sdl, // video bits canvas: WindowCanvas, pixel_format: sdl2::pixels::PixelFormatEnum, texture_creator: TextureCreator, // audio bits audio_buffer: Vec, audio_spec: AudioSpec, audio_device: AudioDevice, audio_sender: crossbeam_channel::Sender>, // input bits gamepad_subsys: sdl2::GameControllerSubsystem, gamepads: Vec, } impl MyEmulator { pub fn with_core(core: impl AsRef) -> Pin> { let lib = libloading::Library::new(core.as_ref()).unwrap(); let raw_retro = retro::loading::LibretroApi::from_library(lib).unwrap(); let retro = retro::wrapper::LibretroWrapper::from(raw_retro); let title = format!( "{} - rust libretro", unsafe { CStr::from_ptr(retro.as_ref().get_system_info().library_name) } .to_string_lossy() ); let mut av_info = retro.as_ref().get_system_av_info(); let pixel_format = sdl2::pixels::PixelFormatEnum::ABGR1555; // 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; } // similar hack, this one making sure we don't divide by zero in our frame limiter. if av_info.timing.fps == 0.0 { av_info.timing.fps = 60.0; } let sdl_context = sdl2::init().unwrap(); let (width, height) = (av_info.geometry.base_width, av_info.geometry.base_height); let window = sdl_context .video() .unwrap() .window(title.as_str(), width, height) .opengl() .build() .unwrap(); let canvas = window.into_canvas().build().unwrap(); let texture_creator = canvas.texture_creator(); 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 emu = MyEmulator { retro, av_info, sdl_context, canvas, pixel_format, texture_creator, audio_buffer: Default::default(), audio_spec: audio_spec.unwrap(), audio_device, audio_sender, gamepad_subsys, gamepads, }; let mut pin_emu = Box::pin(emu); retro::wrapper::set_handler(pin_emu.as_mut()); pin_emu.retro.as_ref().init(); pin_emu } 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, _ => {} } } // The rest of the game loop goes here... self.retro.as_ref().run(); self.canvas.present(); Duration::from_secs_f64(1.0 / self.av_info.timing.fps) .checked_sub(frame_begin.elapsed()) .map(std::thread::sleep); } } pub fn load_game(&self, rom: impl AsRef) { let mut data = None; let mut v = Vec::new(); if let Ok(mut f) = std::fs::File::open(rom.as_ref()) { if f.read_to_end(&mut v).is_ok() { data = Some(v.as_ref()); } } self.retro .as_ref() .load_game(Some(rom.as_ref()), data, None) .unwrap(); } 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); } } } impl retro::wrapper::Handler for MyEmulator { 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.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: Input, index: DeviceIndex) -> i16 { match self.gamepads.get(port as usize) { Some(gamepad) => { match device { Input::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, } } } Input::Analog(axis) => gamepad.axis(axis_map(index, axis)), _ => 0, } } None => 0, } } fn get_can_dupe(&mut self) -> Option { Some(true) } 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 set_system_av_info(&mut self, av_info: SystemAvInfo) -> bool { let (width, height) = (av_info.geometry.base_width, av_info.geometry.base_height); self.canvas.window_mut().set_size(width, height); self.canvas.set_logical_size(width, height); self.av_info = av_info; true } fn set_geometry(&mut self, geom: GameGeometry) -> bool { let (width, height) = (geom.base_width, geom.base_height); self.canvas.window_mut().set_size(width, height); self.canvas.set_logical_size(width, height); self.av_info.geometry = geom; 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()]); } } } } pub fn main() -> failure::Fallible<()> { let opt: Opt = Opt::from_args(); let mut emu = MyEmulator::with_core(&opt.core); emu.load_game(&opt.rom); emu.run(); Ok(()) } #[derive(StructOpt)] struct Opt { /// Core module to use. #[structopt(short, long, parse(from_os_str))] core: PathBuf, /// Rom to load using the core. #[structopt(short, long, parse(from_os_str))] rom: PathBuf, } fn button_map(retro_button: &JoypadButton) -> Option