diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b2f072..b24f34d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,8 +24,10 @@ jobs: args: --all - name: Cargo n64 build run: | - cargo install --path cargo-n64 + cargo install --path . dd if=/dev/zero of=/tmp/dummy-ipl3 bs=4032 count=1 + git clone https://github.com/rust-console/rrt0.git /tmp/rrt0 + cd /tmp/rrt0 cargo n64 build --ipl3 /tmp/dummy-ipl3 -- --package hello-ipl3font tests: name: Test diff --git a/Cargo.lock b/Cargo.lock index 5c707a5..5be517b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "goblin" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32401e89c6446dcd28185931a01b1093726d0356820ac744023e6850689bf926" +checksum = "cfeb764aa29a0774d290c2df134a37ab2e3c1ba59009162626658aabefda321a" dependencies = [ "log", "plain", @@ -134,14 +134,6 @@ dependencies = [ "syn", ] -[[package]] -name = "hello-ipl3font" -version = "0.1.0" -dependencies = [ - "n64lib", - "rrt0", -] - [[package]] name = "hermit-abi" version = "0.1.19" @@ -169,12 +161,6 @@ version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" -[[package]] -name = "libm" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" - [[package]] name = "log" version = "0.4.17" @@ -184,10 +170,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "n64lib" -version = "0.1.0" - [[package]] name = "num-integer" version = "0.1.45" @@ -215,31 +197,22 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] -[[package]] -name = "rrt0" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95990a96c116f2c3036a0677f1b8e8885bdcf1e0f434a7529dff1467d272c0ec" -dependencies = [ - "libm", -] - [[package]] name = "ryu" version = "1.0.10" @@ -248,18 +221,18 @@ checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" [[package]] name = "scroll" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" dependencies = [ "scroll_derive", ] [[package]] name = "scroll_derive" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaaae8f38bb311444cfb7f1979af0bc9240d95795f75f9ceddf6a59b79ceffa0" +checksum = "bdbda6ac5cd1321e724fa9cee216f3a61885889b896f073b8f82322789c5250e" dependencies = [ "proc-macro2", "quote", @@ -299,9 +272,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.96" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 324f916..55a85f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,25 @@ -[workspace] -members = [ - "cargo-n64", - "examples/n64lib", - "examples/hello-ipl3font", -] +[package] +name = "cargo-n64" +version = "0.2.0" +authors = ["Jay Oster "] +repository = "https://github.com/rust-console/cargo-n64" +description = "Cargo subcommand to build Nintendo 64 ROMs" +license = "MIT" +readme = "README.md" +categories = ["command-line-utilities", "development-tools", "embedded"] +keywords = ["cli", "cross", "compilation", "nintendo", "n64"] +edition = "2021" + +[dependencies] +colored = "2.0" +crc32fast = "1.2" +error-iter = "0.2" +fatfs = "0.3" +goblin = { version = "0.5", default-features = false, features = ["std", "elf32", "elf64", "endian_fd"] } +gumdrop = "0.8" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0" [profile.release] lto = "thin" diff --git a/README.md b/README.md index c61804c..ba30b1b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ rustup run $(cat rust-toolchain) -- rustup component add rust-src Install `cargo-n64` from source: ```bash -cargo install --path cargo-n64 +cargo install --path . ``` Install `cargo-n64` from [crates.io](https://crates.io/): @@ -38,3 +38,7 @@ Nintendo 64 ROMs are flat binaries, and each one is unique. There is no standard This makes it challenging to get started with N64 development, in general. You first have to build an OS from scratch, or use a library like [`libdragon`](https://github.com/DragonMinded/libdragon) or [`libn64`](https://github.com/tj90241/n64chain/tree/master/libn64). Then you need a tool (or two, or three!) to convert the object files from the compiler toolchain into a flat binary, add the header and IPL3, and finally fix the IPL3 checksum. `cargo-n64` takes the place of the latter set of tools and plugs in nicely to the Rust/cargo ecosystem. For copyright purposes, the IPL3 binary is not included in this package. Collecting a working IPL3 binary is left as an exercise for the reader. You will be required to provide the path to your IPL3 with the `--ipl3` command line argument, or extract it from an existing ROM with `--ipl3-from-rom`. + +## Examples + +The separate `rrt0` repo has some examples you can build with `cargo-n64`: https://github.com/rust-console/rrt0/tree/main/examples diff --git a/cargo-n64/Cargo.toml b/cargo-n64/Cargo.toml deleted file mode 100644 index 7eba47b..0000000 --- a/cargo-n64/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "cargo-n64" -version = "0.2.0" -authors = ["Jay Oster "] -repository = "https://github.com/rust-console/cargo-n64" -description = "Cargo subcommand to build Nintendo 64 ROMs" -license = "MIT" -readme = "../README.md" -categories = ["command-line-utilities", "development-tools", "embedded"] -keywords = ["cli", "cross", "compilation", "nintendo", "n64"] -edition = "2018" - -[dependencies] -colored = "2.0" -crc32fast = "1.2" -error-iter = "0.2" -fatfs = "0.3" -goblin = { version = "0.4", default-features = false, features = ["std", "elf32", "elf64", "endian_fd"] } -gumdrop = "0.8" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "1.0" diff --git a/cargo-n64/rust-toolchain b/cargo-n64/rust-toolchain deleted file mode 100644 index 05d4932..0000000 --- a/cargo-n64/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly-2021-08-07 diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 6f1157f..0000000 --- a/examples/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Rust N64 Examples - -IMPORTANT: An official Nintendo IPL3 bootrom is required, but is *not* included for copyright reasons. - - -## n64lib - -This is a sample library for (unsafely) interacting with N64 hardware. It is not intended to be a production quality crate, but is useful for some very simple demos like the ones presented here. - - -## hello-ipl3 - -This is a "hello world" that uses the font embedded in the IPL3 bootrom. The font is kind of ugly; fixed-width, containing only uppercase letters, numbers, and a handful of symbols. It has a small subset of the ASCII character set with only 50 glyphs. - -![hello-ipl3font screenshot](images/hello-ipl3font.png) - - -### Building - -To build with `cargo n64` from the project root directory, use the `--package` argument: - -``` -$ cargo n64 build --ipl3 /path/to/ipl3.bin -- --package hello-ipl3font -``` diff --git a/examples/hello-ipl3font/Cargo.toml b/examples/hello-ipl3font/Cargo.toml deleted file mode 100644 index fa55404..0000000 --- a/examples/hello-ipl3font/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "hello-ipl3font" -version = "0.1.0" -authors = ["Jay Oster "] -edition = "2018" - -[dependencies] -n64lib = { path = "../n64lib" } -rrt0 = "0.3" diff --git a/examples/hello-ipl3font/src/main.rs b/examples/hello-ipl3font/src/main.rs deleted file mode 100644 index 9fcef38..0000000 --- a/examples/hello-ipl3font/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![deny(clippy::all)] -#![forbid(unsafe_code)] -#![no_std] - -// Pull panic into scope -// Required by panic_handler -#[cfg(not(test))] -pub use rrt0; - -use n64lib::{ipl3font, vi}; - -// Colors are 5:5:5:1 RGB with a 16-bit color depth. -#[allow(clippy::unusual_byte_groupings)] -const WHITE: u16 = 0b11111_11111_11111_1; - -fn main() { - vi::init(); - - ipl3font::draw_str_centered(WHITE, "Hello, world!"); - vi::swap_buffer(); -} diff --git a/examples/images/hello-ipl3font.png b/examples/images/hello-ipl3font.png deleted file mode 100644 index 82e4dd8..0000000 Binary files a/examples/images/hello-ipl3font.png and /dev/null differ diff --git a/examples/n64lib/Cargo.toml b/examples/n64lib/Cargo.toml deleted file mode 100644 index c3c5301..0000000 --- a/examples/n64lib/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "n64lib" -version = "0.1.0" -authors = ["Jay Oster "] -edition = "2018" - -[dependencies] diff --git a/examples/n64lib/src/ipl3font.rs b/examples/n64lib/src/ipl3font.rs deleted file mode 100644 index 95c5c8f..0000000 --- a/examples/n64lib/src/ipl3font.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! IPL3 font renderer. -//! -//! Draws strings using the font embedded in the official Nintendo IPL3 bootcode. -//! See: - -use crate::vi; - -/// Glyph width (pixels or bits) -pub const WIDTH: usize = 13; - -/// Glyph height (pixels or bits) -pub const HEIGHT: usize = 14; - -/// Glyphs available in the embedded font -const GLYPHS: &[u8; 50] = br##"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#'*+,-./:=?@"##; - -/// Glyph index for rendering "unknown" characters -/// Unknown characters are rendered with the "?" glyph -const UNKNOWN: usize = 48; - -/// Glyph size (in bytes) -const GLYPH_SIZE: usize = 23; - -/// Location of font in ROM -/// This is only guaranteed to be accurate for the CIC-NUS-6102 IPL3. -const GLYPH_ADDR: usize = 0xB000_0B70; - -/// Font kerning (in pixels) -const KERNING: usize = 1; - -/// Draw a string using the embedded font, centered on the frame buffer. -/// Only supports a small subset of the ASCII character set. -pub fn draw_str_centered(color: u16, string: &str) { - let x = (vi::WIDTH - string.len() * WIDTH) / 2; - let y = (vi::HEIGHT - HEIGHT) / 2; - - draw_str(x, y, color, string); -} - -/// Draw a string using the embedded font. -/// Only supports a small subset of the ASCII character set. -pub fn draw_str(mut x: usize, mut y: usize, color: u16, string: &str) { - for mut ch in string.bytes() { - // Bail if we're trying to draw outside of the frame buffer - if y >= vi::HEIGHT { - return; - } - - // Special handling for space characters - if ch == b' ' { - x += WIDTH; - if x >= vi::WIDTH { - x = 0; - y += HEIGHT; - } - continue; - } - - // Special handling for lowercase letters - if (b'a'..=b'z').contains(&ch) { - ch -= b'a' - b'A'; - } - - draw_char(x, y, color, ch); - x += WIDTH + KERNING; - } -} - -/// Draw a character. -/// Only supports a small subset of the ASCII character set. -fn draw_char(x: usize, y: usize, color: u16, ch: u8) { - let frame_buffer = vi::next_buffer() as usize; - - let index = GLYPHS.iter().position(|c| *c == ch).unwrap_or(UNKNOWN); - - let mut address = GLYPH_ADDR + index * GLYPH_SIZE; - let mut shift = (4 - (address & 3)) * 8 - 1; - address &= 0xFFFF_FFFC; - let mut bits = unsafe { *(address as *const u32) }; - - for yy in y..y + HEIGHT { - // Bail if we're trying to draw outside of the frame buffer - if yy >= vi::HEIGHT { - return; - } - - for xx in x..x + WIDTH { - if (bits >> shift) & 1 == 1 && xx < vi::WIDTH { - // Put a pixel into the frame buffer - let offset = (yy * vi::WIDTH + xx) * 2; - let p = (frame_buffer + offset) as *mut u16; - - unsafe { - *p = color; - } - } - - // Advance to the next glyph pixel - if shift == 0 { - address += 4; - bits = unsafe { *(address as *const u32) }; - shift = 31; - } else { - shift -= 1; - } - } - } -} diff --git a/examples/n64lib/src/lib.rs b/examples/n64lib/src/lib.rs deleted file mode 100644 index b89d31a..0000000 --- a/examples/n64lib/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -#![no_std] - -pub mod ipl3font; -pub mod vi; diff --git a/examples/n64lib/src/vi.rs b/examples/n64lib/src/vi.rs deleted file mode 100644 index 4cdff3f..0000000 --- a/examples/n64lib/src/vi.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Video Interface -//! -//! Provides low level access to the N64 vi hardware. - -use core::ptr::read_volatile; - -// TODO: Heap allocate (needs std and global_allocator) -const FRAME_BUFFER: *mut u16 = 0xA010_0000 as *mut u16; - -pub const WIDTH: usize = 320; -pub const HEIGHT: usize = 240; -pub const FRAME_BUFFER_SIZE: usize = WIDTH * HEIGHT * 2; - -const VI_BASE: usize = 0xA440_0000; - -const VI_STATUS: *mut u32 = VI_BASE as *mut u32; -const VI_DRAM_ADDR: *mut usize = (VI_BASE + 0x04) as *mut usize; -const VI_H_WIDTH: *mut u32 = (VI_BASE + 0x08) as *mut u32; -const VI_V_INTR: *mut u32 = (VI_BASE + 0x0C) as *mut u32; -const VI_CURRENT: *const u32 = (VI_BASE + 0x10) as *const u32; -const VI_TIMING: *mut u32 = (VI_BASE + 0x14) as *mut u32; -const VI_V_SYNC: *mut u32 = (VI_BASE + 0x18) as *mut u32; -const VI_H_SYNC: *mut u32 = (VI_BASE + 0x1C) as *mut u32; -const VI_H_SYNC_LEAP: *mut u32 = (VI_BASE + 0x20) as *mut u32; -const VI_H_VIDEO: *mut u32 = (VI_BASE + 0x24) as *mut u32; -const VI_V_VIDEO: *mut u32 = (VI_BASE + 0x28) as *mut u32; -const VI_V_BURST: *mut u32 = (VI_BASE + 0x2C) as *mut u32; -const VI_X_SCALE: *mut u32 = (VI_BASE + 0x30) as *mut u32; -const VI_Y_SCALE: *mut u32 = (VI_BASE + 0x34) as *mut u32; - -const VIDEO_MODE: *const u32 = 0x8000_0300 as *const u32; - -pub enum VideoMode { - PAL, - NTSC, - MPAL, -} - -/// Video frequency in Hertz -pub fn get_video_frequency() -> u32 { - match get_video_mode() { - VideoMode::PAL => 49_656_530, - VideoMode::NTSC => 48_681_812, - VideoMode::MPAL => 48_628_316, - } -} - -/// Returns the current video mode -pub fn get_video_mode() -> VideoMode { - match unsafe { read_volatile(VIDEO_MODE) } { - 0 => VideoMode::PAL, - 1 => VideoMode::NTSC, - _ => VideoMode::MPAL, - } -} - -/// Busy-wait for VBlank -pub fn wait_for_ready() { - loop { - let current_halfline = unsafe { read_volatile(VI_CURRENT) }; - if current_halfline <= 10 { - break; - } - } -} - -/// Return a raw pointer to the back buffer -pub fn next_buffer() -> *mut u16 { - let current_fb = unsafe { read_volatile(VI_DRAM_ADDR) }; - - if current_fb & 0xFFFFF != 0 { - FRAME_BUFFER - } else { - (FRAME_BUFFER as usize + FRAME_BUFFER_SIZE) as *mut u16 - } -} - -/// Swap frame buffers (display the back buffer) -pub fn swap_buffer() { - unsafe { - *VI_DRAM_ADDR = next_buffer() as usize; - } -} - -/// Initialize Video Interface with 320x240x16 resolution and double buffering -pub fn init() { - // Clear both frame buffers to black, writing two pixels at a time - let frame_buffer = FRAME_BUFFER as usize; - for i in 0..WIDTH * HEIGHT { - let p = (frame_buffer + i * 4) as *mut u32; - unsafe { - *p = 0x0001_0001; - } - } - - // Initialize VI - unsafe { - *VI_STATUS = 0x0000_320E; - *VI_DRAM_ADDR = frame_buffer; - *VI_H_WIDTH = WIDTH as u32; - *VI_V_INTR = 2; - *VI_TIMING = 0x03E5_2239; - *VI_V_SYNC = 0x0000_020D; - *VI_H_SYNC = 0x0000_0C15; - *VI_H_SYNC_LEAP = 0x0C15_0C15; - *VI_H_VIDEO = 0x006C_02EC; - *VI_V_VIDEO = 0x0025_01FF; - *VI_V_BURST = 0x000E_0204; - *VI_X_SCALE = 0x0000_0200; - *VI_Y_SCALE = 0x0000_0400; - } -} diff --git a/rust-toolchain b/rust-toolchain index 05d4932..b618352 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2021-08-07 +nightly-2022-06-21 diff --git a/cargo-n64/src/bin/cargo-n64.rs b/src/bin/cargo-n64.rs similarity index 100% rename from cargo-n64/src/bin/cargo-n64.rs rename to src/bin/cargo-n64.rs diff --git a/cargo-n64/src/cargo.rs b/src/cargo.rs similarity index 100% rename from cargo-n64/src/cargo.rs rename to src/cargo.rs diff --git a/cargo-n64/src/cli.rs b/src/cli.rs similarity index 100% rename from cargo-n64/src/cli.rs rename to src/cli.rs diff --git a/cargo-n64/src/elf.rs b/src/elf.rs similarity index 100% rename from cargo-n64/src/elf.rs rename to src/elf.rs diff --git a/cargo-n64/src/fs.rs b/src/fs.rs similarity index 100% rename from cargo-n64/src/fs.rs rename to src/fs.rs diff --git a/cargo-n64/src/header.rs b/src/header.rs similarity index 100% rename from cargo-n64/src/header.rs rename to src/header.rs diff --git a/cargo-n64/src/ipl3.rs b/src/ipl3.rs similarity index 100% rename from cargo-n64/src/ipl3.rs rename to src/ipl3.rs diff --git a/cargo-n64/src/lib.rs b/src/lib.rs similarity index 100% rename from cargo-n64/src/lib.rs rename to src/lib.rs diff --git a/cargo-n64/src/templates/linker.ld b/src/templates/linker.ld similarity index 100% rename from cargo-n64/src/templates/linker.ld rename to src/templates/linker.ld diff --git a/cargo-n64/src/templates/mips-nintendo64-none.fmt b/src/templates/mips-nintendo64-none.fmt similarity index 100% rename from cargo-n64/src/templates/mips-nintendo64-none.fmt rename to src/templates/mips-nintendo64-none.fmt