This repository has been archived on 2024-06-01. You can view files and clone it, but cannot push or open issues or pull requests.
rustboyadvance-ng/platform/rustboyadvance-sdl2/src/main.rs
Michel Heily a56f515563 sdl2 UI improvements, Auto download Normmatt BIOS
Former-commit-id: 9a8bb7f68ba7638e758b01cef17654b6057b1db3
Former-commit-id: 83dc923063d75a167662474446f9fa7eb0ee836b
2020-05-01 17:56:26 +03:00

370 lines
11 KiB
Rust

use sdl2;
use sdl2::event::Event;
use sdl2::image::{InitFlag, LoadTexture};
use sdl2::keyboard::Keycode;
use sdl2::messagebox::*;
use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::{Texture, WindowCanvas};
use sdl2::EventPump;
use bytesize;
use spin_sleep;
use std::cell::RefCell;
use std::rc::Rc;
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process;
use std::time;
use std::convert::TryFrom;
#[macro_use]
extern crate clap;
#[macro_use]
extern crate log;
use flexi_logger;
use flexi_logger::*;
use reqwest;
mod audio;
mod input;
mod video;
use audio::create_audio_player;
use input::create_input;
use video::{create_video_interface, SCREEN_HEIGHT, SCREEN_WIDTH};
use rustboyadvance_core::core::cartridge::BackupType;
use rustboyadvance_core::prelude::*;
use rustboyadvance_core::util::spawn_and_run_gdb_server;
use rustboyadvance_core::util::FpsCounter;
const LOG_DIR: &str = ".logs";
const DEFAULT_GDB_SERVER_ADDR: &'static str = "localhost:1337";
const CANVAS_WIDTH: u32 = SCREEN_WIDTH;
const CANVAS_HEIGHT: u32 = SCREEN_HEIGHT;
fn get_savestate_path(rom_filename: &Path) -> PathBuf {
rom_filename.with_extension("savestate")
}
/// Waits for the user to drag a rom file to window
fn wait_for_rom(canvas: &mut WindowCanvas, event_pump: &mut EventPump) -> Result<String, String> {
let texture_creator = canvas.texture_creator();
let icon_texture = texture_creator
.load_texture("assets/icon_cropped_small.png")
.expect("failed to load icon");
let background = Color::RGB(0xDD, 0xDD, 0xDD);
loop {
for event in event_pump.poll_iter() {
match event {
Event::DropFile { filename, .. } => {
return Ok(filename);
}
Event::Quit { .. } => process::exit(0),
_ => {}
}
}
canvas.set_draw_color(background);
canvas.clear();
canvas.copy(
&icon_texture,
None,
Some(Rect::from_center(
((CANVAS_WIDTH / 2) as i32, (CANVAS_HEIGHT / 2) as i32),
160,
100,
)),
)?;
canvas.present();
}
}
fn ask_download_bios() -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> {
const OPEN_SOURCE_BIOS_URL: &'static str =
"https://github.com/Nebuleon/ReGBA/raw/master/bios/gba_bios.bin";
let buttons: Vec<_> = vec![
ButtonData {
flags: MessageBoxButtonFlag::NOTHING,
button_id: 100,
text: "No, Exit!",
},
ButtonData {
flags: MessageBoxButtonFlag::NOTHING,
button_id: 101,
text: "Yes",
},
];
let res = show_message_box(
MessageBoxFlag::WARNING,
buttons.as_slice(),
"bios not found",
"would you like to me download an open source bios for you?",
None,
None,
)?;
match res {
ClickedButton::CloseButton => Ok(None),
ClickedButton::CustomButton(button) => match button.button_id {
101 => {
let mut resp = reqwest::blocking::get(OPEN_SOURCE_BIOS_URL)?;
let mut bios = Vec::with_capacity(16 * 1024);
resp.read_to_end(&mut bios)?;
show_simple_message_box(
MessageBoxFlag::INFORMATION,
"Download Finished!",
"Download Finished!",
None,
)?;
Ok(Some(bios))
}
_ => Ok(None),
},
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
fs::create_dir_all(LOG_DIR).expect(&format!("could not create log directory ({})", LOG_DIR));
flexi_logger::Logger::with_env_or_str("info")
.log_to_file()
.directory(LOG_DIR)
.duplicate_to_stderr(Duplicate::Debug)
.format_for_files(default_format)
.format_for_stderr(colored_default_format)
.start()
.unwrap();
let mut frame_limiter = true;
let yaml = load_yaml!("cli.yml");
let matches = clap::App::from_yaml(yaml).get_matches();
let skip_bios = matches.occurrences_of("skip_bios") != 0;
let debug = matches.occurrences_of("debug") != 0;
let with_gdbserver = matches.occurrences_of("with_gdbserver") != 0;
info!("Initializing SDL2 context");
let sdl_context = sdl2::init().expect("failed to initialize sdl2");
let bios_path = Path::new(matches.value_of("bios").unwrap_or_default());
let bios_bin = match read_bin_file(bios_path) {
Ok(bios) => bios,
_ => match ask_download_bios() {
Ok(Some(bios)) => {
info!("saving downloaded bios to {}", bios_path.display());
write_bin_file(bios_path, &bios)?;
bios
}
Ok(None) => {
info!("Exiting");
std::process::exit(0);
}
Err(e) => {
error!("error when downloading bios: {}", e);
std::process::exit(1);
}
},
};
let mut event_pump = sdl_context.event_pump()?;
let video_subsystem = sdl_context.video()?;
let _image_context = sdl2::image::init(InitFlag::PNG | InitFlag::JPG)?;
let window = video_subsystem
.window("RustBoyAdvance", SCREEN_WIDTH * 3, SCREEN_HEIGHT * 3)
.opengl()
.position_centered()
.resizable()
.build()?;
let mut canvas = window.into_canvas().accelerated().build()?;
canvas.set_logical_size(CANVAS_WIDTH, CANVAS_HEIGHT)?;
let mut rom_path = match matches.value_of("game_rom") {
Some(path) => path.to_string(),
_ => {
info!("[!] Rom file missing, please drag a rom file into the emulator window...");
wait_for_rom(&mut canvas, &mut event_pump)?
}
};
let video = Rc::new(RefCell::new(create_video_interface(canvas)));
let audio = Rc::new(RefCell::new(create_audio_player(&sdl_context)));
let input = Rc::new(RefCell::new(create_input()));
let mut savestate_path = get_savestate_path(&Path::new(&rom_path));
let mut rom_name = Path::new(&rom_path).file_name().unwrap().to_str().unwrap();
let gamepak = GamepakBuilder::new()
.save_type(BackupType::try_from(
matches.value_of("save_type").unwrap(),
)?)
.file(Path::new(&rom_path))
.build()?;
let mut gba = GameBoyAdvance::new(
bios_bin.into_boxed_slice(),
gamepak,
video.clone(),
audio.clone(),
input.clone(),
);
if skip_bios {
gba.skip_bios();
}
if debug {
#[cfg(feature = "debugger")]
{
gba.cpu.set_verbose(true);
let mut debugger = Debugger::new(gba);
info!("starting debugger...");
debugger.repl(matches.value_of("script_file")).unwrap();
info!("ending debugger...");
return Ok(());
}
#[cfg(not(feature = "debugger"))]
{
panic!("Please compile me with 'debugger' feature");
}
}
if with_gdbserver {
spawn_and_run_gdb_server(&mut gba, DEFAULT_GDB_SERVER_ADDR)?;
}
let mut fps_counter = FpsCounter::default();
let frame_time = time::Duration::new(0, 1_000_000_000u32 / 60);
'running: loop {
let start_time = time::Instant::now();
for event in event_pump.poll_iter() {
match event {
Event::KeyDown {
keycode: Some(Keycode::Space),
..
} => {
frame_limiter = false;
}
Event::KeyUp {
keycode: Some(Keycode::Space),
..
} => {
frame_limiter = true;
}
#[cfg(feature = "debugger")]
Event::KeyUp {
keycode: Some(Keycode::F1),
..
} => {
let mut debugger = Debugger::new(gba);
info!("starting debugger...");
debugger.repl(matches.value_of("script_file")).unwrap();
gba = debugger.gba;
info!("ending debugger...");
break;
}
#[cfg(feature = "gdb")]
Event::KeyUp {
keycode: Some(Keycode::F2),
..
} => {
spawn_and_run_gdb_server(&mut gba, DEFAULT_GDB_SERVER_ADDR)?;
}
Event::KeyUp {
keycode: Some(Keycode::F5),
..
} => {
info!("Saving state ...");
let save = gba.save_state()?;
write_bin_file(&savestate_path, &save)?;
info!(
"Saved to {:?} ({})",
savestate_path,
bytesize::ByteSize::b(save.len() as u64)
);
}
Event::KeyUp {
keycode: Some(Keycode::F9),
..
} => {
if savestate_path.is_file() {
let save = read_bin_file(&savestate_path)?;
info!("Restoring state from {:?}...", savestate_path);
gba.restore_state(&save)?;
info!("Restored!");
} else {
info!("Savestate not created, please create one by pressing F5");
}
}
Event::KeyDown {
keycode: Some(keycode),
..
} => {
input.borrow_mut().on_keyboard_key_down(keycode);
}
Event::KeyUp {
keycode: Some(keycode),
..
} => {
input.borrow_mut().on_keyboard_key_up(keycode);
}
Event::Quit { .. } => break 'running,
Event::DropFile { filename, .. } => {
// load the new rom
rom_path = filename;
savestate_path = get_savestate_path(&Path::new(&rom_path));
rom_name = Path::new(&rom_path).file_name().unwrap().to_str().unwrap();
let gamepak = GamepakBuilder::new().file(Path::new(&rom_path)).build()?;
let bios_bin = read_bin_file(bios_path).unwrap();
// create a new emulator - TODO, export to a function
gba = GameBoyAdvance::new(
bios_bin.into_boxed_slice(),
gamepak,
video.clone(),
audio.clone(),
input.clone(),
);
gba.skip_bios();
}
_ => {}
}
}
gba.frame();
if let Some(fps) = fps_counter.tick() {
let title = format!("{} ({} fps)", rom_name, fps);
video.borrow_mut().set_window_title(&title);
}
if frame_limiter {
let time_passed = start_time.elapsed();
let delay = frame_time.checked_sub(time_passed);
match delay {
None => {}
Some(delay) => {
spin_sleep::sleep(delay);
}
};
}
}
Ok(())
}