From b1a0351399678c95c6b2df02135f6ae391e58b07 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Thu, 13 Nov 2025 12:31:40 -0700 Subject: [PATCH] WIP --- Cargo.lock | 1 + abi/src/lib.rs | 28 ++++-- abi_sys/src/lib.rs | 23 ++++- kernel/src/abi.rs | 61 ++++++++++-- kernel/src/display.rs | 20 +++- kernel/src/elf.rs | 1 + kernel/src/main.rs | 14 +-- kernel/src/storage.rs | 14 ++- kernel/src/ui.rs | 4 +- selection_ui/src/lib.rs | 5 +- user-apps/calculator/src/main.rs | 2 +- user-apps/gboy/Cargo.toml | 1 + user-apps/gboy/build.rs | 1 + user-apps/gboy/src/main.rs | 156 +++++++++++++++++++++++++------ user-apps/gboy/src/peanut.rs | 31 ++++-- user-apps/gif/Cargo.toml | 2 +- user-apps/gif/src/main.rs | 5 +- 17 files changed, 293 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 941d6b6..8a5a333 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1291,6 +1291,7 @@ dependencies = [ "bindgen", "cc", "embedded-graphics", + "selection_ui", ] [[package]] diff --git a/abi/src/lib.rs b/abi/src/lib.rs index d98c8bd..29be913 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -3,8 +3,8 @@ extern crate alloc; -pub use abi_sys::{self, keyboard}; use abi_sys::{RngRequest, alloc, dealloc, keyboard::KeyEvent}; +pub use abi_sys::{keyboard, print}; pub use alloc::format; use core::alloc::{GlobalAlloc, Layout}; use rand_core::RngCore; @@ -28,7 +28,7 @@ unsafe impl GlobalAlloc for Alloc { macro_rules! print { ($($arg:tt)*) => {{ let s = $crate::format!($($arg)*); - $crate::abi_sys::print(s.as_ptr(), s.len()); + $crate::print(s.as_ptr(), s.len()); }}; } @@ -61,10 +61,8 @@ pub mod display { pub type Pixel565 = Pixel; - const BUF_SIZE: usize = 15 * 1024; // tune this for performance + const BUF_SIZE: usize = 1024; static mut BUF: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; - // const BUF_SIZE: usize = 250 * 1024; // tune this for performance - // static mut BUF: Lazy> = Lazy::new(|| vec![const { CPixel::new() }; BUF_SIZE]); static DISPLAY_TAKEN: AtomicBool = AtomicBool::new(false); @@ -160,20 +158,30 @@ impl RngCore for Rng { } pub mod fs { + use alloc::vec::Vec; use core::fmt::Display; - use alloc::{format, vec::Vec}; - - pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize { + pub fn read_file(file: &str, start_from: usize, buf: &mut [u8]) -> usize { abi_sys::read_file( file.as_ptr(), file.len(), - read_from, + start_from, buf.as_mut_ptr(), buf.len(), ) } + pub fn write_file(file: &str, start_from: usize, buf: &[u8]) { + abi_sys::write_file( + file.as_ptr(), + file.len(), + start_from, + buf.as_ptr(), + buf.len(), + ) + } + + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct FileName<'a> { full: &'a str, base: &'a str, @@ -217,7 +225,7 @@ pub mod fs { const MAX_ENTRY_NAME_LEN: usize = 25; const MAX_ENTRIES: usize = 25; - #[derive(Clone, Copy)] + #[derive(Clone, Copy, Debug)] pub struct Entries([[u8; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]); impl Entries { diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 3243771..af05c58 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -12,7 +12,7 @@ use strum::{EnumCount, EnumIter}; pub type EntryFn = fn(); -pub const ABI_CALL_TABLE_COUNT: usize = 11; +pub const ABI_CALL_TABLE_COUNT: usize = 12; const _: () = assert!(ABI_CALL_TABLE_COUNT == CallTable::COUNT); #[derive(Clone, Copy, EnumIter, EnumCount)] @@ -28,7 +28,8 @@ pub enum CallTable { GenRand = 7, ListDir = 8, ReadFile = 9, - FileLen = 10, + WriteFile = 10, + FileLen = 11, } #[unsafe(no_mangle)] @@ -438,6 +439,24 @@ pub extern "C" fn read_file( } } +pub type WriteFile = + extern "C" fn(str: *const u8, len: usize, write_from: usize, buf: *const u8, buf_len: usize); + +#[unsafe(no_mangle)] +pub extern "C" fn write_file( + str: *const u8, + len: usize, + write_from: usize, + buf: *const u8, + buf_len: usize, +) { + unsafe { + let ptr = CALL_ABI_TABLE[CallTable::WriteFile as usize]; + let f: WriteFile = core::mem::transmute(ptr); + f(str, len, write_from, buf, buf_len) + } +} + pub type FileLen = extern "C" fn(str: *const u8, len: usize) -> usize; #[unsafe(no_mangle)] diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs index fc78a93..5e17d76 100644 --- a/kernel/src/abi.rs +++ b/kernel/src/abi.rs @@ -1,6 +1,6 @@ use abi_sys::{ AllocAbi, CLayout, CPixel, DeallocAbi, DrawIterAbi, FileLen, GenRand, GetMsAbi, ListDir, - PrintAbi, ReadFile, RngRequest, SleepMsAbi, keyboard::*, + PrintAbi, ReadFile, RngRequest, SleepMsAbi, WriteFile, keyboard::*, }; use alloc::{string::ToString, vec::Vec}; use core::{ffi::c_char, ptr, sync::atomic::Ordering}; @@ -207,6 +207,7 @@ fn recurse_file( dirs: &[&str], mut access: impl FnMut(&mut File) -> T, ) -> Result { + defmt::info!("dir: {}, dirs: {}", dir, dirs); if dirs.len() == 1 { let mut b = [0_u8; 50]; let mut buf = LfnBuffer::new(&mut b); @@ -218,7 +219,8 @@ fn recurse_file( } } }) - .unwrap(); + .expect("Failed to iterate dir"); + if let Some(name) = short_name { let mut file = dir .open_file_in_dir(name, embedded_sdmmc::Mode::ReadWriteAppend) @@ -242,7 +244,17 @@ pub extern "C" fn read_file( ) -> usize { // SAFETY: caller guarantees `ptr` is valid for `len` bytes let file = unsafe { core::str::from_raw_parts(str, len) }; - let file: Vec<&str> = file.split('/').collect(); + + let mut components: [&str; 8] = [""; 8]; + let mut count = 0; + for part in file.split('/') { + if count >= components.len() { + break; + } + components[count] = part; + count += 1; + } + // SAFETY: caller guarantees `ptr` is valid for `len` bytes let mut buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; @@ -252,8 +264,8 @@ pub extern "C" fn read_file( let sd = guard.as_mut().unwrap(); if !file.is_empty() { sd.access_root_dir(|root| { - if let Ok(result) = recurse_file(&root, &file[1..], |file| { - file.seek_from_start(start_from as u32).unwrap(); + if let Ok(result) = recurse_file(&root, &components[1..count], |file| { + file.seek_from_start(start_from as u32).unwrap_or(()); file.read(&mut buf).unwrap() }) { read = result @@ -263,9 +275,46 @@ pub extern "C" fn read_file( read } +const _: WriteFile = write_file; +pub extern "C" fn write_file( + str: *const u8, + len: usize, + start_from: usize, + buf: *const u8, + buf_len: usize, +) { + // SAFETY: caller guarantees str ptr is valid for `len` bytes + let file = unsafe { core::str::from_raw_parts(str, len) }; + + let mut components: [&str; 8] = [""; 8]; + let mut count = 0; + for part in file.split('/') { + if count >= components.len() { + break; + } + components[count] = part; + count += 1; + } + + // SAFETY: caller guarantees buf ptr is valid for `buf_len` bytes + let buf = unsafe { core::slice::from_raw_parts(buf, buf_len) }; + + let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard"); + let sd = guard.as_mut().unwrap(); + if !file.is_empty() { + sd.access_root_dir(|root| { + recurse_file(&root, &components[1..count], |file| { + file.seek_from_start(start_from as u32).unwrap(); + file.write(&buf).unwrap() + }) + .unwrap_or(()) + }); + }; +} + const _: FileLen = file_len; pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { - // SAFETY: caller guarantees `ptr` is valid for `len` bytes + // SAFETY: caller guarantees str ptr is valid for `len` bytes let file = unsafe { core::str::from_raw_parts(str, len) }; let file: Vec<&str> = file.split('/').collect(); diff --git a/kernel/src/display.rs b/kernel/src/display.rs index 2fdd9f2..86c693f 100644 --- a/kernel/src/display.rs +++ b/kernel/src/display.rs @@ -87,8 +87,14 @@ pub async fn init_display( #[embassy_executor::task] pub async fn display_handler(mut display: DISPLAY) { + use embassy_time::{Instant, Timer}; + + // Target ~60 Hz refresh (≈16.67 ms per frame) + const FRAME_TIME_MS: u64 = 1000 / 60; + loop { - // renders fps text to canvas + let start = Instant::now(); + #[cfg(feature = "fps")] unsafe { if FPS_COUNTER.should_draw() { @@ -103,11 +109,15 @@ pub async fn display_handler(mut display: DISPLAY) { .unwrap() .partial_draw(&mut display) .await - .unwrap() - }; + .unwrap(); + } } - // small yield to allow other tasks to run - Timer::after_millis(10).await; + let elapsed = start.elapsed().as_millis() as u64; + if elapsed < FRAME_TIME_MS { + Timer::after_millis(FRAME_TIME_MS - elapsed).await; + } else { + Timer::after_millis(1).await; + } } } diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index bdc15de..ec180a4 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -206,6 +206,7 @@ fn patch_abi( CallTable::GenRand => abi::gen_rand as usize, CallTable::ListDir => abi::list_dir as usize, CallTable::ReadFile => abi::read_file as usize, + CallTable::WriteFile => abi::write_file as usize, CallTable::FileLen => abi::file_len as usize, }; unsafe { diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 5b1fa00..8c1c757 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -122,7 +122,7 @@ static UI_CHANGE: Signal = Signal::new(); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = if cfg!(feature = "overclock") { - let clocks = ClockConfig::system_freq(192_000_000).unwrap(); + let clocks = ClockConfig::system_freq(300_000_000).unwrap(); let config = Config::new(clocks); embassy_rp::init(config) } else { @@ -339,6 +339,9 @@ async fn kernel_task( .spawn(watchdog_task(Watchdog::new(watchdog))) .unwrap(); + #[cfg(feature = "debug")] + defmt::info!("Clock: {}", embassy_rp::clocks::clk_sys_freq()); + setup_mcu(mcu).await; #[cfg(feature = "defmt")] @@ -372,7 +375,8 @@ async fn prog_search_handler() { let mut guard = SDCARD.get().lock().await; let sd = guard.as_mut().unwrap(); - let files = sd.list_files_by_extension(".bin").unwrap(); + let mut files = sd.list_files_by_extension(".bin").unwrap(); + files.sort(); let mut select = SELECTIONS.lock().await; if *select.selections() != files { @@ -387,10 +391,8 @@ async fn prog_search_handler() { async fn key_handler() { loop { if let Some(event) = read_keyboard_fifo().await { - if let KeyState::Pressed = event.state { - unsafe { - let _ = KEY_CACHE.enqueue(event); - } + unsafe { + let _ = KEY_CACHE.enqueue(event); } } Timer::after_millis(50).await; diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index 15093d8..0632fa5 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -35,12 +35,24 @@ impl TimeSource for DummyTimeSource { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub struct FileName { pub long_name: String, pub short_name: ShortFileName, } +impl PartialOrd for FileName { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.long_name.cmp(&other.long_name)) + } +} + +impl Ord for FileName { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.long_name.cmp(&other.long_name) + } +} + pub struct SdCard { det: Input<'static>, volume_mgr: VolMgr, diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index dc6017c..2a304a9 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -75,7 +75,7 @@ pub async fn clear_selection() { async fn draw_selection() { let mut guard = SELECTIONS.lock().await; - let file_names = &guard.selections.clone(); + let file_names = guard.selections.clone(); let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); let display_area = unsafe { FRAMEBUFFER.as_mut().unwrap().bounding_box() }; @@ -99,7 +99,7 @@ async fn draw_selection() { } else { let mut views: alloc::vec::Vec>> = Vec::new(); - for i in file_names { + for i in &file_names { views.push(Text::new(&i.long_name, Point::zero(), text_style)); } diff --git a/selection_ui/src/lib.rs b/selection_ui/src/lib.rs index ccb175e..b389515 100644 --- a/selection_ui/src/lib.rs +++ b/selection_ui/src/lib.rs @@ -55,6 +55,9 @@ impl<'a> SelectionUi<'a> { if key.state == KeyState::Pressed { if let Some(s) = self.update(display, key.key)? { selection = Some(s); + display + .clear(Rgb565::BLACK) + .map_err(|e| SelectionUiError::DisplayError(e))?; break; } } @@ -69,7 +72,6 @@ impl<'a> SelectionUi<'a> { display: &mut Display, key: KeyCode, ) -> Result, SelectionUiError<::Error>> { - print!("Got Key: {:?}", key); match key { KeyCode::Down => { self.selection = (self.selection + 1).min(self.items.len() - 1); @@ -80,7 +82,6 @@ impl<'a> SelectionUi<'a> { KeyCode::Enter | KeyCode::Right => return Ok(Some(self.selection)), _ => return Ok(None), }; - print!("new selection: {:?}", self.selection); self.draw(display)?; Ok(None) } diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs index 9387018..5b84b29 100644 --- a/user-apps/calculator/src/main.rs +++ b/user-apps/calculator/src/main.rs @@ -104,7 +104,7 @@ pub fn main() { } let event = get_key(); - if event.state != KeyState::Idle { + if event.state == KeyState::Released { match event.key { KeyCode::Char(ch) => { input.push(ch); diff --git a/user-apps/gboy/Cargo.toml b/user-apps/gboy/Cargo.toml index 184cec7..81408d7 100644 --- a/user-apps/gboy/Cargo.toml +++ b/user-apps/gboy/Cargo.toml @@ -9,4 +9,5 @@ cc = "1.2.44" [dependencies] abi = { path = "../../abi" } +selection_ui = { path = "../../selection_ui" } embedded-graphics = "0.8.1" diff --git a/user-apps/gboy/build.rs b/user-apps/gboy/build.rs index a8bd29d..dfec06b 100644 --- a/user-apps/gboy/build.rs +++ b/user-apps/gboy/build.rs @@ -45,6 +45,7 @@ fn bindgen() { .expect("Couldn't write bindings!"); cc::Build::new() + .define("PEANUT_GB_IS_LITTLE_ENDIAN", None) .file("peanut_gb_stub.c") .include("Peanut-GB") // optimization flags diff --git a/user-apps/gboy/src/main.rs b/user-apps/gboy/src/main.rs index d81816f..158f789 100644 --- a/user-apps/gboy/src/main.rs +++ b/user-apps/gboy/src/main.rs @@ -5,24 +5,34 @@ extern crate alloc; use abi::{ display::Display, - fs::{file_len, read_file}, + format, + fs::{Entries, file_len, list_dir, read_file, write_file}, get_key, keyboard::{KeyCode, KeyState}, print, }; -use alloc::{vec, vec::Vec}; -use core::{ffi::c_void, mem::MaybeUninit, panic::PanicInfo}; +use alloc::{string::String, vec, vec::Vec}; +use core::{cell::LazyCell, ffi::c_void, mem::MaybeUninit, panic::PanicInfo}; +use embedded_graphics::{ + mono_font::{MonoTextStyle, ascii::FONT_6X10}, + pixelcolor::Rgb565, + prelude::RgbColor, +}; +use selection_ui::{SelectionUi, SelectionUiError, draw_text_center}; mod peanut; use peanut::gb_run_frame; use crate::peanut::{ JOYPAD_A, JOYPAD_B, JOYPAD_DOWN, JOYPAD_LEFT, JOYPAD_RIGHT, JOYPAD_SELECT, JOYPAD_START, - JOYPAD_UP, gb_cart_ram_read, gb_cart_ram_write, gb_error, gb_init, gb_init_lcd, gb_rom_read, - gb_s, lcd_draw_line, + JOYPAD_UP, gb_cart_ram_read, gb_cart_ram_write, gb_error, gb_get_rom_name, gb_get_save_size, + gb_init, gb_init_lcd, gb_rom_read, gb_s, lcd_draw_line, }; -static mut DISPLAY: Display = Display; +static mut DISPLAY: LazyCell = LazyCell::new(|| Display::take().unwrap()); + +const RAM_SIZE: usize = 32 * 1024; // largest ram size is 32k +static mut RAM: [u8; RAM_SIZE] = [0; RAM_SIZE]; #[panic_handler] fn panic(info: &PanicInfo) -> ! { @@ -35,7 +45,7 @@ pub extern "Rust" fn _start() { main() } -const GAME: &'static str = "/games/gameboy/zelda.gb"; +const GAME_PATH: &'static str = "/games/gameboy"; static mut GAME_ROM: Option> = None; @@ -45,9 +55,43 @@ struct Priv {} pub fn main() { print!("Starting Gameboy app"); - let size = file_len(GAME); + let mut entries = Entries::new(); + list_dir(GAME_PATH, &mut entries); + + let mut files = entries.entries(); + files.retain(|e| { + let ext = e.extension().unwrap_or(""); + ext == "gb" || ext == "GB" + }); + let mut roms = files.iter().map(|e| e.full_name()).collect::>(); + roms.sort(); + + let selection = { + let display = unsafe { &mut *DISPLAY }; + let mut selection_ui = SelectionUi::new(&roms); + match selection_ui.run_selection_ui(display) { + Ok(maybe_sel) => maybe_sel, + Err(e) => match e { + SelectionUiError::SelectionListEmpty => { + draw_text_center( + display, + &format!("No Roms were found in {}", GAME_PATH), + MonoTextStyle::new(&FONT_6X10, Rgb565::RED), + ) + .expect("Display Error"); + None + } + SelectionUiError::DisplayError(_) => panic!("Display Error"), + }, + } + }; + + assert!(selection.is_some()); + + let file_name = format!("{}/{}", GAME_PATH, roms[selection.unwrap()]); + let size = file_len(&file_name); unsafe { GAME_ROM = Some(vec![0_u8; size]) }; - let read = read_file(GAME, 0, unsafe { GAME_ROM.as_mut().unwrap() }); + let read = read_file(&file_name, 0, unsafe { GAME_ROM.as_mut().unwrap() }); assert!(size == read); print!("Rom size: {}", read); @@ -66,40 +110,92 @@ pub fn main() { }; print!("gb init status: {}", init_status); + unsafe { + load_save(&mut gb.assume_init()); + } + unsafe { gb_init_lcd(gb.as_mut_ptr(), Some(lcd_draw_line)); // enable frame skip - gb.assume_init().direct.set_frame_skip(true); + gb.assume_init().direct.set_frame_skip(!true); // active low }; loop { let event = get_key(); - let keycode = match event.key { - KeyCode::Esc => break, - KeyCode::Tab => Some(JOYPAD_START), - KeyCode::Del => Some(JOYPAD_SELECT), - KeyCode::Enter => Some(JOYPAD_A), - KeyCode::Backspace => Some(JOYPAD_B), - KeyCode::JoyUp => Some(JOYPAD_UP), - KeyCode::JoyDown => Some(JOYPAD_DOWN), - KeyCode::JoyLeft => Some(JOYPAD_LEFT), - KeyCode::JoyRight => Some(JOYPAD_RIGHT), - _ => None, + let button = match event.key { + KeyCode::Esc => { + unsafe { write_save(&mut gb.assume_init()) }; + break; + } + KeyCode::Tab => JOYPAD_START as u8, + KeyCode::Del => JOYPAD_SELECT as u8, + KeyCode::Enter => JOYPAD_A as u8, + KeyCode::Backspace => JOYPAD_B as u8, + KeyCode::Up => JOYPAD_UP as u8, + KeyCode::Down => JOYPAD_DOWN as u8, + KeyCode::Left => JOYPAD_LEFT as u8, + KeyCode::Right => JOYPAD_RIGHT as u8, + _ => 0, }; - if let Some(keycode) = keycode { + if button != 0 { + let mut joypad = unsafe { (*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad }; match event.state { - KeyState::Pressed => unsafe { - (*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad &= !keycode as u8 - }, - KeyState::Released => unsafe { - (*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad |= keycode as u8 - }, - _ => (), + KeyState::Pressed => joypad &= !button, + KeyState::Released => joypad |= button, + _ => {} } + + print!("joypad now: {:#010b}\n", joypad); } - unsafe { gb_run_frame(gb.as_mut_ptr()) }; + unsafe { + gb_run_frame(gb.as_mut_ptr()); + } + } +} + +unsafe fn load_save(gb: &mut gb_s) { + let mut buf = [0; 16]; + + unsafe { + gb_get_rom_name(gb, buf.as_mut_ptr()); + + let save_size = gb_get_save_size(gb); + + if save_size > 0 { + read_file( + &format!( + "{}/saves/{}.sav", + GAME_PATH, + str::from_utf8(&buf).expect("bad rom name") + ), + 0, + &mut RAM, + ); + } + } +} + +unsafe fn write_save(gb: &mut gb_s) { + let mut buf = [0; 16]; + + unsafe { + gb_get_rom_name(gb, buf.as_mut_ptr()); + + let save_size = gb_get_save_size(gb); + + if save_size > 0 { + write_file( + &format!( + "{}/saves/{}.sav", + GAME_PATH, + str::from_utf8(&buf).expect("bad rom name") + ), + 0, + &mut RAM, + ); + } } } diff --git a/user-apps/gboy/src/peanut.rs b/user-apps/gboy/src/peanut.rs index 7bfd046..818cc30 100644 --- a/user-apps/gboy/src/peanut.rs +++ b/user-apps/gboy/src/peanut.rs @@ -2,27 +2,42 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] -use crate::{DISPLAY, GAME_ROM}; +use crate::{DISPLAY, GAME_ROM, RAM}; +#[allow(unused)] include!(concat!(env!("OUT_DIR"), "/bindings.rs")); -use abi::{display::Pixel565, fs::read_file}; +use abi::{display::Pixel565, print}; use embedded_graphics::{Drawable, pixelcolor::Rgb565, prelude::Point}; pub const GBOY_WIDTH: usize = 160; pub const GBOY_HEIGHT: usize = 144; -pub unsafe extern "C" fn gb_rom_read(gb: *mut gb_s, addr: u32) -> u8 { +pub unsafe extern "C" fn gb_rom_read(_gb: *mut gb_s, addr: u32) -> u8 { unsafe { GAME_ROM.as_ref().unwrap()[addr as usize] } } -pub unsafe extern "C" fn gb_cart_ram_read(gb: *mut gb_s, addr: u32) -> u8 { - 0 +pub unsafe extern "C" fn gb_cart_ram_read(_gb: *mut gb_s, addr: u32) -> u8 { + unsafe { RAM[addr as usize] } } -pub unsafe extern "C" fn gb_cart_ram_write(gb: *mut gb_s, addr: u32, val: u8) {} +pub unsafe extern "C" fn gb_cart_ram_write(_gb: *mut gb_s, addr: u32, val: u8) { + unsafe { RAM[addr as usize] = val } +} -pub unsafe extern "C" fn gb_error(gb: *mut gb_s, err: gb_error_e, addr: u16) {} +pub unsafe extern "C" fn gb_error(_gb: *mut gb_s, err: gb_error_e, addr: u16) { + let e = match err { + 0 => "UNKNOWN ERROR", + 1 => "INVALID OPCODE", + 2 => "INVALID READ", + 3 => "INVALID WRITE", + 4 => "HALT FOREVER", + 5 => "INVALID MAX", + _ => unreachable!(), + }; + + print!("PeanutGB error: {}, addr: {}", e, addr); +} const NUM_PALETTES: usize = 3; const SHADES_PER_PALETTE: usize = 4; @@ -82,6 +97,6 @@ fn draw_color(color: Rgb565, x: u16, y: u16) { pixel.1 = color; unsafe { - pixel.draw(&mut DISPLAY).unwrap(); + pixel.draw(&mut *DISPLAY).unwrap(); } } diff --git a/user-apps/gif/Cargo.toml b/user-apps/gif/Cargo.toml index 1c5ecd8..cc45976 100644 --- a/user-apps/gif/Cargo.toml +++ b/user-apps/gif/Cargo.toml @@ -5,6 +5,6 @@ edition = "2024" [dependencies] abi = { path = "../../abi" } -embedded-graphics = "0.8.1" selection_ui = { path = "../../selection_ui" } +embedded-graphics = "0.8.1" tinygif = { git = "https://github.com/LegitCamper/tinygif" } diff --git a/user-apps/gif/src/main.rs b/user-apps/gif/src/main.rs index 77f580b..4afee6c 100644 --- a/user-apps/gif/src/main.rs +++ b/user-apps/gif/src/main.rs @@ -41,9 +41,10 @@ pub fn main() { let mut files = entries.entries(); files.retain(|e| e.extension().unwrap_or("") == "gif"); - let gifs = &files.iter().map(|e| e.full_name()).collect::>(); + let mut gifs = files.iter().map(|e| e.full_name()).collect::>(); + gifs.sort(); - let mut selection_ui = SelectionUi::new(&gifs); + let mut selection_ui = SelectionUi::new(&mut gifs); let selection = match selection_ui.run_selection_ui(&mut display) { Ok(maybe_sel) => maybe_sel, Err(e) => match e {