132 lines
4.0 KiB
Rust
132 lines
4.0 KiB
Rust
extern crate ferretro_components;
|
|
extern crate sdl2;
|
|
|
|
use std::cell::RefCell;
|
|
use std::ffi::CStr;
|
|
use std::path::PathBuf;
|
|
use std::pin::Pin;
|
|
use std::rc::Rc;
|
|
|
|
use sdl2::render::{WindowCanvas, Texture, TextureCreator};
|
|
use sdl2::image::ImageRWops;
|
|
use sdl2::surface::Surface;
|
|
use sdl2::video::WindowContext;
|
|
|
|
use structopt::StructOpt;
|
|
|
|
use ferretro_components::base::{ControlFlow, RetroComponentBase};
|
|
use ferretro_components::prelude::LibretroWrapperAccess;
|
|
use ferretro_components::provided::{
|
|
sdl2::{Sdl2SurfaceComponent, SimpleSdl2AudioComponent},
|
|
stdlib::{SleepFramerateLimitComponent, PathBufComponent},
|
|
};
|
|
|
|
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
|
|
|
#[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,
|
|
/// System directory, often containing BIOS files
|
|
#[structopt(short, long, parse(from_os_str))]
|
|
system: Option<PathBuf>,
|
|
}
|
|
|
|
struct Zretro {
|
|
emu: Pin<Box<RetroComponentBase>>,
|
|
canvas: WindowCanvas,
|
|
font: Surface<'static>,
|
|
sdl2surf_comp: Rc<RefCell<Sdl2SurfaceComponent>>,
|
|
}
|
|
|
|
const FONT_PNG: &'static [u8] = include_bytes!("lifont.png");
|
|
|
|
impl Zretro {
|
|
fn new(opt: Opt) -> Result<Self> {
|
|
let mut emu = RetroComponentBase::new(&opt.core);
|
|
|
|
let geometry = emu.libretro_core().get_system_av_info().geometry;
|
|
let sys_info = emu.libretro_core().get_system_info();
|
|
let title = format!(
|
|
"zretro - {}",
|
|
unsafe { CStr::from_ptr(sys_info.library_name) }.to_string_lossy()
|
|
);
|
|
|
|
let mut sdl_context = sdl2::init()?;
|
|
|
|
let window = sdl_context
|
|
.video()?
|
|
.window(title.as_str(), geometry.base_width, geometry.base_height)
|
|
.borderless()
|
|
.build()?;
|
|
let mut canvas = window.into_canvas().build()?;
|
|
|
|
let sdl2surf_comp = Rc::new(RefCell::new(
|
|
Sdl2SurfaceComponent::new(emu.libretro_core())?
|
|
));
|
|
emu.register_component(sdl2surf_comp.clone());
|
|
|
|
let sdl2_audio = SimpleSdl2AudioComponent::new(&mut sdl_context, emu.libretro_core());
|
|
emu.register_component(sdl2_audio);
|
|
|
|
let sleep_fps = SleepFramerateLimitComponent::new(emu.libretro_core());
|
|
emu.register_component(sleep_fps);
|
|
|
|
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()),
|
|
});
|
|
|
|
emu.init().unwrap();
|
|
emu.load_game(&opt.rom).unwrap();
|
|
|
|
let rwops = sdl2::rwops::RWops::from_bytes(FONT_PNG)?;
|
|
let font_shortlived = rwops.load_png()?;
|
|
// there's a nonsemantic inheritance of lifetime from ImageRWops::load_png. TODO: pr
|
|
let font = unsafe {
|
|
let ptr = font_shortlived.raw();
|
|
std::mem::forget(font_shortlived);
|
|
Surface::from_ll(ptr)
|
|
};
|
|
|
|
Ok(Zretro {
|
|
emu,
|
|
canvas,
|
|
font,
|
|
sdl2surf_comp,
|
|
})
|
|
}
|
|
|
|
fn run(&mut self) -> Result<()> {
|
|
let tc = self.canvas.texture_creator();
|
|
let font_tx = tc.create_texture_from_surface(&self.font)?;
|
|
let mut font_rect = self.font.rect();
|
|
font_rect.set_height(64);
|
|
while let ControlFlow::Continue = self.emu.run() {
|
|
let ref_mut = RefCell::try_borrow_mut(&self.sdl2surf_comp)?;
|
|
let mut surface = ref_mut.surface();
|
|
|
|
let texture = tc.create_texture_from_surface(surface)?;
|
|
|
|
self.canvas.clear();
|
|
self.canvas.copy(&texture, None, None)?;
|
|
self.canvas.copy(&font_tx, font_rect, font_rect)?;
|
|
self.canvas.present();
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
let opt: Opt = Opt::from_args();
|
|
let mut frontend = Zretro::new(opt)?;
|
|
frontend.run()?;
|
|
Ok(())
|
|
}
|