use crate::buttons::Buttons;
use crate::error::*;
use crate::pixels::*;
use libc::c_char;
use libloading::Library;
use libloading::Symbol;
use libretro_sys::*;
use std::ffi::{c_void, CStr, CString};
use std::fs::File;
use std::io::Read;
use std::marker::PhantomData;
use std::panic;
use std::path::{Path, PathBuf};
use std::ptr;
type NotSendSync = *const [u8; 0];
static mut EMULATOR: *mut EmulatorCore = ptr::null_mut();
static mut CONTEXT: *mut EmulatorContext = ptr::null_mut();
struct EmulatorCore {
core_lib: Box<Library>,
core_path: CString,
rom_path: CString,
core: CoreAPI,
_marker: PhantomData<NotSendSync>,
}
struct EmulatorContext {
audio_sample: Vec<i16>,
buttons: [Buttons; 2],
frame_ptr: *const c_void,
frame_pitch: usize,
frame_width: u32,
frame_height: u32,
pixfmt: PixelFormat,
image_depth: usize,
memory_map: Vec<MemoryDescriptor>,
_marker: PhantomData<NotSendSync>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MemoryRegion {
which: usize,
pub flags: u64,
pub len: usize,
pub start: usize,
pub offset: usize,
pub name: String,
pub select: usize,
pub disconnect: usize,
}
pub struct Emulator {
phantom: PhantomData<NotSendSync>,
}
impl Emulator {
pub fn create(core_path: &Path, rom_path: &Path) -> Emulator {
unsafe {
assert!(EMULATOR.is_null());
assert!(CONTEXT.is_null());
}
let suffix = if cfg!(target_os = "windows") {
"dll"
} else if cfg!(target_os = "macos") {
"dylib"
} else if cfg!(target_os = "linux") {
"so"
} else {
panic!("Unsupported platform")
};
let path: PathBuf = core_path.with_extension(suffix);
#[cfg(target_os = "linux")]
let library: Library = {
::libloading::os::unix::Library::open(Some(path), 0x2 | 0x1000)
.unwrap()
.into()
};
#[cfg(not(target_os = "linux"))]
let library = Library::new(path).unwrap();
let dll = Box::new(library);
unsafe {
let retro_set_environment = *(dll.get(b"retro_set_environment").unwrap());
let retro_set_video_refresh = *(dll.get(b"retro_set_video_refresh").unwrap());
let retro_set_audio_sample = *(dll.get(b"retro_set_audio_sample").unwrap());
let retro_set_audio_sample_batch = *(dll.get(b"retro_set_audio_sample_batch").unwrap());
let retro_set_input_poll = *(dll.get(b"retro_set_input_poll").unwrap());
let retro_set_input_state = *(dll.get(b"retro_set_input_state").unwrap());
let retro_init = *(dll.get(b"retro_init").unwrap());
let retro_deinit = *(dll.get(b"retro_deinit").unwrap());
let retro_api_version = *(dll.get(b"retro_api_version").unwrap());
let retro_get_system_info = *(dll.get(b"retro_get_system_info").unwrap());
let retro_get_system_av_info = *(dll.get(b"retro_get_system_av_info").unwrap());
let retro_set_controller_port_device =
*(dll.get(b"retro_set_controller_port_device").unwrap());
let retro_reset = *(dll.get(b"retro_reset").unwrap());
let retro_run = *(dll.get(b"retro_run").unwrap());
let retro_serialize_size = *(dll.get(b"retro_serialize_size").unwrap());
let retro_serialize = *(dll.get(b"retro_serialize").unwrap());
let retro_unserialize = *(dll.get(b"retro_unserialize").unwrap());
let retro_cheat_reset = *(dll.get(b"retro_cheat_reset").unwrap());
let retro_cheat_set = *(dll.get(b"retro_cheat_set").unwrap());
let retro_load_game = *(dll.get(b"retro_load_game").unwrap());
let retro_load_game_special = *(dll.get(b"retro_load_game_special").unwrap());
let retro_unload_game = *(dll.get(b"retro_unload_game").unwrap());
let retro_get_region = *(dll.get(b"retro_get_region").unwrap());
let retro_get_memory_data = *(dll.get(b"retro_get_memory_data").unwrap());
let retro_get_memory_size = *(dll.get(b"retro_get_memory_size").unwrap());
let emu = EmulatorCore {
core_lib: dll,
rom_path: CString::new(rom_path.to_str().unwrap()).unwrap(),
core_path: CString::new(core_path.to_str().unwrap()).unwrap(),
core: CoreAPI {
retro_set_environment,
retro_set_video_refresh,
retro_set_audio_sample,
retro_set_audio_sample_batch,
retro_set_input_poll,
retro_set_input_state,
retro_init,
retro_deinit,
retro_api_version,
retro_get_system_info,
retro_get_system_av_info,
retro_set_controller_port_device,
retro_reset,
retro_run,
retro_serialize_size,
retro_serialize,
retro_unserialize,
retro_cheat_reset,
retro_cheat_set,
retro_load_game,
retro_load_game_special,
retro_unload_game,
retro_get_region,
retro_get_memory_data,
retro_get_memory_size,
},
_marker: PhantomData,
};
let emup = Box::new(emu);
EMULATOR = Box::leak(emup);
let ctx = EmulatorContext {
audio_sample: Vec::new(),
buttons: [Buttons::new(), Buttons::new()],
frame_ptr: ptr::null(),
frame_pitch: 0,
frame_width: 0,
frame_height: 0,
pixfmt: PixelFormat::ARGB1555,
image_depth: 0,
memory_map: Vec::new(),
_marker: PhantomData,
};
let ctxp = Box::new(ctx);
CONTEXT = Box::leak(ctxp);
let emu = &(*EMULATOR);
(emu.core.retro_set_environment)(callback_environment);
(emu.core.retro_set_video_refresh)(callback_video_refresh);
(emu.core.retro_set_audio_sample)(callback_audio_sample);
(emu.core.retro_set_audio_sample_batch)(callback_audio_sample_batch);
(emu.core.retro_set_input_poll)(callback_input_poll);
(emu.core.retro_set_input_state)(callback_input_state);
(emu.core.retro_init)();
let mut sys_info = SystemInfo {
library_name: ptr::null(),
library_version: ptr::null(),
valid_extensions: ptr::null(),
need_fullpath: false,
block_extract: false,
};
retro_get_system_info(&mut sys_info);
let rom_cstr = &(*EMULATOR).rom_path;
let mut rom_file = File::open(rom_path).unwrap();
let mut buffer = Vec::new();
rom_file.read_to_end(&mut buffer).unwrap();
buffer.shrink_to_fit();
let game_info = GameInfo {
path: rom_cstr.as_ptr(),
data: buffer.as_ptr() as *const c_void,
size: buffer.len(),
meta: ptr::null(),
};
(emu.core.retro_load_game)(&game_info);
let mut av_info = SystemAvInfo {
geometry: GameGeometry {
base_width: 0,
base_height: 0,
max_width: 0,
max_height: 0,
aspect_ratio: 0.0,
},
timing: SystemTiming {
fps: 0.0,
sample_rate: 0.0,
},
};
(retro_get_system_av_info)(&mut av_info);
Emulator {
phantom: PhantomData,
}
}
}
pub fn get_library(&mut self) -> &Library {
unsafe { &(*EMULATOR).core_lib }
}
pub fn get_symbol<'a, T>(&'a self, symbol: &[u8]) -> Option<Symbol<'a, T>> {
let dll = unsafe { &(*EMULATOR).core_lib };
let sym: Result<Symbol<T>, _> = unsafe { dll.get(symbol) };
if sym.is_err() {
return None;
}
Some(sym.unwrap())
}
pub fn run(&mut self, inputs: [Buttons; 2]) {
unsafe {
(*CONTEXT).audio_sample.clear();
(*CONTEXT).buttons = inputs;
((*EMULATOR).core.retro_run)()
}
}
pub fn reset(&mut self) {
unsafe {
(*CONTEXT).audio_sample.clear();
(*CONTEXT).buttons = [Buttons::new(), Buttons::new()];
(*CONTEXT).frame_ptr = ptr::null();
((*EMULATOR).core.retro_reset)()
}
}
fn get_ram_size(&self, rtype: libc::c_uint) -> usize {
unsafe { ((*EMULATOR).core.retro_get_memory_size)(rtype) as usize }
}
pub fn get_video_ram_size(&self) -> usize {
self.get_ram_size(MEMORY_VIDEO_RAM)
}
pub fn get_system_ram_size(&self) -> usize {
self.get_ram_size(MEMORY_SYSTEM_RAM)
}
pub fn get_save_ram_size(&self) -> usize {
self.get_ram_size(MEMORY_SAVE_RAM)
}
pub fn video_ram_ref(&self) -> &[u8] {
self.get_ram(MEMORY_VIDEO_RAM)
}
pub fn system_ram_ref(&self) -> &[u8] {
self.get_ram(MEMORY_SYSTEM_RAM)
}
pub fn system_ram_mut(&mut self) -> &mut [u8] {
self.get_ram_mut(MEMORY_SYSTEM_RAM)
}
pub fn save_ram(&self) -> &[u8] {
self.get_ram(MEMORY_SAVE_RAM)
}
fn get_ram(&self, ramtype: libc::c_uint) -> &[u8] {
let len = self.get_ram_size(ramtype);
unsafe {
let ptr: *const u8 = ((*EMULATOR).core.retro_get_memory_data)(ramtype).cast();
std::slice::from_raw_parts(ptr, len)
}
}
fn get_ram_mut(&mut self, ramtype: libc::c_uint) -> &mut [u8] {
let len = self.get_ram_size(ramtype);
unsafe {
let ptr: *mut u8 = ((*EMULATOR).core.retro_get_memory_data)(ramtype).cast();
std::slice::from_raw_parts_mut(ptr, len)
}
}
pub fn memory_regions(&self) -> Vec<MemoryRegion> {
let map = unsafe { &((*CONTEXT).memory_map) };
map.iter()
.enumerate()
.map(|(i, mdesc)| MemoryRegion {
which: i,
flags: mdesc.flags,
len: mdesc.len,
start: mdesc.start,
offset: mdesc.offset,
select: mdesc.select,
disconnect: mdesc.disconnect,
name: if mdesc.addrspace.is_null() {
"".to_owned()
} else {
unsafe { CStr::from_ptr(mdesc.addrspace) }
.to_string_lossy()
.into_owned()
},
})
.collect()
}
pub fn memory_ref(&self, start: usize) -> Result<&[u8], RetroRsError> {
for mr in self.memory_regions() {
if mr.select != 0 && (start & mr.select) == 0 {
continue;
}
if start >= mr.start && start < mr.start + mr.len {
return self.memory_ref_mut(mr, start).map(|slice| &*slice);
}
}
Err(RetroRsError::RAMCopyNotMappedIntoMemoryRegionError)
}
pub fn memory_ref_mut(
&self,
mr: MemoryRegion,
start: usize,
) -> Result<&mut [u8], RetroRsError> {
let maps = unsafe { &(*CONTEXT).memory_map };
if mr.which >= maps.len() {
return Err(RetroRsError::RAMMapOutOfRangeError);
}
if start < mr.start {
return Err(RetroRsError::RAMCopySrcOutOfBoundsError);
}
let start = (start - mr.start) & !mr.disconnect;
let map = &maps[mr.which];
let ptr: *mut u8 = map.ptr.cast();
let slice = unsafe {
let ptr = ptr.add(start).add(map.offset);
std::slice::from_raw_parts_mut(ptr, map.len - start)
};
Ok(slice)
}
pub fn pixel_format(&self) -> PixelFormat {
unsafe { (*CONTEXT).pixfmt }
}
pub fn framebuffer_size(&self) -> (usize, usize) {
unsafe {
(
(*CONTEXT).frame_width as usize,
(*CONTEXT).frame_height as usize,
)
}
}
pub fn framebuffer_pitch(&self) -> usize {
unsafe { (*CONTEXT).frame_pitch }
}
fn peek_framebuffer<FBPeek, FBPeekRet>(&self, f: FBPeek) -> Result<FBPeekRet, RetroRsError>
where
FBPeek: FnOnce(&[u8]) -> FBPeekRet,
{
unsafe {
if (*CONTEXT).frame_ptr.is_null() {
Err(RetroRsError::NoFramebufferError)
} else {
let frame_slice = std::slice::from_raw_parts(
(*CONTEXT).frame_ptr as *const u8,
((*CONTEXT).frame_height * ((*CONTEXT).frame_pitch as u32)) as usize,
);
Ok(f(frame_slice))
}
}
}
pub fn save(&self, bytes: &mut [u8]) {
let size = self.save_size();
assert!(bytes.len() >= size);
unsafe { ((*EMULATOR).core.retro_serialize)(bytes.as_mut_ptr() as *mut c_void, size) }
}
pub fn load(&mut self, bytes: &[u8]) -> bool {
let size = self.save_size();
assert!(bytes.len() >= size);
unsafe { ((*EMULATOR).core.retro_unserialize)(bytes.as_ptr() as *const c_void, size) }
}
pub fn save_size(&self) -> usize {
unsafe { ((*EMULATOR).core.retro_serialize_size)() }
}
pub fn clear_cheats(&mut self) {
unsafe { ((*EMULATOR).core.retro_cheat_reset)() }
}
pub fn set_cheat(&mut self, index: usize, enabled: bool, code: &str) {
unsafe {
((*EMULATOR).core.retro_cheat_set)(
index as u32,
enabled,
CString::new(code).unwrap().into_raw(),
)
}
}
pub fn get_pixel(&self, x: usize, y: usize) -> Result<(u8, u8, u8), RetroRsError> {
let (w, _h) = self.framebuffer_size();
self.peek_framebuffer(move |fb| match self.pixel_format() {
PixelFormat::ARGB1555 => {
let start = y * w + x;
let gb = fb[start * 2];
let arg = fb[start * 2 + 1];
let (red, green, blue) = argb555to888(gb, arg);
(red, green, blue)
}
PixelFormat::ARGB8888 => {
let off = (y * w + x) * 4;
(fb[off + 1], fb[off + 2], fb[off + 3])
}
PixelFormat::RGB565 => {
let start = y * w + x;
let gb = fb[start * 2];
let rg = fb[start * 2 + 1];
let (red, green, blue) = rgb565to888(gb, rg);
(red, green, blue)
}
})
}
#[allow(clippy::many_single_char_names)]
pub fn for_each_pixel(
&self,
mut f: impl FnMut(usize, usize, u8, u8, u8),
) -> Result<(), RetroRsError> {
let (w, h) = self.framebuffer_size();
let fmt = self.pixel_format();
self.peek_framebuffer(move |fb| {
let mut x = 0;
let mut y = 0;
match fmt {
PixelFormat::ARGB1555 => {
for components in fb.chunks_exact(2) {
let gb = components[0];
let arg = components[1];
let (red, green, blue) = argb555to888(gb, arg);
f(x, y, red, green, blue);
x += 1;
if x >= w {
y += 1;
x = 0;
}
}
}
PixelFormat::ARGB8888 => {
for components in fb.chunks_exact(4) {
let red = components[1];
let green = components[2];
let blue = components[3];
f(x, y, red, green, blue);
x += 1;
if x >= w {
y += 1;
x = 0;
}
}
}
PixelFormat::RGB565 => {
for components in fb.chunks_exact(2) {
let gb = components[0];
let rg = components[1];
let (red, green, blue) = rgb565to888(gb, rg);
f(x, y, red, green, blue);
x += 1;
if x >= w {
y += 1;
x = 0;
}
}
}
};
assert_eq!(y, h);
assert_eq!(x, 0);
})
}
pub fn copy_framebuffer_rgb888(&self, slice: &mut [u8]) -> Result<(), RetroRsError> {
let fmt = self.pixel_format();
self.peek_framebuffer(move |fb| {
match fmt {
PixelFormat::ARGB1555 => {
for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(3)) {
let gb = components[0];
let arg = components[1];
let (red, green, blue) = argb555to888(gb, arg);
dst[0] = red;
dst[1] = green;
dst[2] = blue;
}
}
PixelFormat::ARGB8888 => {
for (components, dst) in fb.chunks_exact(4).zip(slice.chunks_exact_mut(3)) {
let r = components[1];
let g = components[2];
let b = components[3];
dst[0] = r;
dst[1] = g;
dst[2] = b;
}
}
PixelFormat::RGB565 => {
for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(3)) {
let gb = components[0];
let rg = components[1];
let (red, green, blue) = rgb565to888(gb, rg);
dst[0] = red;
dst[1] = green;
dst[2] = blue;
}
}
};
})
}
pub fn copy_framebuffer_rgb332(&self, slice: &mut [u8]) -> Result<(), RetroRsError> {
let fmt = self.pixel_format();
self.peek_framebuffer(move |fb| {
match fmt {
PixelFormat::ARGB1555 => {
for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
let gb = components[0];
let arg = components[1];
let (red, green, blue) = argb555to888(gb, arg);
*dst = rgb888_to_rgb332(red, green, blue);
}
}
PixelFormat::ARGB8888 => {
for (components, dst) in fb.chunks_exact(4).zip(slice.iter_mut()) {
let r = components[1];
let g = components[2];
let b = components[3];
*dst = rgb888_to_rgb332(r, g, b);
}
}
PixelFormat::RGB565 => {
for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
let gb = components[0];
let rg = components[1];
let (red, green, blue) = rgb565to888(gb, rg);
*dst = rgb888_to_rgb332(red, green, blue);
}
}
};
})
}
pub fn copy_framebuffer_argb32(&self, slice: &mut [u32]) -> Result<(), RetroRsError> {
let fmt = self.pixel_format();
self.peek_framebuffer(move |fb| {
match fmt {
PixelFormat::ARGB1555 => {
for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
let gb = components[0];
let arg = components[1];
let (red, green, blue) = argb555to888(gb, arg);
*dst = 0xFF00_0000
| (u32::from(red) << 16)
| (u32::from(green) << 8)
| u32::from(blue);
}
}
PixelFormat::ARGB8888 => {
for (components, dst) in fb.chunks_exact(4).zip(slice.iter_mut()) {
*dst = (u32::from(components[0]) << 24)
| (u32::from(components[1]) << 16)
| (u32::from(components[2]) << 8)
| u32::from(components[3]);
}
}
PixelFormat::RGB565 => {
for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
let gb = components[0];
let rg = components[1];
let (red, green, blue) = rgb565to888(gb, rg);
*dst = 0xFF00_0000
| (u32::from(red) << 16)
| (u32::from(green) << 8)
| u32::from(blue);
}
}
};
})
}
pub fn copy_framebuffer_rgba32(&self, slice: &mut [u32]) -> Result<(), RetroRsError> {
let fmt = self.pixel_format();
self.peek_framebuffer(move |fb| {
match fmt {
PixelFormat::ARGB1555 => {
for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
let gb = components[0];
let arg = components[1];
let (red, green, blue) = argb555to888(gb, arg);
*dst = (u32::from(red) << 24)
| (u32::from(green) << 16)
| (u32::from(blue) << 8)
| (u32::from(0xFF * (arg >> 7)));
}
}
PixelFormat::ARGB8888 => {
for (components, dst) in fb.chunks_exact(4).zip(slice.iter_mut()) {
*dst = (u32::from(components[1]) << 24)
| (u32::from(components[2]) << 16)
| (u32::from(components[3]) << 8)
| u32::from(components[0]);
}
}
PixelFormat::RGB565 => {
for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
let gb = components[0];
let rg = components[1];
let (red, green, blue) = rgb565to888(gb, rg);
*dst = (u32::from(red) << 24)
| (u32::from(green) << 16)
| (u32::from(blue) << 8)
| 0x0000_00FF;
}
}
};
})
}
}
unsafe extern "C" fn callback_environment(cmd: u32, data: *mut c_void) -> bool {
let result = panic::catch_unwind(|| {
match cmd {
ENVIRONMENT_SET_CONTROLLER_INFO => true,
ENVIRONMENT_SET_PIXEL_FORMAT => {
let pixfmti = *(data as *const u32);
let pixfmt = PixelFormat::from_uint(pixfmti);
if pixfmt.is_none() {
return false;
}
let pixfmt = pixfmt.unwrap();
(*CONTEXT).image_depth = match pixfmt {
PixelFormat::ARGB1555 => 15,
PixelFormat::ARGB8888 => 32,
PixelFormat::RGB565 => 16,
};
(*CONTEXT).pixfmt = pixfmt;
true
}
ENVIRONMENT_GET_SYSTEM_DIRECTORY => {
*(data as *mut *const c_char) = (*EMULATOR).core_path.as_ptr();
true
}
ENVIRONMENT_GET_CAN_DUPE => {
*(data as *mut bool) = true;
true
}
ENVIRONMENT_SET_MEMORY_MAPS => {
let map = data as *const MemoryMap;
let desc_slice =
std::slice::from_raw_parts((*map).descriptors, (*map).num_descriptors as usize);
(*CONTEXT).memory_map = Vec::new();
(*CONTEXT).memory_map.extend_from_slice(desc_slice);
true
}
_ => false,
}
});
result.unwrap_or(false)
}
extern "C" fn callback_video_refresh(data: *const c_void, width: u32, height: u32, pitch: usize) {
unsafe {
if !data.is_null() {
(*CONTEXT).frame_ptr = data;
(*CONTEXT).frame_pitch = pitch;
(*CONTEXT).frame_width = width;
(*CONTEXT).frame_height = height;
}
}
}
extern "C" fn callback_audio_sample(left: i16, right: i16) {
unsafe {
let sample_buf = &mut (*CONTEXT).audio_sample;
sample_buf.push(left);
sample_buf.push(right);
}
}
extern "C" fn callback_audio_sample_batch(data: *const i16, frames: usize) -> usize {
unsafe {
let sample_buf = &mut (*CONTEXT).audio_sample;
let slice = std::slice::from_raw_parts(data, frames * 2);
sample_buf.clear();
sample_buf.extend_from_slice(slice);
frames
}
}
extern "C" fn callback_input_poll() {}
extern "C" fn callback_input_state(port: u32, device: u32, index: u32, id: u32) -> i16 {
if port > 1 || device != 1 || index != 0 {
println!("Unsupported port/device/index");
return 0;
}
let id = id;
let port = port as usize;
if id > 16 {
println!("Unexpected button id {}", id);
return 0;
}
unsafe {
if (*CONTEXT).buttons[port].get(id) {
1
} else {
0
}
}
}
impl Drop for Emulator {
fn drop(&mut self) {
unsafe {
((*EMULATOR).core.retro_unload_game)();
((*EMULATOR).core.retro_deinit)();
}
unsafe {
let _ctx = Box::from_raw(CONTEXT);
let _emu = Box::from_raw(EMULATOR);
CONTEXT = ptr::null_mut();
EMULATOR = ptr::null_mut();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[cfg(feature = "use_image")]
extern crate image;
#[cfg(feature = "use_image")]
use crate::fb_to_image::*;
fn mario_is_dead(emu: &Emulator) -> bool {
emu.system_ram_ref()[0x0770] == 0x03
}
const PPU_BIT: usize = 1 << 31;
fn get_byte(emu: &Emulator, addr: usize) -> u8 {
emu.memory_ref(addr).expect("Couldn't read RAM!")[0]
}
#[cfg(feature = "use_image")]
#[test]
fn it_works() {
let mut emu = Emulator::create(
Path::new("../mechlearn/mappy/cores/fceumm_libretro"),
Path::new("roms/mario.nes"),
);
emu.run([Buttons::new(), Buttons::new()]);
emu.reset();
for i in 0..250 {
emu.run([
Buttons::new()
.start(i > 80 && i < 100)
.right(i >= 100)
.a((i >= 100 && i <= 150) || (i >= 180)),
Buttons::new(),
]);
}
let fb = emu.create_imagebuffer();
fb.unwrap().save("out.png").unwrap();
let mut died = false;
for _ in 0..10000 {
emu.run([Buttons::new().right(true), Buttons::new()]);
if mario_is_dead(&emu) {
died = true;
let fb = emu.create_imagebuffer();
fb.unwrap().save("out2.png").unwrap();
break;
}
}
assert!(died);
emu.reset();
for i in 0..250 {
emu.run([
Buttons::new()
.start(i > 80 && i < 100)
.right(i >= 100)
.a((i >= 100 && i <= 150) || (i >= 180)),
Buttons::new(),
]);
}
}
}