mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 16:21:33 +11:00
initial commit
This commit is contained in:
commit
0790f78103
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
/out
|
||||
/.vscode
|
16
Cargo.lock
generated
Normal file
16
Cargo.lock
generated
Normal file
|
@ -0,0 +1,16 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "gba"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "gba"
|
||||
version = "0.1.0"
|
||||
authors = ["Corwin Kuiper <corwin@kuiper.dev>"]
|
||||
edition = "2018"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.0"
|
44
Makefile
Normal file
44
Makefile
Normal file
|
@ -0,0 +1,44 @@
|
|||
BINUTILS_PREFIX=arm-none-eabi-
|
||||
CC=$(BINUTILS_PREFIX)as
|
||||
ARCH = -mthumb-interwork -mthumb
|
||||
|
||||
RUSTFILES=$(shell find . -name '*.rs')
|
||||
|
||||
.ONESHELL:
|
||||
|
||||
out/debug/%.gba: cargo-debug-%
|
||||
@mkdir -p $(dir $@)
|
||||
@OUTNAME=$(patsubst out/debug/%.gba,%,$@)
|
||||
@$(BINUTILS_PREFIX)objcopy -O binary target/gba/debug/examples/$${OUTNAME} out/debug/$${OUTNAME}.gba
|
||||
@gbafix $@
|
||||
|
||||
out/release/%.gba: cargo-release-%
|
||||
@mkdir -p $(dir $@)
|
||||
@OUTNAME=$(patsubst out/release/%.gba,%,$@)
|
||||
@$(BINUTILS_PREFIX)objcopy -O binary target/gba/release/examples/$${OUTNAME} out/release/$${OUTNAME}.gba
|
||||
@gbafix $@
|
||||
|
||||
d-%: out/debug/%.gba
|
||||
@OUTNAME=$(patsubst d-%,%,$@)
|
||||
@mgba-qt $<
|
||||
@rm out/debug/$${OUTNAME}.sav
|
||||
|
||||
r-%: out/release/%.gba
|
||||
@OUTNAME=$(patsubst r-%,%,$@)
|
||||
@mgba-qt $<
|
||||
@rm out/release/$${OUTNAME}.sav
|
||||
|
||||
cargo-release-%: $(RUSTFILES) out/crt0.o
|
||||
@OUTNAME=$(patsubst cargo-release-%,%, $@)
|
||||
@rustup run nightly cargo xbuild --release --target=gba.json --example=$${OUTNAME}
|
||||
|
||||
cargo-debug-%: $(RUSTFILES) out/crt0.o
|
||||
@OUTNAME=$(patsubst cargo-debug-%,%, $@)
|
||||
@rustup run nightly cargo xbuild --target=gba.json --example=$${OUTNAME}
|
||||
|
||||
out/crt0.o: crt0.s interrupt_simple.s
|
||||
@mkdir $(dir $@)
|
||||
@$(CC) $(ARCH) -o out/crt0.o crt0.s
|
||||
|
||||
clippy:
|
||||
rustup run nightly cargo xclippy --target=gba.json
|
16
README.md
Normal file
16
README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Rust for the Gameboy Advance
|
||||
|
||||
This is my in development library for rust on the gameboy advance. It uses
|
||||
information from GbaTek, Tonc, and the existing
|
||||
[rust-console/gba](https://github.com/rust-console/gba). Namely the gba.json
|
||||
file comes from [rust-console/gba](https://github.com/rust-console/gba).
|
||||
|
||||
Note that this currently contains no documentation of any kind, unless you count
|
||||
examples as documentation.
|
||||
|
||||
## Requirements
|
||||
|
||||
* Nightly rust, probably quite a recent version.
|
||||
* Devkitarm.
|
||||
|
||||
This is probably easiest to do in a container.
|
19
crt0.s
Normal file
19
crt0.s
Normal file
|
@ -0,0 +1,19 @@
|
|||
.arm
|
||||
.global __start
|
||||
__start:
|
||||
b .Initialise
|
||||
|
||||
@ Filled in by gbafix
|
||||
.fill 188, 1, 0
|
||||
|
||||
.Initialise:
|
||||
@ Set interrupt handler
|
||||
ldr r0, =InterruptHandlerSimple
|
||||
ldr r1, =0x03007FFC
|
||||
str r0, [r1]
|
||||
|
||||
@ load main and branch
|
||||
ldr r0, =main
|
||||
bx r0
|
||||
|
||||
.include "interrupt_simple.s"
|
39
examples/bitmap3.rs
Normal file
39
examples/bitmap3.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
extern crate gba;
|
||||
|
||||
use gba::display;
|
||||
|
||||
struct Vector2D {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
let gba = gba::Gba::new();
|
||||
let bitmap = gba.display.bitmap3();
|
||||
|
||||
let mut input = gba::input::ButtonController::new();
|
||||
let mut pos = Vector2D {
|
||||
x: display::WIDTH / 2,
|
||||
y: display::HEIGHT / 2,
|
||||
};
|
||||
|
||||
gba::interrupt::enable(gba::interrupt::Interrupt::VBlank);
|
||||
gba::interrupt::enable_interrupts();
|
||||
gba::display::enable_VBlank_interrupt();
|
||||
|
||||
loop {
|
||||
gba::display::wait_for_VBlank();
|
||||
|
||||
input.update();
|
||||
pos.x += input.x_tri() as i32;
|
||||
pos.y += input.y_tri() as i32;
|
||||
|
||||
pos.x = pos.x.clamp(0, display::WIDTH - 1);
|
||||
pos.y = pos.y.clamp(0, display::HEIGHT - 1);
|
||||
bitmap.draw_point(pos.x, pos.y, 0x001F);
|
||||
}
|
||||
}
|
42
examples/bitmap4.rs
Normal file
42
examples/bitmap4.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
extern crate gba;
|
||||
|
||||
use gba::display;
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
let gba = gba::Gba::new();
|
||||
let bitmap = gba.display.bitmap4();
|
||||
|
||||
bitmap.set_palette_entry(1, 0x001F);
|
||||
bitmap.set_palette_entry(2, 0x03E0);
|
||||
|
||||
bitmap.draw_point_page(
|
||||
display::WIDTH / 2,
|
||||
display::HEIGHT / 2,
|
||||
1,
|
||||
display::Page::Front,
|
||||
);
|
||||
bitmap.draw_point_page(
|
||||
display::WIDTH / 2 + 5,
|
||||
display::HEIGHT / 2,
|
||||
2,
|
||||
display::Page::Back,
|
||||
);
|
||||
|
||||
gba::interrupt::enable(gba::interrupt::Interrupt::VBlank);
|
||||
gba::interrupt::enable_interrupts();
|
||||
gba::display::enable_VBlank_interrupt();
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
loop {
|
||||
gba::display::wait_for_VBlank();
|
||||
count += 1;
|
||||
if count % 6 == 0 {
|
||||
bitmap.flip_page();
|
||||
}
|
||||
}
|
||||
}
|
21
examples/panic.rs
Normal file
21
examples/panic.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
extern crate gba;
|
||||
|
||||
use gba::display;
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
let gba = gba::Gba::new();
|
||||
let bitmap = gba.display.bitmap3();
|
||||
|
||||
let mut input = gba::input::ButtonController::new();
|
||||
|
||||
loop {
|
||||
input.update();
|
||||
if input.is_just_pressed(gba::input::Button::A) {
|
||||
bitmap.draw_point(display::WIDTH, 0, 0x05);
|
||||
}
|
||||
}
|
||||
}
|
21
examples/syscall.rs
Normal file
21
examples/syscall.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
extern crate gba;
|
||||
use gba::{display, syscall};
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
let gba = gba::Gba::new();
|
||||
let bitmap = gba.display.bitmap3();
|
||||
|
||||
for x in 0..display::WIDTH {
|
||||
let y = syscall::sqrt(x << 6);
|
||||
let y = (display::HEIGHT - y).clamp(0, display::HEIGHT - 1);
|
||||
bitmap.draw_point(x, y, 0x001F);
|
||||
}
|
||||
|
||||
loop {
|
||||
syscall::halt();
|
||||
}
|
||||
}
|
35
gba.json
Normal file
35
gba.json
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"abi-blacklist": [
|
||||
"stdcall",
|
||||
"fastcall",
|
||||
"vectorcall",
|
||||
"thiscall",
|
||||
"win64",
|
||||
"sysv64"
|
||||
],
|
||||
"arch": "arm",
|
||||
"atomic-cas": false,
|
||||
"cpu": "arm7tdmi",
|
||||
"data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64",
|
||||
"emit-debug-gdb-scripts": false,
|
||||
"env": "agb",
|
||||
"executables": true,
|
||||
"features": "+soft-float,+strict-align",
|
||||
"linker": "arm-none-eabi-ld",
|
||||
"linker-flavor": "ld",
|
||||
"linker-is-gnu": true,
|
||||
"llvm-target": "thumbv4-none-eabi",
|
||||
"os": "none",
|
||||
"panic-strategy": "abort",
|
||||
"pre-link-args": {
|
||||
"ld": [
|
||||
"out/crt0.o",
|
||||
"-Tgba.ld"
|
||||
]
|
||||
},
|
||||
"relocation-model": "static",
|
||||
"target-c-int-width": "32",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "32",
|
||||
"vendor": "nintendo"
|
||||
}
|
51
gba.ld
Normal file
51
gba.ld
Normal file
|
@ -0,0 +1,51 @@
|
|||
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
|
||||
OUTPUT_ARCH(arm)
|
||||
|
||||
ENTRY(__start)
|
||||
|
||||
MEMORY {
|
||||
ewram (w!x) : ORIGIN = 0x2000000, LENGTH = 256K
|
||||
iwram (w!x) : ORIGIN = 0x3000000, LENGTH = 32K
|
||||
rom (rx) : ORIGIN = 0x8000000, LENGTH = 32M
|
||||
}
|
||||
|
||||
__text_start = ORIGIN(rom);
|
||||
|
||||
SECTIONS {
|
||||
. = __text_start;
|
||||
|
||||
.crt0 : {
|
||||
KEEP (*(.crt0));
|
||||
. = ALIGN(4);
|
||||
} > rom
|
||||
|
||||
.text : {
|
||||
*(.text .text*);
|
||||
. = ALIGN(4);
|
||||
} > rom
|
||||
__text_end = .;
|
||||
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*);
|
||||
. = ALIGN(4);
|
||||
} > rom
|
||||
|
||||
__data_start = .;
|
||||
.data : {
|
||||
*(.data .data.*);
|
||||
. = ALIGN(4);
|
||||
} > iwram
|
||||
__data_end = .;
|
||||
|
||||
.bss : {
|
||||
*(.bss .bss.*);
|
||||
. = ALIGN(4);
|
||||
} > iwram
|
||||
|
||||
.stack 0x80000 : {
|
||||
*(.stack)
|
||||
}
|
||||
|
||||
/* discard anything not already mentioned */
|
||||
/DISCARD/ : { *(*) }
|
||||
}
|
17
interrupt_simple.s
Normal file
17
interrupt_simple.s
Normal file
|
@ -0,0 +1,17 @@
|
|||
@ An interrupt handler that simply acknowledges all interrupts
|
||||
.arm
|
||||
.global InterruptHandlerSimple
|
||||
.align
|
||||
InterruptHandlerSimple:
|
||||
ldr r2, =0x04000200
|
||||
ldrh r1, [r2]
|
||||
ldrh r3, [r2, #2]
|
||||
and r0, r1, r3
|
||||
strh r0, [r2, #2]
|
||||
|
||||
ldr r2, =0x03007FF8
|
||||
ldrh r1, [r2]
|
||||
orr r1, r1, r0
|
||||
strh r1, [r2]
|
||||
|
||||
bx lr
|
196
src/display.rs
Normal file
196
src/display.rs
Normal file
|
@ -0,0 +1,196 @@
|
|||
use crate::{
|
||||
memory_mapped::{MemoryMapped, MemoryMapped1DArray, MemoryMapped2DArray},
|
||||
single::{Single, SingleToken},
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use core::convert::TryInto;
|
||||
|
||||
const DISPLAY_CONTROL: MemoryMapped<u16> = MemoryMapped::new(0x0400_0000);
|
||||
const DISPLAY_STATUS: MemoryMapped<u16> = MemoryMapped::new(0x0400_0004);
|
||||
const VCOUNT: MemoryMapped<u16> = MemoryMapped::new(0x0400_0006);
|
||||
|
||||
const PALETTE_BACKGROUND: MemoryMapped1DArray<u16, 256> = MemoryMapped1DArray::new(0x0500_0000);
|
||||
const PALETTE_SPRITE: MemoryMapped1DArray<u16, 256> = MemoryMapped1DArray::new(0x0500_0200);
|
||||
|
||||
const BITMAP_MODE_3: MemoryMapped2DArray<u16, { WIDTH as usize }, { HEIGHT as usize }> =
|
||||
MemoryMapped2DArray::new(0x600_0000);
|
||||
|
||||
const BITMAP_PAGE_FRONT_MODE_4: MemoryMapped2DArray<
|
||||
u16,
|
||||
{ (WIDTH / 2) as usize },
|
||||
{ HEIGHT as usize },
|
||||
> = MemoryMapped2DArray::new(0x600_0000);
|
||||
const BITMAP_PAGE_BACK_MODE_4: MemoryMapped2DArray<
|
||||
u16,
|
||||
{ (WIDTH / 2) as usize },
|
||||
{ HEIGHT as usize },
|
||||
> = MemoryMapped2DArray::new(0x600_A000);
|
||||
|
||||
pub const WIDTH: i32 = 240;
|
||||
pub const HEIGHT: i32 = 160;
|
||||
|
||||
pub enum DisplayMode {
|
||||
Tiled0 = 0,
|
||||
Tiled1 = 1,
|
||||
Tiled2 = 2,
|
||||
Bitmap3 = 3,
|
||||
Bitmap4 = 4,
|
||||
Bitmap5 = 5,
|
||||
}
|
||||
|
||||
pub enum Page {
|
||||
Front = 0,
|
||||
Back = 1,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct GraphicsSettings: u16 {
|
||||
const PAGE_SELECT = 1 << 0x4;
|
||||
const OAM_HBLANK = 1 << 0x5;
|
||||
const SPRITE1_D = 1 << 0x6;
|
||||
const SCREEN_BLANK = 1 << 0x7;
|
||||
const LAYER_BG0 = 1 << 0x8;
|
||||
const LAYER_BG1 = 1 << 0x9;
|
||||
const LAYER_BG2 = 1 << 0xA;
|
||||
const LAYER_BG3 = 1 << 0xB;
|
||||
const LAYER_OBJ = 1 << 0xC;
|
||||
const WINDOW0 = 1 << 0xD;
|
||||
const WINDOW1 = 1 << 0xE;
|
||||
const WINDOW_OBJECT = 1 << 0xF;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Display {
|
||||
in_mode: Single,
|
||||
}
|
||||
|
||||
impl Default for Display {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Display {
|
||||
in_mode: Single::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bitmap3(&self) -> Bitmap3 {
|
||||
Bitmap3::new(
|
||||
self.in_mode
|
||||
.take()
|
||||
.expect("Cannot create new mode as mode already taken"),
|
||||
)
|
||||
}
|
||||
pub fn bitmap4(&self) -> Bitmap4 {
|
||||
Bitmap4::new(
|
||||
self.in_mode
|
||||
.take()
|
||||
.expect("Cannot create new mode as mode already taken"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bitmap3<'a> {
|
||||
_in_mode: SingleToken<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Bitmap3<'a> {
|
||||
fn new(in_mode: SingleToken<'a>) -> Self {
|
||||
set_graphics_mode(DisplayMode::Bitmap3);
|
||||
set_graphics_settings(GraphicsSettings::LAYER_BG2);
|
||||
Bitmap3 { _in_mode: in_mode }
|
||||
}
|
||||
pub fn draw_point(&self, x: i32, y: i32, colour: u16) {
|
||||
let x = x.try_into().unwrap();
|
||||
let y = y.try_into().unwrap();
|
||||
BITMAP_MODE_3.set(x, y, colour)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bitmap4<'a> {
|
||||
_in_mode: SingleToken<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Bitmap4<'a> {
|
||||
fn new(in_mode: SingleToken<'a>) -> Self {
|
||||
set_graphics_mode(DisplayMode::Bitmap4);
|
||||
set_graphics_settings(GraphicsSettings::LAYER_BG2);
|
||||
Bitmap4 { _in_mode: in_mode }
|
||||
}
|
||||
|
||||
pub fn draw_point_page(&self, x: i32, y: i32, colour: u8, page: Page) {
|
||||
let addr = match page {
|
||||
Page::Front => BITMAP_PAGE_FRONT_MODE_4,
|
||||
Page::Back => BITMAP_PAGE_BACK_MODE_4,
|
||||
};
|
||||
|
||||
let x_in_screen = (x / 2) as usize;
|
||||
let y_in_screen = y as usize;
|
||||
|
||||
let c = addr.get(x_in_screen, y_in_screen);
|
||||
if x & 0b1 != 0 {
|
||||
addr.set(x_in_screen, y_in_screen, c | (colour as u16) << 8);
|
||||
} else {
|
||||
addr.set(x_in_screen, y_in_screen, c | colour as u16);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_point(&self, x: i32, y: i32, colour: u8) {
|
||||
let disp = DISPLAY_CONTROL.get();
|
||||
|
||||
let page = if disp & GraphicsSettings::PAGE_SELECT.bits() != 0 {
|
||||
Page::Back
|
||||
} else {
|
||||
Page::Front
|
||||
};
|
||||
|
||||
self.draw_point_page(x, y, colour, page)
|
||||
}
|
||||
|
||||
pub fn set_palette_entry(&self, entry: u32, colour: u16) {
|
||||
PALETTE_BACKGROUND.set(entry as usize, colour);
|
||||
}
|
||||
|
||||
pub fn flip_page(&self) {
|
||||
let disp = DISPLAY_CONTROL.get();
|
||||
let swapped = disp ^ GraphicsSettings::PAGE_SELECT.bits();
|
||||
DISPLAY_CONTROL.set(swapped);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_graphics_mode(mode: DisplayMode) {
|
||||
let current = DISPLAY_CONTROL.get();
|
||||
let current = current & (!0b111);
|
||||
let s = current | (mode as u16 & 0b111);
|
||||
|
||||
DISPLAY_CONTROL.set(s);
|
||||
}
|
||||
|
||||
pub fn set_graphics_settings(settings: GraphicsSettings) {
|
||||
let current = DISPLAY_CONTROL.get();
|
||||
// preserve display mode
|
||||
let current = current & 0b111;
|
||||
let s = settings.bits() | current;
|
||||
|
||||
DISPLAY_CONTROL.set(s);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn busy_wait_for_VBlank() {
|
||||
while VCOUNT.get() >= 160 {}
|
||||
while VCOUNT.get() < 160 {}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn enable_VBlank_interrupt() {
|
||||
let status = DISPLAY_STATUS.get() | (1 << 3);
|
||||
DISPLAY_STATUS.set(status);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn wait_for_VBlank() {
|
||||
crate::syscall::wait_for_VBlank();
|
||||
}
|
99
src/input.rs
Normal file
99
src/input.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
use bitflags::bitflags;
|
||||
use core::convert::From;
|
||||
|
||||
pub enum Tri {
|
||||
Positive = 1,
|
||||
Zero = 0,
|
||||
Negative = -1,
|
||||
}
|
||||
|
||||
impl From<(bool, bool)> for Tri {
|
||||
fn from(a: (bool, bool)) -> Tri {
|
||||
let b1 = a.0 as i8;
|
||||
let b2 = a.1 as i8;
|
||||
unsafe { core::mem::transmute(b2 - b1) }
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct Button: u32 {
|
||||
const A = 1 << 0;
|
||||
const B = 1 << 1;
|
||||
const SELECT = 1 << 2;
|
||||
const START = 1 << 3;
|
||||
const RIGHT = 1 << 4;
|
||||
const LEFT = 1 << 5;
|
||||
const UP = 1 << 6;
|
||||
const DOWN = 1 << 7;
|
||||
const R = 1 << 8;
|
||||
const L = 1 << 9;
|
||||
}
|
||||
}
|
||||
|
||||
const KEY_MASK: u16 = 0b1111111111;
|
||||
|
||||
const BUTTON_INPUT: *mut u16 = (0x04000130) as *mut u16;
|
||||
|
||||
// const BUTTON_INTURRUPT: *mut u16 = (0x04000132) as *mut u16;
|
||||
|
||||
pub struct ButtonController {
|
||||
previous: u16,
|
||||
current: u16,
|
||||
}
|
||||
|
||||
impl Default for ButtonController {
|
||||
fn default() -> Self {
|
||||
ButtonController::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ButtonController {
|
||||
pub fn new() -> Self {
|
||||
ButtonController {
|
||||
previous: 0,
|
||||
current: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.previous = self.current;
|
||||
self.current = unsafe { BUTTON_INPUT.read_volatile() } ^ KEY_MASK;
|
||||
}
|
||||
|
||||
pub fn x_tri(&self) -> Tri {
|
||||
let left = self.is_pressed(Button::LEFT);
|
||||
let right = self.is_pressed(Button::RIGHT);
|
||||
|
||||
(left, right).into()
|
||||
}
|
||||
|
||||
pub fn y_tri(&self) -> Tri {
|
||||
let up = self.is_pressed(Button::UP);
|
||||
let down = self.is_pressed(Button::DOWN);
|
||||
|
||||
(up, down).into()
|
||||
}
|
||||
|
||||
pub fn is_pressed(&self, keys: Button) -> bool {
|
||||
let currently_pressed = self.current as u32;
|
||||
let keys = keys.bits();
|
||||
(currently_pressed & keys) != 0
|
||||
}
|
||||
pub fn is_released(&self, keys: Button) -> bool {
|
||||
!self.is_pressed(keys)
|
||||
}
|
||||
|
||||
pub fn is_just_pressed(&self, keys: Button) -> bool {
|
||||
let current = self.current as u32;
|
||||
let previous = self.previous as u32;
|
||||
let keys = keys.bits();
|
||||
((current & keys) != 0) && ((previous & keys) == 0)
|
||||
}
|
||||
|
||||
pub fn is_just_released(&self, keys: Button) -> bool {
|
||||
let current = self.current as u32;
|
||||
let previous = self.previous as u32;
|
||||
let keys = keys.bits();
|
||||
((current & keys) == 0) && ((previous & keys) != 0)
|
||||
}
|
||||
}
|
56
src/interrupt.rs
Normal file
56
src/interrupt.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use crate::memory_mapped::MemoryMapped;
|
||||
|
||||
pub enum Interrupt {
|
||||
VBlank,
|
||||
HBlank,
|
||||
VCounter,
|
||||
Timer0,
|
||||
Timer1,
|
||||
Timer2,
|
||||
Timer3,
|
||||
Serial,
|
||||
Dma0,
|
||||
Dma1,
|
||||
Dma2,
|
||||
Dma3,
|
||||
Keypad,
|
||||
Gamepak,
|
||||
}
|
||||
|
||||
const ENABLED_INTERRUPTS: MemoryMapped<u16> = MemoryMapped::new(0x04000200);
|
||||
const INTERRUPTS_ENABLED: MemoryMapped<u16> = MemoryMapped::new(0x04000208);
|
||||
|
||||
pub fn enable(interrupt: Interrupt) {
|
||||
let _interrupt_token = temporary_interrupt_disable();
|
||||
let interrupt = interrupt as usize;
|
||||
let enabled = ENABLED_INTERRUPTS.get() | (1 << (interrupt as u16));
|
||||
ENABLED_INTERRUPTS.set(enabled);
|
||||
}
|
||||
|
||||
pub fn disable(interrupt: Interrupt) {
|
||||
let _interrupt_token = temporary_interrupt_disable();
|
||||
let interrupt = interrupt as usize;
|
||||
let enabled = ENABLED_INTERRUPTS.get() & !(1 << (interrupt as u16));
|
||||
ENABLED_INTERRUPTS.set(enabled);
|
||||
}
|
||||
|
||||
pub struct Disable {}
|
||||
|
||||
impl Drop for Disable {
|
||||
fn drop(&mut self) {
|
||||
enable_interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn temporary_interrupt_disable() -> Disable {
|
||||
disable_interrupts();
|
||||
Disable {}
|
||||
}
|
||||
|
||||
pub fn enable_interrupts() {
|
||||
INTERRUPTS_ENABLED.set(1);
|
||||
}
|
||||
|
||||
fn disable_interrupts() {
|
||||
INTERRUPTS_ENABLED.set(0);
|
||||
}
|
49
src/lib.rs
Normal file
49
src/lib.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
#![no_std]
|
||||
#![feature(asm)]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use core::fmt::Write;
|
||||
pub mod display;
|
||||
pub mod input;
|
||||
pub mod interrupt;
|
||||
|
||||
mod memory_mapped;
|
||||
mod mgba;
|
||||
mod single;
|
||||
|
||||
pub mod syscall;
|
||||
|
||||
#[panic_handler]
|
||||
#[allow(unused_must_use)]
|
||||
fn panic_implementation(info: &core::panic::PanicInfo) -> ! {
|
||||
if let Some(mut mgba) = mgba::Mgba::new() {
|
||||
write!(mgba, "{}", info);
|
||||
mgba.set_level(mgba::DebugLevel::Fatal);
|
||||
}
|
||||
|
||||
loop {}
|
||||
}
|
||||
|
||||
static mut GBASINGLE: single::Singleton<Gba> = single::Singleton::new(Gba::single_new());
|
||||
|
||||
pub struct Gba {
|
||||
pub display: display::Display,
|
||||
}
|
||||
|
||||
impl Gba {
|
||||
pub fn new() -> Self {
|
||||
unsafe { GBASINGLE.take() }
|
||||
}
|
||||
|
||||
const fn single_new() -> Self {
|
||||
Self {
|
||||
display: display::Display::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Gba {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
56
src/memory_mapped.rs
Normal file
56
src/memory_mapped.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
pub struct MemoryMapped<T> {
|
||||
address: *mut T,
|
||||
}
|
||||
|
||||
impl<T> MemoryMapped<T> {
|
||||
pub const fn new(address: usize) -> Self {
|
||||
MemoryMapped {
|
||||
address: address as *mut T,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> T {
|
||||
unsafe { self.address.read_volatile() }
|
||||
}
|
||||
|
||||
pub fn set(&self, val: T) {
|
||||
unsafe { self.address.write_volatile(val) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MemoryMapped1DArray<T, const N: usize> {
|
||||
array: *mut [T; N],
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl<T, const N: usize> MemoryMapped1DArray<T, N> {
|
||||
pub const fn new(address: usize) -> Self {
|
||||
MemoryMapped1DArray {
|
||||
array: address as *mut [T; N],
|
||||
}
|
||||
}
|
||||
pub fn get(&self, n: usize) -> T {
|
||||
unsafe { (&mut (*self.array)[n] as *mut T).read_volatile() }
|
||||
}
|
||||
pub fn set(&self, n: usize, val: T) {
|
||||
unsafe { (&mut (*self.array)[n] as *mut T).write_volatile(val) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MemoryMapped2DArray<T, const X: usize, const Y: usize> {
|
||||
array: *mut [[T; X]; Y],
|
||||
}
|
||||
|
||||
impl<T, const X: usize, const Y: usize> MemoryMapped2DArray<T, X, Y> {
|
||||
pub const fn new(address: usize) -> Self {
|
||||
MemoryMapped2DArray {
|
||||
array: address as *mut [[T; X]; Y],
|
||||
}
|
||||
}
|
||||
pub fn get(&self, x: usize, y: usize) -> T {
|
||||
unsafe { (&mut (*self.array)[y][x] as *mut T).read_volatile() }
|
||||
}
|
||||
pub fn set(&self, x: usize, y: usize, val: T) {
|
||||
unsafe { (&mut (*self.array)[y][x] as *mut T).write_volatile(val) }
|
||||
}
|
||||
}
|
67
src/mgba.rs
Normal file
67
src/mgba.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use crate::memory_mapped::MemoryMapped;
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Copy)]
|
||||
#[repr(u16)]
|
||||
#[allow(dead_code)]
|
||||
pub enum DebugLevel {
|
||||
Fatal = 0,
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Info = 3,
|
||||
Debug = 4,
|
||||
}
|
||||
|
||||
const OUTPUT: *mut u8 = 0x04FF_F600 as *mut u8;
|
||||
const ENABLE: MemoryMapped<u16> = MemoryMapped::new(0x04FF_F780);
|
||||
|
||||
const ENABLE_HANDSHAKE_IN: u16 = 0xC0DE;
|
||||
const ENABLE_HANDSHAKE_OUT: u16 = 0x1DEA;
|
||||
|
||||
const DEBUG_LEVEL: MemoryMapped<u16> = MemoryMapped::new(0x04FF_F700);
|
||||
const DEBUG_FLAG_CODE: u16 = 0x0100;
|
||||
|
||||
fn is_running_in_mgba() -> bool {
|
||||
ENABLE.set(ENABLE_HANDSHAKE_IN);
|
||||
ENABLE.get() == ENABLE_HANDSHAKE_OUT
|
||||
}
|
||||
|
||||
pub struct Mgba {
|
||||
bytes_written: usize,
|
||||
}
|
||||
|
||||
impl Mgba {
|
||||
pub fn new() -> Option<Self> {
|
||||
if is_running_in_mgba() {
|
||||
Some(Mgba { bytes_written: 0 })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mgba {
|
||||
pub fn set_level(&mut self, level: DebugLevel) {
|
||||
DEBUG_LEVEL.set(DEBUG_FLAG_CODE | level as u16);
|
||||
self.bytes_written = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Write for Mgba {
|
||||
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
|
||||
unsafe {
|
||||
let mut current_location = OUTPUT.add(self.bytes_written);
|
||||
let mut str_iter = s.bytes();
|
||||
while self.bytes_written < 255 {
|
||||
match str_iter.next() {
|
||||
Some(byte) => {
|
||||
current_location.write(byte);
|
||||
current_location = current_location.offset(1);
|
||||
self.bytes_written += 1;
|
||||
}
|
||||
None => return Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
48
src/single.rs
Normal file
48
src/single.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use core::cell::Cell;
|
||||
|
||||
pub struct Singleton<T> {
|
||||
single: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Singleton<T> {
|
||||
pub const fn new(s: T) -> Self {
|
||||
Singleton { single: Some(s) }
|
||||
}
|
||||
pub fn take(&mut self) -> T {
|
||||
let g = core::mem::replace(&mut self.single, None);
|
||||
g.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Single {
|
||||
is_taken: Cell<bool>,
|
||||
}
|
||||
|
||||
pub struct SingleToken<'a> {
|
||||
cell: &'a Cell<bool>,
|
||||
}
|
||||
|
||||
impl Single {
|
||||
pub const fn new() -> Self {
|
||||
Single {
|
||||
is_taken: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take(&self) -> Result<SingleToken, &'static str> {
|
||||
if self.is_taken.get() {
|
||||
Err("Already taken")
|
||||
} else {
|
||||
self.is_taken.set(true);
|
||||
Ok(SingleToken {
|
||||
cell: &self.is_taken,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SingleToken<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.cell.set(false);
|
||||
}
|
||||
}
|
104
src/syscall.rs
Normal file
104
src/syscall.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
#[allow(non_snake_case)]
|
||||
|
||||
pub fn halt() {
|
||||
unsafe {
|
||||
asm!(
|
||||
"swi 0x02",
|
||||
lateout("r0") _,
|
||||
lateout("r1") _,
|
||||
lateout("r2") _,
|
||||
lateout("r3") _
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop() {
|
||||
unsafe {
|
||||
asm!(
|
||||
"swi 0x03",
|
||||
lateout("r0") _,
|
||||
lateout("r1") _,
|
||||
lateout("r2") _,
|
||||
lateout("r3") _
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_for_interrupt() {
|
||||
unsafe {
|
||||
asm!(
|
||||
"swi 0x04",
|
||||
lateout("r0") _,
|
||||
lateout("r1") _,
|
||||
lateout("r2") _,
|
||||
lateout("r3") _
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn wait_for_VBlank() {
|
||||
unsafe {
|
||||
asm!(
|
||||
"swi 0x05",
|
||||
lateout("r0") _,
|
||||
lateout("r1") _,
|
||||
lateout("r2") _,
|
||||
lateout("r3") _
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn div(numerator: i32, denominator: i32) -> (i32, i32, i32) {
|
||||
let divide: i32;
|
||||
let modulo: i32;
|
||||
let abs_divide: i32;
|
||||
unsafe {
|
||||
asm!(
|
||||
"swi 0x06",
|
||||
in("r0") numerator,
|
||||
in("r1") denominator,
|
||||
lateout("r0") divide,
|
||||
lateout("r1") modulo,
|
||||
lateout("r3") abs_divide,
|
||||
);
|
||||
}
|
||||
(divide, modulo, abs_divide)
|
||||
}
|
||||
|
||||
pub fn sqrt(n: i32) -> i32 {
|
||||
let result: i32;
|
||||
unsafe {
|
||||
asm!(
|
||||
"swi 0x08",
|
||||
in("r0") n,
|
||||
lateout("r0") result,
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn arc_tan(n: i16) -> i16 {
|
||||
let result: i16;
|
||||
unsafe {
|
||||
asm!(
|
||||
"swi 0x09",
|
||||
in("r0") n,
|
||||
lateout("r0") result,
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn arc_tan2(x: i16, y: i32) -> i16 {
|
||||
let result: i16;
|
||||
unsafe {
|
||||
asm!(
|
||||
"swi 0x09",
|
||||
in("r0") x,
|
||||
in("r1") y,
|
||||
lateout("r0") result,
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
Loading…
Reference in a new issue