emuladoor/src/main.rs
2021-11-24 01:20:29 -08:00

143 lines
4.6 KiB
Rust

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<PathBuf>,
}
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<dyn std::error::Error + Send + Sync>> {
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(())
}