use std::path::PathBuf; use structopt::StructOpt; use ferretro_components::prelude::*; use ferretro_components::provided::stdlib::*; use anime_telnet::encoding::Encoder as AnsiArtEncoder; use anime_telnet::encoding::ProcessorPipeline; use anime_telnet::metadata::ColorMode; use fast_image_resize::FilterType; use image::RgbaImage; use sdl2::pixels::PixelFormatEnum; use sdl2::surface::Surface; use ferretro_components::base::ControlFlow; #[derive(StructOpt)] struct Opt { /// Emulator core to use. #[structopt(short, long, parse(from_os_str))] core: PathBuf, /// Path to ROM. #[structopt(parse(from_os_str))] rom: PathBuf, /// Directory containing BIOS files #[structopt(short, long, parse(from_os_str))] system: Option, } struct RetroFrameEncoder { terminal_width: u32, terminal_height: u32, color_mode: ColorMode, } impl Default for RetroFrameEncoder { fn default() -> Self { use terminal_size::*; let (Width(w), Height(h)) = terminal_size() .unwrap_or((Width(80), Height(24))); RetroFrameEncoder { terminal_width: w as u32, terminal_height: h as u32, color_mode: ColorMode::EightBit, } } } impl AnsiArtEncoder for RetroFrameEncoder { fn needs_width(&self) -> u32 { self.terminal_width } fn needs_height(&self) -> u32 { self.terminal_height * 2 // half-blocks? } fn needs_color(&self) -> ColorMode { self.color_mode } } struct AnsiVideoComponent { processor: ProcessorPipeline, encoder: RetroFrameEncoder, } impl Default for AnsiVideoComponent { fn default() -> Self { let encoder = RetroFrameEncoder::default(); let processor = ProcessorPipeline { filter: FilterType::Hamming, width: encoder.needs_width(), height: encoder.needs_height(), color_modes: Some(encoder.needs_color()).into_iter().collect(), }; AnsiVideoComponent { processor, encoder, } } } impl RetroCallbacks for AnsiVideoComponent { fn video_refresh(&mut self, frame: &VideoFrame) { match frame { VideoFrame::XRGB1555 { width, height, .. } | VideoFrame::RGB565 { width, height, .. } | VideoFrame::XRGB8888 { width, height, .. } => { // dirty, but must be &mut for SDL API. // safe as long as we don't leak the Surface we construct here. let (bytes, pitch) = frame.data_pitch_as_bytes().unwrap(); let pitch = pitch as u32; let format = match frame.pixel_format().unwrap() { PixelFormat::ARGB1555 => sdl2::pixels::PixelFormatEnum::RGB555, PixelFormat::ARGB8888 => sdl2::pixels::PixelFormatEnum::ARGB8888, PixelFormat::RGB565 => sdl2::pixels::PixelFormatEnum::RGB565, }; let data = unsafe { core::slice::from_raw_parts_mut(bytes.as_ptr() as *mut u8, bytes.len()) }; if let Ok(surf) = Surface::from_data(data, *width, *height, pitch, format) { let rgba_raw = surf.into_canvas().unwrap().read_pixels(None, PixelFormatEnum::ABGR8888).unwrap(); let rgba_img = RgbaImage::from_raw(*width, *height, rgba_raw).unwrap(); let processed = self.processor.process(&rgba_img).into_iter().next().unwrap().1; println!("\x1B[0m\x1B[0J{}", self.encoder.encode_frame(&processed)); } } _ => {} } } } impl RetroComponent for AnsiVideoComponent {} fn main() -> Result<(), Box> { let opt: Opt = Opt::from_args(); let mut emu = RetroComponentBase::new(&opt.core); emu.register_component(AnsiVideoComponent::default())?; emu.register_component(StatefulInputComponent::default())?; emu.register_component(PathBufComponent { sys_path: opt.system.clone(), libretro_path: Some(opt.core.to_path_buf()), core_assets_path: None, save_path: Some(std::env::temp_dir()), })?; let mut vars_comp = VariableStoreComponent::default(); vars_comp.insert("mgba_skip_bios", "ON"); vars_comp.insert("mgba_sgb_borders", "OFF"); vars_comp.insert("parallel-n64-gfxplugin", "angrylion"); emu.register_component(vars_comp)?; emu.register_component(SleepFramerateLimitComponent::default())?; emu.init()?; emu.load_game(&opt.rom)?; while let ControlFlow::Continue = emu.run() { } Ok(()) }