This commit is contained in:
2025-11-02 15:46:22 -07:00
parent 7fcc5291e6
commit 957189cd0b
11 changed files with 356 additions and 0 deletions

12
user-apps/nes/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "nes"
version = "0.1.0"
edition = "2024"
[build-dependencies]
bindgen = "0.71.0"
cc = "1.2.44"
[dependencies]
abi = { path = "../../abi" }
embedded-graphics = "0.8.1"

54
user-apps/nes/build.rs Normal file
View File

@@ -0,0 +1,54 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
bindgen();
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("../memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=memory.x");
println!("cargo:rustc-link-arg-bins=-Tmemory.x");
}
fn bindgen() {
let bindings = bindgen::Builder::default()
.header("Peanut-GB/peanut_gb.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.clang_arg("-I../../picolibc/newlib/libc/include/")
.clang_arg("-I../../picolibc/build/")
.use_core()
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
cc::Build::new()
.file("peanut_gb_stub.c")
.include("Peanut-GB")
.compile("peanut_gb");
println!("cargo:rustc-link-search=Peanut-GB");
println!("cargo:rustc-link-lib=peanut_gb");
}

View File

@@ -0,0 +1 @@
#include <peanut_gb.h>

88
user-apps/nes/src/main.rs Normal file
View File

@@ -0,0 +1,88 @@
#![no_std]
#![no_main]
#![allow(static_mut_refs)]
extern crate alloc;
use abi::{
Rng,
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
fs::{file_len, read_file},
get_key,
keyboard::{KeyCode, KeyState},
print, sleep,
};
use alloc::{vec, vec::Vec};
use core::{ffi::c_void, mem::MaybeUninit, panic::PanicInfo};
use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
mod peanut;
use peanut::gb_run_frame;
use crate::peanut::{
gb_cart_ram_read, gb_cart_ram_write, gb_error, gb_init, gb_init_lcd, gb_rom_read, gb_s,
lcd_draw_line,
};
static mut DISPLAY: Display = Display;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
print!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {}
}
#[unsafe(no_mangle)]
pub extern "Rust" fn _start() {
main()
}
const GAME: &'static str = "/games/nes/game.nes";
#[repr(C)]
struct Priv {
rom: Vec<u8>,
}
pub fn main() {
print!("Starting Nes app");
let size = file_len(GAME);
let mut priv_ = MaybeUninit::<Priv>::uninit();
// let read = unsafe {
// let priv_ptr = priv_.as_mut_ptr();
// (*priv_ptr).rom = Vec::with_capacity(size);
// read_file(GAME, 0, &mut (*priv_ptr).rom)
// };
// print!("read: {}, file size: {}", read, size);
// assert!(read == size);
let mut gb = MaybeUninit::<gb_s>::uninit();
let init_status = unsafe {
gb_init(
gb.as_mut_ptr(),
Some(gb_rom_read),
Some(gb_cart_ram_read),
Some(gb_cart_ram_write),
Some(gb_error),
priv_.as_mut_ptr() as *mut c_void,
)
};
print!("gb init status: {}", init_status);
unsafe { gb_init_lcd(gb.as_mut_ptr(), Some(lcd_draw_line)) };
loop {
let event = get_key();
if event.state != KeyState::Idle {
let key = match event.key {
KeyCode::Esc => return,
_ => (),
};
};
unsafe { gb_run_frame(gb.as_mut_ptr()) };
}
}

View File

@@ -0,0 +1,45 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::DISPLAY;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
use abi::{display::Pixel565, fs::read_file};
use embedded_graphics::{Drawable, pixelcolor::Rgb565, prelude::Point};
pub const NES_WIDTH: usize = 256;
pub const NES_HEIGHT: usize = 240;
pub unsafe extern "C" fn gb_rom_read(gb: *mut gb_s, addr: u32) -> u8 {
let mut buf = [0_u8; 1];
read_file("/games/nes/rom.nes", addr as usize, &mut buf);
buf[0]
}
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_write(gb: *mut gb_s, addr: u32, val: u8) {}
pub unsafe extern "C" fn gb_error(gb: *mut gb_s, err: gb_error_e, addr: u16) {}
pub unsafe extern "C" fn lcd_draw_line(gb: *mut gb_s, pixels: *const u8, line: u8) {
unsafe {
if line < NES_HEIGHT as u8 {
let pixels = core::slice::from_raw_parts(pixels, NES_WIDTH);
let mut x = 0;
for p in pixels {
let mut pixel = Pixel565::default();
pixel.0 = Point::new(x, line.into());
pixel.1 = Rgb565::new(*p, 10, 10);
pixel.draw(&mut DISPLAY).unwrap();
x += 1;
}
}
}
}