diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..2c694b5 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,15 @@ +[build] +target = "wasm32-unknown-unknown" + +[target.wasm32-unknown-unknown] +rustflags = [ + # Import memory from WASM-4 + "-C", "link-arg=--import-memory", + "-C", "link-arg=--initial-memory=65536", + "-C", "link-arg=--max-memory=65536", + + # Temporary workaround for #255 issue. + # Reserve 8192 bytes of Rust stack space, offset from 6560. + # Bump this value, 16-byte aligned, if the framebuffer gets corrupted. + "-C", "link-arg=-zstack-size=14752", +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d7837fe --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "buddy-alloc" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3240a4cb09cf0da6a51641bd40ce90e96ea6065e3a1adc46434029254bcc2d09" + +[[package]] +name = "cart" +version = "0.1.0" +dependencies = [ + "buddy-alloc", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9e57f16 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "cart" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib"] + +[dependencies] +buddy-alloc = { version = "0.4.1", optional = true } + +[profile.release] +opt-level = "z" +lto = true + +[features] +# use `--no-default-features` or comment out next line to disable allocator +default = ["buddy-alloc"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..f429c8e --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# ld53 + +A game written in Rust for the [WASM-4](https://wasm4.org) fantasy console. + +## Building + +Build the cart by running: + +```shell +cargo build --release +``` + +Then run it with: + +```shell +w4 run target/wasm32-unknown-unknown/release/cart.wasm +``` + +For more info about setting up WASM-4, see the [quickstart guide](https://wasm4.org/docs/getting-started/setup?code-lang=rust#quickstart). + +## Links + +- [Documentation](https://wasm4.org/docs): Learn more about WASM-4. +- [Snake Tutorial](https://wasm4.org/docs/tutorials/snake/goal): Learn how to build a complete game + with a step-by-step tutorial. +- [GitHub](https://github.com/aduros/wasm4): Submit an issue or PR. Contributions are welcome! diff --git a/src/alloc.rs b/src/alloc.rs new file mode 100644 index 0000000..92d488e --- /dev/null +++ b/src/alloc.rs @@ -0,0 +1,16 @@ +use buddy_alloc::{BuddyAllocParam, FastAllocParam, NonThreadsafeAlloc}; + +// These values can be tuned +const FAST_HEAP_SIZE: usize = 4 * 1024; // 4 KB +const HEAP_SIZE: usize = 16 * 1024; // 16 KB +const LEAF_SIZE: usize = 16; + +static mut FAST_HEAP: [u8; FAST_HEAP_SIZE] = [0u8; FAST_HEAP_SIZE]; +static mut HEAP: [u8; HEAP_SIZE] = [0u8; HEAP_SIZE]; + +#[global_allocator] +static ALLOC: NonThreadsafeAlloc = unsafe { + let fast_param = FastAllocParam::new(FAST_HEAP.as_ptr(), FAST_HEAP_SIZE); + let buddy_param = BuddyAllocParam::new(HEAP.as_ptr(), HEAP_SIZE, LEAF_SIZE); + NonThreadsafeAlloc::new(fast_param, buddy_param) +}; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d1c7f30 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,30 @@ +#[cfg(feature = "buddy-alloc")] +mod alloc; +mod wasm4; +use wasm4::*; + +#[rustfmt::skip] +const SMILEY: [u8; 8] = [ + 0b11000011, + 0b10000001, + 0b00100100, + 0b00100100, + 0b00000000, + 0b00100100, + 0b10011001, + 0b11000011, +]; + +#[no_mangle] +fn update() { + unsafe { *DRAW_COLORS = 2 } + text("Hello from Rust!", 10, 10); + + let gamepad = unsafe { *GAMEPAD1 }; + if gamepad & BUTTON_1 != 0 { + unsafe { *DRAW_COLORS = 4 } + } + + blit(&SMILEY, 76, 76, 8, 8, BLIT_1BPP); + text("Press X to blink", 16, 90); +} diff --git a/src/wasm4.rs b/src/wasm4.rs new file mode 100644 index 0000000..78503c9 --- /dev/null +++ b/src/wasm4.rs @@ -0,0 +1,225 @@ +// +// WASM-4: https://wasm4.org/docs + +#![allow(unused)] + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Platform Constants │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +pub const SCREEN_SIZE: u32 = 160; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Memory Addresses │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +pub const PALETTE: *mut [u32; 4] = 0x04 as *mut [u32; 4]; +pub const DRAW_COLORS: *mut u16 = 0x14 as *mut u16; +pub const GAMEPAD1: *const u8 = 0x16 as *const u8; +pub const GAMEPAD2: *const u8 = 0x17 as *const u8; +pub const GAMEPAD3: *const u8 = 0x18 as *const u8; +pub const GAMEPAD4: *const u8 = 0x19 as *const u8; +pub const MOUSE_X: *const i16 = 0x1a as *const i16; +pub const MOUSE_Y: *const i16 = 0x1c as *const i16; +pub const MOUSE_BUTTONS: *const u8 = 0x1e as *const u8; +pub const SYSTEM_FLAGS: *mut u8 = 0x1f as *mut u8; +pub const NETPLAY: *const u8 = 0x20 as *const u8; +pub const FRAMEBUFFER: *mut [u8; 6400] = 0xa0 as *mut [u8; 6400]; + +pub const BUTTON_1: u8 = 1; +pub const BUTTON_2: u8 = 2; +pub const BUTTON_LEFT: u8 = 16; +pub const BUTTON_RIGHT: u8 = 32; +pub const BUTTON_UP: u8 = 64; +pub const BUTTON_DOWN: u8 = 128; + +pub const MOUSE_LEFT: u8 = 1; +pub const MOUSE_RIGHT: u8 = 2; +pub const MOUSE_MIDDLE: u8 = 4; + +pub const SYSTEM_PRESERVE_FRAMEBUFFER: u8 = 1; +pub const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Drawing Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Copies pixels to the framebuffer. +pub fn blit(sprite: &[u8], x: i32, y: i32, width: u32, height: u32, flags: u32) { + unsafe { extern_blit(sprite.as_ptr(), x, y, width, height, flags) } +} +extern "C" { + #[link_name = "blit"] + fn extern_blit(sprite: *const u8, x: i32, y: i32, width: u32, height: u32, flags: u32); +} + +/// Copies a subregion within a larger sprite atlas to the framebuffer. +#[allow(clippy::too_many_arguments)] +pub fn blit_sub( + sprite: &[u8], + x: i32, + y: i32, + width: u32, + height: u32, + src_x: u32, + src_y: u32, + stride: u32, + flags: u32, +) { + unsafe { + extern_blit_sub( + sprite.as_ptr(), + x, + y, + width, + height, + src_x, + src_y, + stride, + flags, + ) + } +} +extern "C" { + #[link_name = "blitSub"] + fn extern_blit_sub( + sprite: *const u8, + x: i32, + y: i32, + width: u32, + height: u32, + src_x: u32, + src_y: u32, + stride: u32, + flags: u32, + ); +} + +pub const BLIT_2BPP: u32 = 1; +pub const BLIT_1BPP: u32 = 0; +pub const BLIT_FLIP_X: u32 = 2; +pub const BLIT_FLIP_Y: u32 = 4; +pub const BLIT_ROTATE: u32 = 8; + +/// Draws a line between two points. +pub fn line(x1: i32, y1: i32, x2: i32, y2: i32) { + unsafe { extern_line(x1, y1, x2, y2) } +} +extern "C" { + #[link_name = "line"] + fn extern_line(x1: i32, y1: i32, x2: i32, y2: i32); +} + +/// Draws an oval (or circle). +pub fn oval(x: i32, y: i32, width: u32, height: u32) { + unsafe { extern_oval(x, y, width, height) } +} +extern "C" { + #[link_name = "oval"] + fn extern_oval(x: i32, y: i32, width: u32, height: u32); +} + +/// Draws a rectangle. +pub fn rect(x: i32, y: i32, width: u32, height: u32) { + unsafe { extern_rect(x, y, width, height) } +} +extern "C" { + #[link_name = "rect"] + fn extern_rect(x: i32, y: i32, width: u32, height: u32); +} + +/// Draws text using the built-in system font. +pub fn text>(text: T, x: i32, y: i32) { + let text_ref = text.as_ref(); + unsafe { extern_text(text_ref.as_ptr(), text_ref.len(), x, y) } +} +extern "C" { + #[link_name = "textUtf8"] + fn extern_text(text: *const u8, length: usize, x: i32, y: i32); +} + +/// Draws a vertical line +pub fn vline(x: i32, y: i32, len: u32) { + unsafe { + extern_vline(x, y, len); + } +} + +extern "C" { + #[link_name = "vline"] + fn extern_vline(x: i32, y: i32, len: u32); +} + +/// Draws a horizontal line +pub fn hline(x: i32, y: i32, len: u32) { + unsafe { + extern_hline(x, y, len); + } +} + +extern "C" { + #[link_name = "hline"] + fn extern_hline(x: i32, y: i32, len: u32); +} + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Sound Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Plays a sound tone. +pub fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) { + unsafe { extern_tone(frequency, duration, volume, flags) } +} +extern "C" { + #[link_name = "tone"] + fn extern_tone(frequency: u32, duration: u32, volume: u32, flags: u32); +} + +pub const TONE_PULSE1: u32 = 0; +pub const TONE_PULSE2: u32 = 1; +pub const TONE_TRIANGLE: u32 = 2; +pub const TONE_NOISE: u32 = 3; +pub const TONE_MODE1: u32 = 0; +pub const TONE_MODE2: u32 = 4; +pub const TONE_MODE3: u32 = 8; +pub const TONE_MODE4: u32 = 12; +pub const TONE_PAN_LEFT: u32 = 16; +pub const TONE_PAN_RIGHT: u32 = 32; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Storage Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +extern "C" { + /// Reads up to `size` bytes from persistent storage into the pointer `dest`. + pub fn diskr(dest: *mut u8, size: u32) -> u32; + + /// Writes up to `size` bytes from the pointer `src` into persistent storage. + pub fn diskw(src: *const u8, size: u32) -> u32; +} + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Other Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Prints a message to the debug console. +pub fn trace>(text: T) { + let text_ref = text.as_ref(); + unsafe { extern_trace(text_ref.as_ptr(), text_ref.len()) } +} +extern "C" { + #[link_name = "traceUtf8"] + fn extern_trace(trace: *const u8, length: usize); +}