Add experimental RetroArch Core
Former-commit-id: 5d6bd10c6e9214f5980eddf86ed92d35c0e9ea93 Former-commit-id: 2ad4828ecbb3de7924f6103b106c7ec06c7822cc
This commit is contained in:
parent
7646c2d6de
commit
be36345849
|
@ -2,6 +2,7 @@
|
|||
members = [
|
||||
"rustboyadvance-core/",
|
||||
"platform/rustboyadvance-sdl2",
|
||||
"platform/rustboyadvance-libretro",
|
||||
"platform/rustboyadvance-minifb",
|
||||
"platform/rustboyadvance-wasm",
|
||||
"bindings/rustboyadvance-jni",
|
||||
|
|
23
platform/rustboyadvance-libretro/Cargo.toml
Normal file
23
platform/rustboyadvance-libretro/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "rustboyadvance-libretro"
|
||||
version = "0.1.0"
|
||||
authors = ["Michel Heily <michelheily@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
rustboyadvance-core = { path = "../../rustboyadvance-core/" }
|
||||
log = "0.4.8"
|
||||
libc = "0.2"
|
||||
libretro-sys = "0.1.1"
|
||||
bit = "^0.1"
|
||||
unsafe_unwrap = "0.1.0"
|
||||
|
||||
[dependencies.libretro-backend]
|
||||
git = "https://github.com/michelhe/libretro-backend.git"
|
||||
branch = "rustboyadvance"
|
||||
features = ["logging"]
|
22
platform/rustboyadvance-libretro/README.md
Normal file
22
platform/rustboyadvance-libretro/README.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Experimental RustBoyAdvance-NG retroarch core
|
||||
|
||||
## Desktop Build
|
||||
|
||||
To build for your host system, run
|
||||
```sh
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
For Linux, the output is `repo/target/release/librustboyadvance_libretro.so`
|
||||
For windows, the output is `repo/target/release/rustboyadvance_libretro.dll`
|
||||
|
||||
## Android Build
|
||||
|
||||
Assuming you have NDK toolchain installed and configured, this crate can be built for android targets.
|
||||
|
||||
For example, for armv7-linux-androideabi
|
||||
```sh
|
||||
cargo build --release --target=armv7-linux-androideabi
|
||||
```
|
||||
|
||||
The output will be in `/repo/target/armv7-linux-androideabi/release/librustboyadvance_libretro.so`
|
192
platform/rustboyadvance-libretro/src/lib.rs
Normal file
192
platform/rustboyadvance-libretro/src/lib.rs
Normal file
|
@ -0,0 +1,192 @@
|
|||
#[macro_use]
|
||||
extern crate libretro_backend;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use libretro_backend::{
|
||||
AudioVideoInfo, CoreInfo, GameData, JoypadButton, LoadGameResult, PixelFormat, RuntimeHandle,
|
||||
};
|
||||
|
||||
use bit::BitIndex;
|
||||
use unsafe_unwrap::UnsafeUnwrap;
|
||||
|
||||
use rustboyadvance_core::keypad::Keys as GbaButton;
|
||||
use rustboyadvance_core::prelude::*;
|
||||
use rustboyadvance_core::util::audio::AudioRingBuffer;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
struct HwInterface {
|
||||
key_state: u16,
|
||||
audio_ring_buffer: AudioRingBuffer,
|
||||
}
|
||||
|
||||
impl HwInterface {
|
||||
fn set_button_state(&mut self, button: JoypadButton, is_pressed: bool) {
|
||||
let mapped_button = match button {
|
||||
JoypadButton::A => GbaButton::ButtonA,
|
||||
JoypadButton::B => GbaButton::ButtonB,
|
||||
JoypadButton::Start => GbaButton::Start,
|
||||
JoypadButton::Select => GbaButton::Select,
|
||||
JoypadButton::Left => GbaButton::Left,
|
||||
JoypadButton::Up => GbaButton::Up,
|
||||
JoypadButton::Right => GbaButton::Right,
|
||||
JoypadButton::Down => GbaButton::Down,
|
||||
JoypadButton::L1 => GbaButton::ButtonL,
|
||||
JoypadButton::R1 => GbaButton::ButtonR,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.key_state.set_bit(mapped_button as usize, !is_pressed);
|
||||
}
|
||||
}
|
||||
|
||||
// do nothing here, everything is handled in the libretro_backend::Core impl
|
||||
impl VideoInterface for HwInterface {}
|
||||
|
||||
impl AudioInterface for HwInterface {
|
||||
fn push_sample(&mut self, samples: StereoSample<i16>) {
|
||||
let prod = self.audio_ring_buffer.producer();
|
||||
prod.push(samples.0).unwrap();
|
||||
prod.push(samples.1).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl InputInterface for HwInterface {
|
||||
fn poll(&mut self) -> u16 {
|
||||
self.key_state
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RustBoyAdvanceCore {
|
||||
gba: Option<GameBoyAdvance>,
|
||||
game_data: Option<GameData>,
|
||||
hwif: Option<Rc<RefCell<HwInterface>>>,
|
||||
}
|
||||
|
||||
impl libretro_backend::Core for RustBoyAdvanceCore {
|
||||
fn info() -> CoreInfo {
|
||||
info!("Getting core info!");
|
||||
CoreInfo::new("RustBoyAdvance", env!("CARGO_PKG_VERSION"))
|
||||
.supports_roms_with_extension("gba")
|
||||
}
|
||||
|
||||
fn on_load_game(&mut self, game_data: GameData) -> LoadGameResult {
|
||||
debug!("on_load_game");
|
||||
|
||||
let system_directory = libretro_backend::environment::get_system_directory();
|
||||
if system_directory.is_none() {
|
||||
error!("no system directory!");
|
||||
return LoadGameResult::Failed(game_data);
|
||||
}
|
||||
let system_directory = system_directory.unwrap();
|
||||
let system_directory_path = Path::new(&system_directory);
|
||||
|
||||
let bios_path = system_directory_path.join("gba_bios.bin");
|
||||
if !bios_path.exists() {
|
||||
error!("bios file missing, please place it in {:?}", bios_path);
|
||||
return LoadGameResult::Failed(game_data);
|
||||
}
|
||||
let bios = read_bin_file(&bios_path);
|
||||
|
||||
if game_data.is_empty() {
|
||||
error!("game data is empty!");
|
||||
return LoadGameResult::Failed(game_data);
|
||||
}
|
||||
|
||||
let result = if let Some(data) = game_data.data() {
|
||||
GamepakBuilder::new()
|
||||
.buffer(data)
|
||||
.without_backup_to_file()
|
||||
.build()
|
||||
} else if let Some(path) = game_data.path() {
|
||||
GamepakBuilder::new().file(Path::new(&path)).build()
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
match (result, bios) {
|
||||
(Ok(gamepak), Ok(bios)) => {
|
||||
let av_info = AudioVideoInfo::new()
|
||||
.video(240, 160, 60.0, PixelFormat::ARGB8888)
|
||||
.audio(44100.0);
|
||||
|
||||
let hwif = Rc::new(RefCell::new(HwInterface {
|
||||
key_state: rustboyadvance_core::keypad::KEYINPUT_ALL_RELEASED,
|
||||
audio_ring_buffer: AudioRingBuffer::new(),
|
||||
}));
|
||||
let gba = GameBoyAdvance::new(
|
||||
bios.into_boxed_slice(),
|
||||
gamepak,
|
||||
hwif.clone(),
|
||||
hwif.clone(),
|
||||
hwif.clone(),
|
||||
);
|
||||
|
||||
self.hwif = Some(hwif);
|
||||
self.gba = Some(gba);
|
||||
self.game_data = Some(game_data);
|
||||
LoadGameResult::Success(av_info)
|
||||
}
|
||||
_ => LoadGameResult::Failed(game_data),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_run(&mut self, handle: &mut RuntimeHandle) {
|
||||
let joypad_port = 0;
|
||||
|
||||
// gba and hwif are `Some` after the game is loaded, so avoiding overhead of unwrap
|
||||
let gba = unsafe { self.gba.as_mut().unsafe_unwrap() };
|
||||
let hwif = unsafe { self.hwif.as_mut().unsafe_unwrap() };
|
||||
|
||||
macro_rules! update_controllers {
|
||||
( $( $button:ident ),+ ) => (
|
||||
$(
|
||||
hwif.borrow_mut().set_button_state( JoypadButton::$button, handle.is_joypad_button_pressed( joypad_port, JoypadButton::$button ) );
|
||||
)+
|
||||
)
|
||||
}
|
||||
|
||||
update_controllers!(A, B, Start, Select, Left, Up, Right, Down, L1, R1);
|
||||
|
||||
gba.frame();
|
||||
|
||||
let framebuffer = gba.get_frame_buffer();
|
||||
let bytes_per_pixel = 4;
|
||||
let framebuffer_size = 240 * 160;
|
||||
let uploaded_frame = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
framebuffer.as_ptr() as *const u8,
|
||||
framebuffer_size * bytes_per_pixel,
|
||||
)
|
||||
};
|
||||
handle.upload_video_frame(uploaded_frame);
|
||||
|
||||
// upload sound samples
|
||||
{
|
||||
let mut audio_samples = [0; 4096 * 2];
|
||||
let mut hwif = hwif.borrow_mut();
|
||||
let consumer = hwif.audio_ring_buffer.consumer();
|
||||
let count = consumer.pop_slice(&mut audio_samples);
|
||||
|
||||
handle.upload_audio_frame(&audio_samples[..count]);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_reset(&mut self) {
|
||||
debug!("on_reset");
|
||||
self.gba.as_mut().unwrap().soft_reset();
|
||||
}
|
||||
|
||||
fn on_unload_game(&mut self) -> GameData {
|
||||
debug!("on_unload_game");
|
||||
self.game_data.take().unwrap()
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
libretro_core!(RustBoyAdvanceCore);
|
|
@ -34,18 +34,17 @@ struct SaveState {
|
|||
cpu: arm7tdmi::Core,
|
||||
}
|
||||
|
||||
/// Checks if the bios provided is the real one,
|
||||
/// Otherwise output a log warning to the user
|
||||
fn check_real_bios(bios: &[u8]) {
|
||||
/// Checks if the bios provided is the real one
|
||||
fn check_real_bios(bios: &[u8]) -> bool {
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input(bios);
|
||||
let digest = hasher.result();
|
||||
|
||||
let expected_hash = hex!("fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570");
|
||||
if digest.as_slice() != &expected_hash[..] {
|
||||
warn!("This is not the real bios, some games may not be compatible");
|
||||
}
|
||||
|
||||
digest.as_slice() == &expected_hash[..]
|
||||
}
|
||||
|
||||
impl GameBoyAdvance {
|
||||
|
@ -57,7 +56,10 @@ impl GameBoyAdvance {
|
|||
input_device: Rc<RefCell<dyn InputInterface>>,
|
||||
) -> GameBoyAdvance {
|
||||
// Warn the user if the bios is not the real one
|
||||
check_real_bios(&bios_rom);
|
||||
match check_real_bios(&bios_rom) {
|
||||
true => info!("Verified bios rom"),
|
||||
false => warn!("This is not the real bios rom, some games may not be compatible"),
|
||||
};
|
||||
let gpu = Box::new(Gpu::new());
|
||||
let sound_controller = Box::new(SoundController::new(
|
||||
audio_device.borrow().get_sample_rate() as f32,
|
||||
|
@ -251,6 +253,11 @@ impl GameBoyAdvance {
|
|||
pub fn get_frame_buffer(&self) -> &[u32] {
|
||||
self.sysbus.io.gpu.get_frame_buffer()
|
||||
}
|
||||
|
||||
/// Reset the emulator
|
||||
pub fn soft_reset(&mut self) {
|
||||
self.cpu.reset(&mut self.sysbus);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -143,5 +143,13 @@ pub mod audio {
|
|||
|
||||
AudioRingBuffer { prod, cons }
|
||||
}
|
||||
|
||||
pub fn producer(&mut self) -> &mut Producer<i16> {
|
||||
&mut self.prod
|
||||
}
|
||||
|
||||
pub fn consumer(&mut self) -> &mut Consumer<i16> {
|
||||
&mut self.cons
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue