mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-23 19:01:30 +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(
|
||||
&["-O", "binary",
|
||||
&format!("target/thumbv4-none-agb/release/examples/{}",name),
|
||||
&format!("target/example-{}.gba",name)])
|
||||
&format!("target/{}.gba",name)])
|
||||
.output().expect("failed to objcopy!");
|
||||
std::process::Command::new("gbafix").args(
|
||||
&[&format!("target/example-{}.gba",name)])
|
||||
&[&format!("target/{}.gba",name)])
|
||||
.output().expect("failed to gbafix!");
|
||||
}
|
||||
}
|
||||
|
|
18
README.md
18
README.md
|
@ -8,17 +8,19 @@
|
|||
|
||||
# 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
|
||||
detailed for you [in the 0th chapter of the
|
||||
book](https://rust-console.github.io/gba/00-introduction/03-development-setup.html) that goes with this
|
||||
crate.
|
||||
Writing a Rust program for the GBA requires a fair amount of special setup. All
|
||||
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).
|
||||
|
||||
If you've done the global setup once before and just want to get a new project
|
||||
started quickly we got you covered:
|
||||
If you've done the described global setup once before and just want to get a new
|
||||
project started quickly we got you covered:
|
||||
|
||||
```sh
|
||||
curl https://raw.githubusercontent.com/rust-console/gba/master/init.sh -sSf | bash -s APP_NAME
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# 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
|
||||
provides the technical advisement, reviews the PRs, and keeps my crazy in check.
|
||||
|
|
|
@ -21,8 +21,8 @@ goes:
|
|||
`hello_magic.rs`:
|
||||
|
||||
```rust
|
||||
#![feature(start)]
|
||||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
|
|
|
@ -1 +1,10 @@
|
|||
# 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
|
||||
|
||||
TODO: replace all this one "the rant" is finalized
|
||||
|
||||
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
|
||||
basically it means "get your grubby mitts off my stuff you over-eager compiler".
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
* [Newtype](00-introduction/05-newtype.md)
|
||||
* [Help and Resources](00-introduction/06-help_and_resources.md)
|
||||
* [Limitations](01-limitations/00-index.md)
|
||||
* [No Floats](01-limitations/01-no_floats.md)
|
||||
* [Core Only](01-limitations/02-core_only.md)
|
||||
* [No Std](01-limitations/01-no_std.md)
|
||||
* [Fixed Only](01-limitations/02-fixed_only.md)
|
||||
* [Volatile Destination](01-limitations/03-volatile_destination.md)
|
||||
* [Concepts](02-concepts/00-index.md)
|
||||
* [CPU](02-concepts/01-cpu.md)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#![feature(start)]
|
||||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
#[panic_handler]
|
||||
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