mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-24 03:11:29 +11:00
Introduction and Limits chapters updated
This commit is contained in:
parent
91057a529d
commit
ac5353b773
|
@ -43,10 +43,10 @@ fn main() -> std::io::Result<()> {
|
||||||
std::process::Command::new("arm-none-eabi-objcopy").args(
|
std::process::Command::new("arm-none-eabi-objcopy").args(
|
||||||
&["-O", "binary",
|
&["-O", "binary",
|
||||||
&format!("target/thumbv4-none-agb/release/examples/{}",name),
|
&format!("target/thumbv4-none-agb/release/examples/{}",name),
|
||||||
&format!("target/example-{}.gba",name)])
|
&format!("target/{}.gba",name)])
|
||||||
.output().expect("failed to objcopy!");
|
.output().expect("failed to objcopy!");
|
||||||
std::process::Command::new("gbafix").args(
|
std::process::Command::new("gbafix").args(
|
||||||
&[&format!("target/example-{}.gba",name)])
|
&[&format!("target/{}.gba",name)])
|
||||||
.output().expect("failed to gbafix!");
|
.output().expect("failed to gbafix!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
README.md
18
README.md
|
@ -8,17 +8,19 @@
|
||||||
|
|
||||||
# gba
|
# gba
|
||||||
|
|
||||||
A crate that helps you make GameBoy Advance (GBA) games
|
This repository is both a [Tutorial Book](https://rust-console.github.io/gba/)
|
||||||
|
that teaches you what you need to know to write Rust games for the GameBoy
|
||||||
|
Advance (GBA), and also a [crate](https://crates.io/crates/gba) that you can
|
||||||
|
use to do the same.
|
||||||
|
|
||||||
# First Time Setup
|
## First Time Setup
|
||||||
|
|
||||||
This crate requires a fair amount of special setup. All of the steps are
|
Writing a Rust program for the GBA requires a fair amount of special setup. All
|
||||||
detailed for you [in the 0th chapter of the
|
of the steps are detailed for you [in the Introduction chapter of the
|
||||||
book](https://rust-console.github.io/gba/00-introduction/03-development-setup.html) that goes with this
|
book](https://rust-console.github.io/gba/00-introduction/03-development-setup.html).
|
||||||
crate.
|
|
||||||
|
|
||||||
If you've done the global setup once before and just want to get a new project
|
If you've done the described global setup once before and just want to get a new
|
||||||
started quickly we got you covered:
|
project started quickly we got you covered:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl https://raw.githubusercontent.com/rust-console/gba/master/init.sh -sSf | bash -s APP_NAME
|
curl https://raw.githubusercontent.com/rust-console/gba/master/init.sh -sSf | bash -s APP_NAME
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
This is the book for learning how to write GBA games in Rust.
|
This is the book for learning how to write GameBoy Advance (GBA) games in Rust.
|
||||||
|
|
||||||
I'm **Lokathor**, the main author of the book. There's also **Ketsuban** who
|
I'm **Lokathor**, the main author of the book. There's also **Ketsuban** who
|
||||||
provides the technical advisement, reviews the PRs, and keeps my crazy in check.
|
provides the technical advisement, reviews the PRs, and keeps my crazy in check.
|
||||||
|
|
|
@ -21,8 +21,8 @@ goes:
|
||||||
`hello_magic.rs`:
|
`hello_magic.rs`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#![feature(start)]
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![feature(start)]
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
|
|
@ -1 +1,10 @@
|
||||||
# GBA Limitations
|
# GBA Limitations
|
||||||
|
|
||||||
|
There's a lot of seemingly normal Rust code that you can't write when compiling
|
||||||
|
for the GBA.
|
||||||
|
|
||||||
|
I mean there's a lot of stuff that you _can_ write, but we don't wanna get
|
||||||
|
tripped up by assuming we can use something only to get stuck when it's missing.
|
||||||
|
|
||||||
|
We'll start the book by reviewing all the major things we don't have so that we
|
||||||
|
can avoid any surprises later.
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
# No Floats
|
|
108
book/src/01-limitations/01-no_std.md
Normal file
108
book/src/01-limitations/01-no_std.md
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
# No Std
|
||||||
|
|
||||||
|
First up, as you already saw in the `hello_magic` code, we have to use the
|
||||||
|
`#![no_std]` outer attribute on our program when we target the GBA. You can find
|
||||||
|
some info about `no_std` in two official sources:
|
||||||
|
|
||||||
|
* [unstable
|
||||||
|
book section](https://doc.rust-lang.org/unstable-book/language-features/lang-items.html#writing-an-executable-without-stdlib)
|
||||||
|
* [embedded
|
||||||
|
book section](https://rust-embedded.github.io/book/intro/no-std.html?highlight=no_std#a--no_std--rust-environment)
|
||||||
|
|
||||||
|
The unstable book is borderline useless here because it's describing too many
|
||||||
|
things in too many words. The embedded book is much better, but still fairly
|
||||||
|
terse.
|
||||||
|
|
||||||
|
## Bare Metal
|
||||||
|
|
||||||
|
The GBA falls under what the Embedded Book calls "Bare Metal Environments".
|
||||||
|
Basically, the machine powers on and immediately begins executing some ASM code.
|
||||||
|
Our ASM startup was provided by `Ketsuban` (check the `crt0.s` file). We'll go
|
||||||
|
over _how_ it works much later on, for now it's enough to know that it does
|
||||||
|
work, and eventually controls passes into Rust code.
|
||||||
|
|
||||||
|
On the rust code side of things, we determine our starting point with the
|
||||||
|
`#[start]` attribute on our `main` function. The `main` function also has a
|
||||||
|
specific type signature that's different from the usual `main` that you'd see in
|
||||||
|
Rust. I'd tell you to read the unstable-book entry on `#[start]` but they
|
||||||
|
[literally](https://doc.rust-lang.org/unstable-book/language-features/start.html)
|
||||||
|
just tell you to look at the [tracking issue for
|
||||||
|
it](https://github.com/rust-lang/rust/issues/29633) instead, and that's not very
|
||||||
|
helpful either. Basically it just _has_ to be declared the way it is, even
|
||||||
|
though there's nothing passing in the arguments and there's no place that the
|
||||||
|
return value will go. The compiler won't accept it any other way.
|
||||||
|
|
||||||
|
## No Standard Library
|
||||||
|
|
||||||
|
The Embedded Book tells us that we can't use the standard library, but we get
|
||||||
|
access to something called "libcore", which sounds kinda funny. What they're
|
||||||
|
talking about is just [the core
|
||||||
|
crate](https://doc.rust-lang.org/core/index.html), which is called `libcore`
|
||||||
|
within the rust repository for historical reasons.
|
||||||
|
|
||||||
|
The `core` crate is actually still a really big portion of Rust. The standard
|
||||||
|
library doesn't actually hold too much code (relatively speaking), instead it
|
||||||
|
just takes code form other crates and then re-exports it in an organized way. So
|
||||||
|
with just `core` instead of `std`, what are we missing?
|
||||||
|
|
||||||
|
In no particular order:
|
||||||
|
|
||||||
|
* Allocation
|
||||||
|
* Clock
|
||||||
|
* Network
|
||||||
|
* File System
|
||||||
|
|
||||||
|
The allocation system and all the types that you can use if you have a global
|
||||||
|
allocator are neatly packaged up in the
|
||||||
|
[alloc](https://doc.rust-lang.org/alloc/index.html) crate. The rest isn't as
|
||||||
|
nicely organized.
|
||||||
|
|
||||||
|
It's _possible_ to implement a fair portion of the entire standard library
|
||||||
|
within a GBA context and make the rest just panic if you try to use it. However,
|
||||||
|
do you really need all that? Eh... probably not?
|
||||||
|
|
||||||
|
* We don't need a file system, because all of our data is just sitting there in
|
||||||
|
the ROM for us to use. When programming we can organize our `const` data into
|
||||||
|
modules and such to keep it organized, but once the game is compiled it's just
|
||||||
|
one huge flat address space.
|
||||||
|
* Networking, well, the GBA has a Link Cable you can use to communicate with
|
||||||
|
another GBA, but it's not really like a unix socket with TCP, so the standard
|
||||||
|
Rust networking isn't a very good match.
|
||||||
|
* Clock is actually two different things at once. One is the ability to store
|
||||||
|
the time long term, which is a bit of hardware that some gamepaks have in them
|
||||||
|
(eg: pokemon ruby/sapphire/emerald). The GBA itself can't keep time while
|
||||||
|
power is off. However, the second part is just tracking time moment to moment,
|
||||||
|
which the GBA can totally do. We'll see how to access the timers soon enough.
|
||||||
|
|
||||||
|
Which just leaves us with allocation. Do we need an allocator? Depends on your
|
||||||
|
game. For demos and small games you probably don't need one. For bigger games
|
||||||
|
you'll maybe want to get an allocator going eventually. It's in some sense a
|
||||||
|
crutch, but it's a very useful one.
|
||||||
|
|
||||||
|
So I promise that at some point we'll cover how to get an allocator going.
|
||||||
|
Either a Rust Global Allocator (if practical), which would allow for a lot of
|
||||||
|
the standard library types to be used "for free" once it was set up, or just a
|
||||||
|
custom allocator that's GBA specific if Rust's global allocator style isn't a
|
||||||
|
good fit for the GBA (I honestly haven't looked into it).
|
||||||
|
|
||||||
|
## Bare Metal Panic
|
||||||
|
|
||||||
|
TODO: expand this
|
||||||
|
|
||||||
|
* Write `0xC0DE` to `0x4fff780` (`u16`) to enable mGBA logging. Write any other
|
||||||
|
value to disable it.
|
||||||
|
* Read `0x4fff780` (`u16`) to check mGBA logging status.
|
||||||
|
* You get `0x1DEA` if debugging is active.
|
||||||
|
* Otherwise you get standard open bus nonsense values.
|
||||||
|
* Write your message into the virtual `[u8; 255]` array starting at `0x4fff600`.
|
||||||
|
mGBA will interpret these bytes as a CString value.
|
||||||
|
* Write `0x100` PLUS the message level to `0x4fff700` (`u16`) when you're ready
|
||||||
|
to send a message line:
|
||||||
|
* 0: Fatal (halts execution with a popup)
|
||||||
|
* 1: Error
|
||||||
|
* 2: Warning
|
||||||
|
* 3: Info
|
||||||
|
* 4: Debug
|
||||||
|
* Sending the message also automatically zeroes the output buffer.
|
||||||
|
* View the output within the "Tools" menu, "View Logs...". Note that the Fatal
|
||||||
|
message, if any doesn't get logged.
|
|
@ -1 +0,0 @@
|
||||||
# Core Only
|
|
13
book/src/01-limitations/02-fixed_only.md
Normal file
13
book/src/01-limitations/02-fixed_only.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Fixed Only
|
||||||
|
|
||||||
|
In addition to not having the standard library available, we don't even have a
|
||||||
|
floating point unit available! We can't do floating pont math in hardware! We
|
||||||
|
could still do floating point math as software computations if we wanted, but
|
||||||
|
that's a slow, slow thing to do.
|
||||||
|
|
||||||
|
Instead let's learn about another way to have fractional values called "Fixed
|
||||||
|
Point"
|
||||||
|
|
||||||
|
## Fixed Point
|
||||||
|
|
||||||
|
TODO: describe fixed point, make some types, do the impls, all that.
|
|
@ -1,5 +1,7 @@
|
||||||
# Volatile Destination
|
# Volatile Destination
|
||||||
|
|
||||||
|
TODO: replace all this one "the rant" is finalized
|
||||||
|
|
||||||
There's a reasonable chance that you've never heard of `volatile` before, so
|
There's a reasonable chance that you've never heard of `volatile` before, so
|
||||||
what's that? Well, it's a term that can be used in more than one context, but
|
what's that? Well, it's a term that can be used in more than one context, but
|
||||||
basically it means "get your grubby mitts off my stuff you over-eager compiler".
|
basically it means "get your grubby mitts off my stuff you over-eager compiler".
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
* [Newtype](00-introduction/05-newtype.md)
|
* [Newtype](00-introduction/05-newtype.md)
|
||||||
* [Help and Resources](00-introduction/06-help_and_resources.md)
|
* [Help and Resources](00-introduction/06-help_and_resources.md)
|
||||||
* [Limitations](01-limitations/00-index.md)
|
* [Limitations](01-limitations/00-index.md)
|
||||||
* [No Floats](01-limitations/01-no_floats.md)
|
* [No Std](01-limitations/01-no_std.md)
|
||||||
* [Core Only](01-limitations/02-core_only.md)
|
* [Fixed Only](01-limitations/02-fixed_only.md)
|
||||||
* [Volatile Destination](01-limitations/03-volatile_destination.md)
|
* [Volatile Destination](01-limitations/03-volatile_destination.md)
|
||||||
* [Concepts](02-concepts/00-index.md)
|
* [Concepts](02-concepts/00-index.md)
|
||||||
* [CPU](02-concepts/01-cpu.md)
|
* [CPU](02-concepts/01-cpu.md)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![feature(start)]
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![feature(start)]
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
|
109
examples/mgba_panic_handler.rs
Normal file
109
examples/mgba_panic_handler.rs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
#![no_std]
|
||||||
|
#![feature(start)]
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn __clzsi2(mut x: usize) -> usize {
|
||||||
|
let mut y: usize;
|
||||||
|
let mut n: usize = 32;
|
||||||
|
y = x >> 16;
|
||||||
|
if y != 0 {
|
||||||
|
n = n - 16;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
y = x >> 8;
|
||||||
|
if y != 0 {
|
||||||
|
n = n - 8;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
y = x >> 4;
|
||||||
|
if y != 0 {
|
||||||
|
n = n - 4;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
y = x >> 2;
|
||||||
|
if y != 0 {
|
||||||
|
n = n - 2;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
y = x >> 1;
|
||||||
|
if y != 0 {
|
||||||
|
n - 2
|
||||||
|
} else {
|
||||||
|
n - x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||||
|
unsafe {
|
||||||
|
const DEBUG_ENABLE_MGBA: *mut u16 = 0x4fff780 as *mut u16;
|
||||||
|
const DEBUG_OUTPUT_BASE: *mut u8 = 0x4fff600 as *mut u8;
|
||||||
|
const DEBUG_SEND_MGBA: *mut u16 = 0x4fff700 as *mut u16;
|
||||||
|
const DEBUG_SEND_FLAG: u16 = 0x100;
|
||||||
|
const DEBUG_FATAL: u16 = 0;
|
||||||
|
const DEBUG_ERROR: u16 = 1;
|
||||||
|
DEBUG_ENABLE_MGBA.write_volatile(0xC0DE);
|
||||||
|
if DEBUG_ENABLE_MGBA.read_volatile() == 0x1DEA {
|
||||||
|
// Give the location
|
||||||
|
if let Some(location) = info.location() {
|
||||||
|
let mut out_ptr = DEBUG_OUTPUT_BASE;
|
||||||
|
let line = location.line();
|
||||||
|
let mut line_bytes = [
|
||||||
|
(line / 10000) as u8,
|
||||||
|
((line / 1000) % 10) as u8,
|
||||||
|
((line / 1000) % 10) as u8,
|
||||||
|
((line / 10) % 10) as u8,
|
||||||
|
(line % 10) as u8,
|
||||||
|
];
|
||||||
|
for line_bytes_mut in line_bytes.iter_mut() {
|
||||||
|
*line_bytes_mut += b'0';
|
||||||
|
}
|
||||||
|
for b in "Panic: "
|
||||||
|
.bytes()
|
||||||
|
.chain(location.file().bytes())
|
||||||
|
.chain(", Line ".bytes())
|
||||||
|
.chain(line_bytes.iter().cloned())
|
||||||
|
.take(255)
|
||||||
|
{
|
||||||
|
out_ptr.write_volatile(b);
|
||||||
|
out_ptr = out_ptr.offset(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut out_ptr = DEBUG_OUTPUT_BASE;
|
||||||
|
for b in "Panic with no location info:".bytes().take(255) {
|
||||||
|
out_ptr.write_volatile(b);
|
||||||
|
out_ptr = out_ptr.offset(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_ERROR);
|
||||||
|
// Give the payload
|
||||||
|
if let Some(payload) = info.payload().downcast_ref::<&str>() {
|
||||||
|
let mut out_ptr = DEBUG_OUTPUT_BASE;
|
||||||
|
for b in payload.bytes().take(255) {
|
||||||
|
out_ptr.write_volatile(b);
|
||||||
|
out_ptr = out_ptr.offset(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut out_ptr = DEBUG_OUTPUT_BASE;
|
||||||
|
for b in "no payload".bytes().take(255) {
|
||||||
|
out_ptr.write_volatile(b);
|
||||||
|
out_ptr = out_ptr.offset(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_ERROR);
|
||||||
|
DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_FATAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[start]
|
||||||
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
|
unsafe {
|
||||||
|
(0x400_0000 as *mut u16).write_volatile(0x0403);
|
||||||
|
(0x600_0000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
|
||||||
|
(0x600_0000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
|
||||||
|
(0x600_0000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
|
||||||
|
panic!("fumoffu!");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue