commit 9c44fdd4f6d90c61ec8e195c73941eb5911eef5f Author: Jessie Williams Date: Sun Mar 6 21:10:49 2022 -0500 initial commit 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..f6098fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/* +dist/* +.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..85a6237 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,41 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "buddy-alloc" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff9f338986406db85e2b5deb40a9255b796ca03a194c7457403d215173f3fd5" + +[[package]] +name = "cart" +version = "0.1.0" +dependencies = [ + "buddy-alloc", + "fastrand", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6795f56 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[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 } +fastrand = "1.6.0" + +[profile.release] +opt-level = "z" +lto = true +panic = 'abort' +debug = true + +[features] +# use `--no-default-features` or comment out next line to disable allocator +default = ["buddy-alloc"] diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..e943764 --- /dev/null +++ b/build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +mkdir -p dist +rm -rf dist/* +cargo build --release +wasm-snip --snip-rust-fmt-code --snip-rust-panicking-code ./target/wasm32-unknown-unknown/release/cart.wasm -o ./dist/mara2_snip.wasm +wasm-opt -Oz --zero-filled-memory --strip-producers --dce ./dist/mara2_snip.wasm -o ./dist/mara2.wasm +wasm-strip ./dist/mara2.wasm diff --git a/sprites/Mai.png b/sprites/Mai.png new file mode 100644 index 0000000..cb61f9a Binary files /dev/null and b/sprites/Mai.png differ diff --git a/sprites/Mara.png b/sprites/Mara.png new file mode 100644 index 0000000..66efa9a Binary files /dev/null and b/sprites/Mara.png differ diff --git a/sprites/gate.png b/sprites/gate.png new file mode 100644 index 0000000..8279ffa Binary files /dev/null and b/sprites/gate.png differ diff --git a/sprites/palette.ase b/sprites/palette.ase new file mode 100644 index 0000000..c9be6aa Binary files /dev/null and b/sprites/palette.ase differ 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..1094f95 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,89 @@ +#[cfg(feature = "buddy-alloc")] +mod alloc; +mod sh0rk; +mod sprites; + +use fastrand::Rng; +use sh0rk::{palette, sys::*, Point, Direction::{self, *}}; + +static mut GAME: Game = Game::new(); + +struct Game { + frame_count: u32, + prev_gamepad: u8, + rng: Option, + mai_position: Point, + mara_position: Point, + mara_frame: u32, + mara_dir: Direction, +} + +impl Game { + const fn new() -> Self { + Self { + frame_count: 0, + prev_gamepad: 0, + rng: None, + mai_position: Point { x: 76, y: 48 }, + mara_position: Point { x: 0, y: 64 }, + mara_frame: 0, + mara_dir: Right, + } + } + + fn update(&mut self) { + self.frame_count += 1; + + palette::set_draw_color(2); + text("Mara: Sh0rk of\nJustice 2\npowered by:\nsh0rk engine v0", 10, 10); + palette::set_draw_color(3); + text("From Within, 2022", 10, 144); + + sprites::MAI.draw(self.mai_position, 0); + + if self.frame_count % 15 == 0 { + self.mara_frame = if self.mara_frame == 1 { 0 } else { 1 }; + } + + if self.frame_count % 4 == 0 { + if self.mara_dir == Right && self.mara_position.x >= 144 { + self.mara_dir = Down; + } + if self.mara_dir == Down && self.mara_position.y >= 120 { + self.mara_dir = Left; + } + if self.mara_dir == Left && self.mara_position.x <= 0 { + self.mara_dir = Up; + } + if self.mara_dir == Up && self.mara_position.y <= 64 { + self.mara_dir = Right; + } + + const SPEED: i32 = 2; + match self.mara_dir { + Left => self.mara_position.x -= SPEED, + Right => self.mara_position.x += SPEED, + Up => self.mara_position.y -= SPEED, + Down => self.mara_position.y += SPEED, + }; + } + + sprites::MARA.draw( + self.mara_dir, + self.mara_frame, + self.mara_position, + 0, + ); + } +} + +#[no_mangle] +unsafe fn start() { + palette::en4(); + GAME.rng = Some(Rng::with_seed(420)); +} + +#[no_mangle] +unsafe fn update() { + GAME.update(); +} diff --git a/src/sh0rk/mod.rs b/src/sh0rk/mod.rs new file mode 100644 index 0000000..d42fe24 --- /dev/null +++ b/src/sh0rk/mod.rs @@ -0,0 +1,91 @@ +pub mod palette; +pub mod sys; + +#[derive(Clone, Copy, PartialEq, Eq, Default)] +pub struct Point { + pub x: i32, + pub y: i32, +} + +impl From<(i32, i32)> for Point { + fn from((x, y): (i32, i32)) -> Self { + Point { x, y } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Left, + Right, + Up, + Down, +} +use Direction::*; + +pub struct SpriteAtlas { + pub atlas: Sprite, + pub width: u32, + pub height: u32, + pub animated: bool, +} + +impl SpriteAtlas { + pub fn draw(&self, dir: Direction, step: u32, p: Point, flags: u32) { + let flags = flags + | if dir == Left { + sys::BLIT_FLIP_X + } else { + 0 + }; + + let frame: u32 = if self.animated { + match dir { + Left => 4, + Right => 4, + Up => 2, + Down => 0, + } + } else { + match dir { + Up => 1, + Down => 0, + _ => 2, + } + } + step; + + palette::set_draw_color(self.atlas.palette); + sys::blit_sub( + &self.atlas.sprite, + p.x, + p.y, + self.width, + self.height, + self.width * frame, + 0, + self.atlas.width, + self.atlas.flags | flags, + ); + } +} + +pub struct Sprite { + pub palette: u16, + pub width: u32, + pub height: u32, + pub flags: u32, + pub sprite: [u8; N], +} + +impl Sprite { + pub fn draw(&self, p: Point, flags: u32) { + palette::set_draw_color(self.palette); + sys::blit( + &self.sprite, + p.x, + p.y, + self.width, + self.height, + self.flags | flags, + ); + } +} diff --git a/src/sh0rk/palette.rs b/src/sh0rk/palette.rs new file mode 100644 index 0000000..d98f94f --- /dev/null +++ b/src/sh0rk/palette.rs @@ -0,0 +1,31 @@ +use super::sys; + +pub fn set_draw_color(idx: u16) { + unsafe { *sys::DRAW_COLORS = idx.into() } +} + +pub fn set_palette(palette: [u32; 4]) { + unsafe { + *sys::PALETTE = palette; + } +} + +pub fn moonlight() { + set_palette([0xf3eaab, 0x86a0b7, 0x3d476a, 0x19152a]); +} + +pub fn en4() { + set_palette([0xfbf7f3, 0xe5b083, 0x426e5d, 0x20283d]); +} + +pub fn amanita() { + set_palette([0xf1eee3, 0xccc2b8, 0xb34750, 0x4d0f40]); +} + +pub fn cafe_nouveau() { + set_palette([0xf8e6d0, 0xc08e70, 0x683a34, 0x200816]); +} + +/*pub fn coldfire() { + set_palette([0x46425e, 0x5b768d, 0xd17c7c, 0xf6c6a8]); +}*/ diff --git a/src/sh0rk/sys.rs b/src/sh0rk/sys.rs new file mode 100644 index 0000000..c3dde0d --- /dev/null +++ b/src/sh0rk/sys.rs @@ -0,0 +1,222 @@ +// +// 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 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; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ 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); +} diff --git a/src/sprites.rs b/src/sprites.rs new file mode 100644 index 0000000..7ad9506 --- /dev/null +++ b/src/sprites.rs @@ -0,0 +1,65 @@ +use crate::sh0rk::{Sprite, SpriteAtlas}; + +pub const GATE: Sprite<12> = Sprite { + palette: 0x1320, + width: 12, + height: 14, + flags: 1, + sprite: [ 0x3d,0x5a,0xa7,0xf5,0xaa,0x57,0xd6,0xa5,0x6b,0x1f,0xff,0xfc ], +}; + +pub const MAI: Sprite<64> = Sprite { + palette: 0x1320, + width: 16, + height: 16, + flags: 1, + sprite: [ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xdf, 0xf5, 0xff, 0xff, + 0x5f, 0xf6, 0x75, 0x9d, 0x9f, 0xf6, 0x59, 0x55, 0x9f, 0xf6, 0x95, 0x96, 0x9f, 0xfd, 0x41, + 0x41, 0x7f, 0xfe, 0x49, 0x49, 0x7f, 0xfd, 0x49, 0x49, 0xbf, 0xfe, 0x55, 0x55, 0x7f, 0xff, + 0x59, 0x66, 0xff, 0xff, 0xd6, 0x97, 0xff, 0xff, 0xf5, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + ], +}; + +pub const MARA: SpriteAtlas<384> = SpriteAtlas { + atlas: Sprite { + palette: 0x3210, + width: 96, + height: 16, + flags: 1, + sprite: [ + 0x3f, 0x00, 0x00, 0xfc, 0x3f, 0x00, 0x00, 0xfc, 0x3f, 0x00, 0x00, 0xfc, 0x3f, 0x00, + 0x00, 0xfc, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x36, 0xff, 0xc3, 0x9c, + 0x36, 0xff, 0xc3, 0x9c, 0x3a, 0xc3, 0xff, 0xac, 0x3a, 0xc3, 0xff, 0xac, 0x00, 0x00, + 0xec, 0x00, 0x00, 0x00, 0xec, 0x00, 0x35, 0xd6, 0xfe, 0x5c, 0x35, 0xd6, 0xfe, 0x5c, + 0x3a, 0xbf, 0xab, 0xac, 0x3a, 0xbf, 0xab, 0xac, 0x30, 0x00, 0xeb, 0x00, 0x30, 0x00, + 0xeb, 0x00, 0x3a, 0xdb, 0x56, 0xac, 0x3a, 0xdb, 0x56, 0xac, 0x3a, 0xe6, 0x66, 0xec, + 0x3a, 0xe6, 0x66, 0xec, 0xdc, 0x03, 0xe9, 0xf0, 0xdc, 0x03, 0xe9, 0xf0, 0x0f, 0x6d, + 0x5e, 0xf0, 0x0f, 0x6d, 0x5e, 0xf0, 0x0f, 0xb9, 0x9d, 0xf0, 0x0f, 0xb9, 0x9d, 0xf0, + 0xe7, 0x0e, 0x65, 0x5c, 0xe7, 0x0e, 0x65, 0x5c, 0x03, 0xbd, 0x5e, 0xc0, 0x03, 0xbd, + 0x5e, 0xc0, 0x03, 0xde, 0x77, 0xc0, 0x03, 0xde, 0x77, 0xc0, 0xe7, 0x0d, 0xa7, 0x5c, + 0xe7, 0x0d, 0xa7, 0x5c, 0x03, 0xf7, 0xd6, 0xc0, 0x03, 0xf7, 0xd6, 0xc0, 0x03, 0xe7, + 0xdb, 0xc0, 0x03, 0xe7, 0xdb, 0xc0, 0xe9, 0xcd, 0xa7, 0x5c, 0xe9, 0xcd, 0xa7, 0x5c, + 0x00, 0xe5, 0x5b, 0x00, 0x00, 0xe5, 0x5b, 0x00, 0x00, 0xe9, 0x6b, 0x00, 0x00, 0xe9, + 0x6b, 0x00, 0x3a, 0x7d, 0xa5, 0x7c, 0x3a, 0x7d, 0xa5, 0x7c, 0x03, 0xff, 0xff, 0xc0, + 0x03, 0xff, 0xff, 0xc0, 0x03, 0xfa, 0xaf, 0xc0, 0x03, 0xfa, 0xaf, 0xc0, 0x3a, 0xc3, + 0xe9, 0xf0, 0x3a, 0xc3, 0xe9, 0xf0, 0x0d, 0xaa, 0xaa, 0x70, 0x0d, 0xaa, 0xaa, 0x70, + 0x0d, 0xae, 0xba, 0x70, 0x0d, 0xae, 0xba, 0x70, 0x3b, 0x00, 0xff, 0xc0, 0x3b, 0x00, + 0xff, 0xc0, 0x0d, 0xea, 0xab, 0x70, 0x0d, 0xea, 0xab, 0x70, 0x0d, 0xee, 0xbb, 0x70, + 0x0d, 0xee, 0xbb, 0x70, 0xeb, 0x03, 0xb7, 0xb0, 0xeb, 0x03, 0xb7, 0xb0, 0x0d, 0xea, + 0xab, 0x70, 0x0d, 0xea, 0xab, 0x70, 0x0d, 0xee, 0xbb, 0x70, 0x0d, 0xee, 0xbb, 0x70, + 0xea, 0xff, 0xde, 0xb0, 0xea, 0xff, 0xad, 0xf0, 0x00, 0x37, 0xdc, 0xc0, 0x03, 0x37, + 0xdc, 0xc0, 0x03, 0x3e, 0xbc, 0xc0, 0x03, 0x3e, 0xbc, 0xc0, 0x3a, 0xaa, 0xfa, 0xb0, + 0x3a, 0xaa, 0xab, 0xb0, 0x00, 0x37, 0xdc, 0x00, 0x00, 0x37, 0xdc, 0x00, 0x00, 0x3f, + 0xfc, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x3a, 0xaf, 0xd5, 0xf0, 0x3a, 0xaf, 0xd5, 0xf0, + 0x00, 0x37, 0x30, 0x00, 0x00, 0x0c, 0xdc, 0x00, 0x00, 0x37, 0x30, 0x00, 0x00, 0x0c, + 0xdc, 0x00, 0x0f, 0xf0, 0x37, 0x00, 0x0f, 0xf0, 0x37, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x03, + 0x8c, 0xb0, 0x00, 0x00, 0xee, 0xc0, + ], + }, + height: 16, + width: 16, + animated: true, +};