Compare commits
No commits in common. "font-0.11" and "gamer" have entirely different histories.
9 changed files with 217 additions and 263 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -1,6 +1,6 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitfrob"
|
name = "bitfrob"
|
||||||
|
@ -10,18 +10,25 @@ checksum = "5a96c7c818dc8807bb1982dd2cba4c7de0ed6eba4ffb5fc24321d1b38676a120"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bracer"
|
name = "bracer"
|
||||||
version = "0.1.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6559b8c3065745016f5cc2d1095273fe8a175e953c976426947ad828d6ba6fda"
|
checksum = "00248d542917c4ef013367c0907300e9befbbc1f99b12938c9e5a356ab50582d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gba"
|
name = "gba"
|
||||||
version = "0.11.3"
|
version = "0.14.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ab90d1de12c1323d80d9850c55d68931512ef7f0462a8c79e92b726f5037ad6"
|
checksum = "dff72a04df599de9991069ab6807f00e02dc24840e13d16f92dfa2929c4e6950"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitfrob",
|
"bitfrob",
|
||||||
"bracer",
|
"bracer",
|
||||||
|
"bytemuck",
|
||||||
"voladdress",
|
"voladdress",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -1,9 +1,13 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gba-template"
|
name = "gba-template"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gba = "0.11.3"
|
gba = "0.14.1"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = "s"
|
||||||
|
|
30
make-rom.sh
Executable file
30
make-rom.sh
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# terrible
|
||||||
|
eval $(grep '^name *=' Cargo.toml | head -n1 | sed 's/ //g')
|
||||||
|
eval $(grep '^target *=' .cargo/config.toml | head -n1 | sed 's/ //g')
|
||||||
|
|
||||||
|
# terribler
|
||||||
|
export RUSTC_BOOTSTRAP=1
|
||||||
|
|
||||||
|
next_arg=false
|
||||||
|
for arg in "$@"; do
|
||||||
|
if $next_arg; then
|
||||||
|
target="$arg"
|
||||||
|
next_arg=false
|
||||||
|
elif [ "$arg" == "--target" ]; then
|
||||||
|
next_arg=true
|
||||||
|
elif [[ "$arg" =~ ^--target= ]]; then
|
||||||
|
target="${arg/--target=/}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
set -exo pipefail
|
||||||
|
|
||||||
|
cargo -Z build-std build --release "$@"
|
||||||
|
|
||||||
|
arm-none-eabi-objcopy -O binary "target/${target}/release/$name" "target/${name}.gba"
|
||||||
|
|
||||||
|
which gbafix || cargo install gbafix
|
||||||
|
|
||||||
|
gbafix "target/${name}.gba"
|
7
run-release.sh
Executable file
7
run-release.sh
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# terrible
|
||||||
|
export RUSTC_BOOTSTRAP=1
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
exec cargo -Z build-std run --release "$@"
|
136
src/halfwidth.rs
136
src/halfwidth.rs
|
@ -1,136 +0,0 @@
|
||||||
use core::fmt::Write;
|
|
||||||
use gba::prelude::*;
|
|
||||||
|
|
||||||
const FONT_WIDTH: usize = 4;
|
|
||||||
const FONT_HEIGHT: usize = 6;
|
|
||||||
const TAB_CHARS: usize = 4;
|
|
||||||
|
|
||||||
const MAP_WIDTH: usize = 32;
|
|
||||||
const MAP_HEIGHT: usize = 32;
|
|
||||||
const WRAP_COL: usize = 60;
|
|
||||||
|
|
||||||
// 96 printable ascii chars, each using half of a 8x8 4bpp tile
|
|
||||||
#[link_section = ".ewram"]
|
|
||||||
static LIFONT: [u8; 96 * 16] = *include_bytes!("lifont-3x5.4bpp");
|
|
||||||
|
|
||||||
type DoubleHalfTile = [u16; 2 * 8];
|
|
||||||
|
|
||||||
fn font() -> &'static [DoubleHalfTile] {
|
|
||||||
unsafe { core::slice::from_raw_parts(LIFONT.as_ptr() as *const DoubleHalfTile, 96 / 2) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn charblock_write(tile_index: usize, tile_row_index: usize, value: u16) {
|
|
||||||
unsafe {
|
|
||||||
let tile_ptr = (CHARBLOCK0_4BPP.as_ptr() as *mut DoubleHalfTile).add(tile_index);
|
|
||||||
(*tile_ptr)
|
|
||||||
.as_mut_ptr()
|
|
||||||
.add(tile_row_index)
|
|
||||||
.write_volatile(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: support for drawing to a region of the charblock/screenblock
|
|
||||||
// (i.e. not necessarily taking over the whole display)
|
|
||||||
pub struct TextPainter {
|
|
||||||
pixel_row: usize,
|
|
||||||
pixel_col: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::fmt::Write for TextPainter {
|
|
||||||
fn write_str(&mut self, text: &str) -> core::fmt::Result {
|
|
||||||
for c in text
|
|
||||||
.chars()
|
|
||||||
.map(|c| c.as_ascii().map(|a| a.to_u8()).unwrap_or(0x7f) as usize)
|
|
||||||
{
|
|
||||||
if self.pixel_col >= FONT_WIDTH * WRAP_COL {
|
|
||||||
self.pixel_row += FONT_HEIGHT;
|
|
||||||
self.pixel_col = 0;
|
|
||||||
}
|
|
||||||
if (self.pixel_row >> 3) > MAP_HEIGHT {
|
|
||||||
return Err(core::fmt::Error::default());
|
|
||||||
}
|
|
||||||
if c < 0x20 {
|
|
||||||
match c as u8 {
|
|
||||||
// '\b', backspace
|
|
||||||
8 => {
|
|
||||||
self.pixel_col = self.pixel_col.saturating_sub(FONT_WIDTH);
|
|
||||||
}
|
|
||||||
b'\t' => {
|
|
||||||
self.pixel_col =
|
|
||||||
(self.pixel_col + 1).next_multiple_of(TAB_CHARS * FONT_WIDTH);
|
|
||||||
}
|
|
||||||
b'\n' => {
|
|
||||||
self.pixel_row += FONT_HEIGHT;
|
|
||||||
self.pixel_col = 0; // assume cooked
|
|
||||||
}
|
|
||||||
b'\r' => {
|
|
||||||
self.pixel_col = 0;
|
|
||||||
}
|
|
||||||
_ => {} // TODO?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let font_tile = &font()[(c - 0x20) >> 1];
|
|
||||||
let out_tile_col = self.pixel_col >> 3;
|
|
||||||
let mut out_pixel_row = self.pixel_row;
|
|
||||||
for font_row in font_tile
|
|
||||||
.iter()
|
|
||||||
.skip(c & 1)
|
|
||||||
.step_by(2)
|
|
||||||
.take(FONT_HEIGHT)
|
|
||||||
.copied()
|
|
||||||
{
|
|
||||||
let out_tile_row = out_pixel_row >> 3;
|
|
||||||
let out_tile_pixel_row =
|
|
||||||
((out_pixel_row & 7) << 1) + ((self.pixel_col & 4) >> 2);
|
|
||||||
let idx = out_tile_row * MAP_WIDTH + out_tile_col;
|
|
||||||
charblock_write(idx, out_tile_pixel_row, font_row);
|
|
||||||
out_pixel_row += 1;
|
|
||||||
}
|
|
||||||
self.pixel_col += FONT_WIDTH;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextPainter {
|
|
||||||
pub const fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
pixel_row: 0,
|
|
||||||
pixel_col: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup_display(&mut self) {
|
|
||||||
self.pixel_row = 0;
|
|
||||||
self.pixel_col = 0;
|
|
||||||
let dispcnt = DisplayControl::new().with_show_bg0(true);
|
|
||||||
DISPCNT.write(dispcnt);
|
|
||||||
let bg0cnt = BackgroundControl::new()
|
|
||||||
.with_size(0)
|
|
||||||
.with_charblock(0)
|
|
||||||
.with_screenblock(16);
|
|
||||||
BG0CNT.write(bg0cnt);
|
|
||||||
BG0HOFS.write(0);
|
|
||||||
BG0VOFS.write(0);
|
|
||||||
let screenblock = TEXT_SCREENBLOCKS.get_frame(16).unwrap();
|
|
||||||
let mut x = 0;
|
|
||||||
for r in 0..32 {
|
|
||||||
let row = screenblock.get_row(r).unwrap();
|
|
||||||
for cell in row.iter() {
|
|
||||||
cell.write(TextEntry::new().with_tile(x));
|
|
||||||
x += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for tile in CHARBLOCK0_4BPP.iter() {
|
|
||||||
tile.write([0; 8]);
|
|
||||||
}
|
|
||||||
BG_PALETTE
|
|
||||||
.index(1)
|
|
||||||
.write(Color::new().with_red(22).with_green(16).with_blue(16));
|
|
||||||
BG_PALETTE
|
|
||||||
.index(2)
|
|
||||||
.write(Color::new().with_red(31).with_green(31).with_blue(31));
|
|
||||||
BG_PALETTE.index(0).write(Color::new().with_red(8));
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
155
src/main.rs
155
src/main.rs
|
@ -1,137 +1,64 @@
|
||||||
// from https://github.com/rust-console/gba/blob/main/examples/hello.rs
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![feature(ascii_char)]
|
#![cfg_attr(test, feature(custom_test_frameworks))]
|
||||||
#![feature(panic_info_message)]
|
#![cfg_attr(test, test_runner(test_harness::test_runner))]
|
||||||
// rustc is simply wrong about core::fmt::Write being unused,
|
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
|
||||||
// it's used in the panic handler.
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
|
|
||||||
mod halfwidth;
|
#[cfg(test)]
|
||||||
|
mod test_harness;
|
||||||
use core::fmt::Write;
|
|
||||||
|
|
||||||
|
use core::panic::PanicInfo;
|
||||||
use gba::prelude::*;
|
use gba::prelude::*;
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
#[panic_handler]
|
#[cfg_attr(not(test), unsafe(no_mangle))]
|
||||||
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
|
#[instruction_set(arm::t32)]
|
||||||
let mut text_painter = halfwidth::TextPainter::new();
|
|
||||||
text_painter.setup_display();
|
|
||||||
|
|
||||||
text_painter.write_str("panic at ");
|
|
||||||
if let Some(location) = info.location() {
|
|
||||||
write!(&mut text_painter, "{}:{}", location.file(), location.line());
|
|
||||||
} else {
|
|
||||||
text_painter.write_str("unknown location");
|
|
||||||
};
|
|
||||||
if let Some(msg) = info.message() {
|
|
||||||
write!(&mut text_painter, ":\n");
|
|
||||||
core::fmt::write(&mut text_painter, *msg);
|
|
||||||
}
|
|
||||||
if let Some(s) = info.payload().downcast_ref::<&str>() {
|
|
||||||
text_painter.write_str("\n");
|
|
||||||
text_painter.write_str(s);
|
|
||||||
}
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
if let Ok(mut logger) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Fatal) {
|
|
||||||
for _ in 0..4 {
|
|
||||||
VBlankIntrWait();
|
|
||||||
}
|
|
||||||
if let Some(args) = info.message() {
|
|
||||||
logger.write_fmt(*args);
|
|
||||||
} else {
|
|
||||||
writeln!(logger, "{info}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
VBlankIntrWait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[link_section = ".ewram"]
|
|
||||||
static FRAME_KEYS: GbaCell<KeyInput> = GbaCell::new(KeyInput::new());
|
|
||||||
|
|
||||||
#[link_section = ".iwram"]
|
|
||||||
extern "C" fn irq_handler(_: IrqBits) {
|
|
||||||
// We'll read the keys during vblank and store it for later.
|
|
||||||
FRAME_KEYS.write(KEYINPUT.read());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
extern "C" fn main() -> ! {
|
extern "C" fn main() -> ! {
|
||||||
RUST_IRQ_HANDLER.write(Some(irq_handler));
|
BG_PALETTE.index(0).write(Color(0x7F2B)); // blue
|
||||||
DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true));
|
BG_PALETTE.index(1).write(Color(0x5EBE)); // pink
|
||||||
IE.write(IrqBits::VBLANK);
|
BG_PALETTE.index(2).write(Color(0x7FFF)); // white
|
||||||
IME.write(true);
|
|
||||||
|
|
||||||
if let Ok(mut logger) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Debug) {
|
// rotate and streeetch
|
||||||
writeln!(logger, "hello!").ok();
|
BG2PA.write(i16fx8::from_bits(0));
|
||||||
|
BG2PB.write(i16fx8::from_bits(8));
|
||||||
|
BG2PC.write(i16fx8::from_bits(1));
|
||||||
|
BG2PD.write(i16fx8::from_bits(0));
|
||||||
|
|
||||||
let fx_u: Fixed<u32, 8> = Fixed::<u32, 8>::wrapping_from(7) + Fixed::<u32, 8>::from_raw(12);
|
// load-bearing low-order 00 byte (it's a blue pixel and a tile ID)
|
||||||
writeln!(logger, "fixed unsigned: {fx_u:?}").ok();
|
unsafe { CHARBLOCK0_8BPP.index(0).cast() }.write(0x01020100);
|
||||||
|
|
||||||
let fx_i1: Fixed<i32, 8> =
|
DISPCNT.write(
|
||||||
Fixed::<i32, 8>::wrapping_from(8) + Fixed::<i32, 8>::from_raw(15);
|
DisplayControl::new()
|
||||||
writeln!(logger, "fixed signed positive: {fx_i1:?}").ok();
|
.with_video_mode(VideoMode::_2)
|
||||||
|
.with_show_bg2(true),
|
||||||
|
);
|
||||||
|
|
||||||
let fx_i2: Fixed<i32, 8> = Fixed::<i32, 8>::wrapping_from(0)
|
// waits forever, as we haven't enabled interrupts
|
||||||
- Fixed::<i32, 8>::wrapping_from(3)
|
VBlankIntrWait();
|
||||||
- Fixed::<i32, 8>::from_raw(17);
|
unreachable!();
|
||||||
writeln!(logger, "fixed signed negative: {fx_i2:?}").ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
#[cfg_attr(not(test), panic_handler)]
|
||||||
// get our tile data into memory.
|
#[cfg_attr(test, allow(unused))]
|
||||||
Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0);
|
fn panic_handler(panic_info: &PanicInfo) -> ! {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if let Ok(mut log) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Fatal) {
|
||||||
|
use core::fmt::Write;
|
||||||
|
if let Some(loc) = panic_info.location() {
|
||||||
|
log.write_fmt(format_args!("{loc}: ")).ok();
|
||||||
}
|
}
|
||||||
|
log.write_fmt(format_args!("{}", panic_info.message())).ok();
|
||||||
{
|
|
||||||
// set up the tilemap
|
|
||||||
let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap();
|
|
||||||
for y in 0..16 {
|
|
||||||
let row = tsb.get_row(y).unwrap();
|
|
||||||
for (x, addr) in row.iter().enumerate().take(16) {
|
|
||||||
let te = TextEntry::new().with_tile((y * 16 + x) as u16);
|
|
||||||
addr.write(te);
|
|
||||||
}
|
}
|
||||||
}
|
let _ = panic_info;
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Set BG0 to use the tilemap we just made, and set it to be shown.
|
|
||||||
BG0CNT.write(BackgroundControl::new().with_screenblock(31));
|
|
||||||
DISPCNT.write(DisplayControl::new().with_show_bg0(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut x_off = 0_u32;
|
|
||||||
let mut y_off = 0_u32;
|
|
||||||
let mut backdrop_color = Color(0);
|
|
||||||
loop {
|
loop {
|
||||||
VBlankIntrWait();
|
VBlankIntrWait();
|
||||||
// show current frame
|
|
||||||
BACKDROP_COLOR.write(backdrop_color);
|
|
||||||
BG0HOFS.write(x_off as u16);
|
|
||||||
BG0VOFS.write(y_off as u16);
|
|
||||||
|
|
||||||
// prep next frame
|
|
||||||
let k = FRAME_KEYS.read();
|
|
||||||
backdrop_color = Color(k.to_u16());
|
|
||||||
if k.up() {
|
|
||||||
y_off = y_off.wrapping_add(1);
|
|
||||||
}
|
}
|
||||||
if k.down() {
|
|
||||||
y_off = y_off.wrapping_sub(1);
|
|
||||||
}
|
|
||||||
if k.left() {
|
|
||||||
x_off = x_off.wrapping_add(1);
|
|
||||||
}
|
|
||||||
if k.right() {
|
|
||||||
x_off = x_off.wrapping_sub(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if k.start() {
|
#[cfg(test)]
|
||||||
panic!("pressed start")
|
mod tests {
|
||||||
}
|
#[test_case]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(1 + 1, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
99
src/test_harness.rs
Normal file
99
src/test_harness.rs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
use core::fmt::Write;
|
||||||
|
use gba::{bios, mem, mgba, prelude::*};
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||||
|
DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true));
|
||||||
|
BG_PALETTE.index(0).write(Color::from_rgb(25, 10, 5));
|
||||||
|
IE.write(IrqBits::VBLANK);
|
||||||
|
IME.write(true);
|
||||||
|
VBlankIntrWait();
|
||||||
|
VBlankIntrWait();
|
||||||
|
VBlankIntrWait();
|
||||||
|
|
||||||
|
// the Fatal one kills emulation after one line / 256 bytes
|
||||||
|
// so emit all the information as Error first
|
||||||
|
if let Ok(mut log) = mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Error) {
|
||||||
|
writeln!(log, "[failed]").ok();
|
||||||
|
write!(log, "{}", info).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(mut log) = mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Fatal) {
|
||||||
|
if let Some(loc) = info.location() {
|
||||||
|
write!(log, "panic at {loc}! see mgba error log for details.").ok();
|
||||||
|
} else {
|
||||||
|
write!(log, "panic! see mgba error log for details.").ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IE.write(IrqBits::new());
|
||||||
|
bios::IntrWait(true, IrqBits::new());
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait UnitTest {
|
||||||
|
fn run(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Fn()> UnitTest for T {
|
||||||
|
fn run(&self) {
|
||||||
|
if let Ok(mut log) = mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) {
|
||||||
|
write!(log, "{}...", core::any::type_name::<T>()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
self();
|
||||||
|
|
||||||
|
if let Ok(mut log) = mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) {
|
||||||
|
writeln!(log, "[ok]").ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn test_runner(tests: &[&dyn UnitTest]) {
|
||||||
|
if let Ok(mut log) = mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) {
|
||||||
|
write!(log, "Running {} tests", tests.len()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
for test in tests {
|
||||||
|
test.run();
|
||||||
|
}
|
||||||
|
if let Ok(mut log) = mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) {
|
||||||
|
write!(log, "Tests finished successfully").ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn main() {
|
||||||
|
DISPCNT.write(DisplayControl::new().with_video_mode(VideoMode::_0));
|
||||||
|
BG_PALETTE.index(0).write(Color::new());
|
||||||
|
|
||||||
|
crate::test_main();
|
||||||
|
|
||||||
|
BG_PALETTE.index(0).write(Color::from_rgb(5, 15, 25));
|
||||||
|
BG_PALETTE.index(1).write(Color::new());
|
||||||
|
BG0CNT.write(
|
||||||
|
BackgroundControl::new()
|
||||||
|
.with_charblock(0)
|
||||||
|
.with_screenblock(31),
|
||||||
|
);
|
||||||
|
DISPCNT.write(
|
||||||
|
DisplayControl::new()
|
||||||
|
.with_video_mode(VideoMode::_0)
|
||||||
|
.with_show_bg0(true),
|
||||||
|
);
|
||||||
|
|
||||||
|
// some niceties for people without mgba-test-runner
|
||||||
|
let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap();
|
||||||
|
unsafe {
|
||||||
|
mem::set_u32x80_unchecked(tsb.into_block::<1024>().as_mut_ptr().cast(), 0, 12);
|
||||||
|
}
|
||||||
|
Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0);
|
||||||
|
|
||||||
|
let row = tsb.get_row(9).unwrap().iter().skip(6);
|
||||||
|
for (addr, ch) in row.zip(b"all tests passed!") {
|
||||||
|
addr.write(TextEntry::new().with_tile(*ch as u16));
|
||||||
|
}
|
||||||
|
|
||||||
|
DISPSTAT.write(DisplayStatus::new());
|
||||||
|
bios::IntrWait(true, IrqBits::new());
|
||||||
|
}
|
16
unit-tests.sh
Executable file
16
unit-tests.sh
Executable file
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
export CARGO_TARGET_THUMBV4T_NONE_EABI_RUNNER=mgba-test-runner
|
||||||
|
export CARGO_TARGET_ARMV4T_NONE_EABI_RUNNER=mgba-test-runner
|
||||||
|
# terrible
|
||||||
|
export RUSTC_BOOTSTRAP=1
|
||||||
|
|
||||||
|
set -exo pipefail
|
||||||
|
|
||||||
|
# terrible
|
||||||
|
if ! which mgba-test-runner; then
|
||||||
|
pkg-config libelf
|
||||||
|
cargo install --git https://github.com/agbrs/agb --rev a7f9fdf01118a7a77d4dcf72f2b74a1961458b36 mgba-test-runner
|
||||||
|
fi
|
||||||
|
|
||||||
|
cargo -Z build-std test "$@"
|
Loading…
Reference in a new issue