initial commit
This commit is contained in:
commit
0a173d12ef
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
.idea
|
1319
Cargo.lock
generated
Normal file
1319
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
32
Cargo.toml
Normal file
32
Cargo.toml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
[package]
|
||||||
|
name = "emuladoor"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["lifning"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[patch.'https://cybre.tech/lifning/ferretro.git']
|
||||||
|
ferretro_base = { path = "../ferretro/ferretro_base" }
|
||||||
|
ferretro_components = { path = "../ferretro/ferretro_components" }
|
||||||
|
|
||||||
|
[dependencies.ferretro_components]
|
||||||
|
git = "https://cybre.tech/lifning/ferretro.git"
|
||||||
|
branch = "liffy/sdl2surf"
|
||||||
|
|
||||||
|
[dependencies.anime_telnet]
|
||||||
|
git = "https://github.com/alisww/anime-over-ansi"
|
||||||
|
|
||||||
|
[dependencies.tokio]
|
||||||
|
version = "1"
|
||||||
|
features = ["sync"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
structopt = "0.3"
|
||||||
|
terminal_size = "0.1"
|
||||||
|
sdl2 = "0.35"
|
||||||
|
|
||||||
|
# must match the versions in anime_telnet:
|
||||||
|
image = "0.23"
|
||||||
|
fast_image_resize = "0.4.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
static = ["ferretro_components/static"]
|
141
src/main.rs
Normal file
141
src/main.rs
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
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");
|
||||||
|
emu.register_component(vars_comp)?;
|
||||||
|
emu.register_component(SleepFramerateLimitComponent::default())?;
|
||||||
|
|
||||||
|
emu.init()?;
|
||||||
|
emu.load_game(&opt.rom)?;
|
||||||
|
|
||||||
|
while let ControlFlow::Continue = emu.run() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue