Revert "Deploy rust-console/gba to github.com/rust-console/gba.git:master"

This reverts commit 2f1c243742.
This commit is contained in:
Lokathor 2018-11-13 12:47:52 -07:00
parent 2f1c243742
commit a4b6a4e8b2
72 changed files with 2442 additions and 23 deletions

14
.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# The crt0.o file should, under the currently suggested build scheme, be
# recompiled every build anyway.
crt0.o

30
.travis.yml Normal file
View file

@ -0,0 +1,30 @@
language: rust
sudo: false
cache:
- cargo
rust:
- nightly
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.1" mdbook)
- cargo install-update -a
script:
- cargo check && cargo check --release
# at the moment we DO NOT run "cargo test" because of lang-item issues
- cd book && mdbook build
deploy:
provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
local-dir: docs
target-branch: master
keep-history: true
name: DocsBot
verbose: true
on:
branch: master

17
Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "gba"
description = "A crate (and book) for making GBA games with Rust."
version = "0.1.0"
authors = ["Lokathor <zefria@gmail.com>", "Ketsuban"]
repository = "https://github.com/rust-console/gba"
readme = "README.md"
keywords = ["gba"]
edition = "2018"
license = "Apache-2.0"
[dependencies]
gba-proc-macro = "0.1.1"
[profile.release]
lto = true
panic = "abort"

201
LICENSE-APACHE2.txt Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

17
README.md Normal file
View file

@ -0,0 +1,17 @@
[![License:Apache2](https://img.shields.io/badge/License-Apache2-green.svg)](https://www.apache.org/licenses/LICENSE-2.0)
# gba
A crate that helps you make GBA games
# 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/ch0/index.html) that goes with this
crate.
# Contribution
This crate is Apache2 licensed and any contributions you submit must also be
Apache2 licensed.

8
book/book.toml Normal file
View file

@ -0,0 +1,8 @@
[book]
title = "Rust GBA Tutorials"
authors = ["Lokathor"]
#description = "Rust GBA Tutorials."
[build]
build-dir = "../docs"
create-missing = true

15
book/src/SUMMARY.md Normal file
View file

@ -0,0 +1,15 @@
# Rust GBA Tutorials
* [Introduction](introduction.md)
* [Ch 0: Development Setup](ch0/index.md)
* [Ch 1: Hello GBA](ch1/index.md)
* [hello1](ch1/hello1.md)
* [IO Registers](ch1/io_registers.md)
* [The Display Control Register](ch1/the_display_control_register.md)
* [Video Memory Intro](ch1/video_memory_intro.md)
* [hello2](ch1/hello2.md)
* [Ch 2: User Input](ch2/index.md)
* [The Key Input Register](ch2/the_key_input_register.md)
* [The VCount Register](ch2/the_vcount_register.md)
* [light_cycle](ch2/light_cycle.md)

134
book/src/ch0/index.md Normal file
View file

@ -0,0 +1,134 @@
# Chapter 0: Development Setup
Before you can build a GBA game you'll have to follow some special steps to
setup the development environment. Perhaps unfortunately, there's enough detail
here to warrant a mini-chapter all on its own.
Before we begin I'd like to give a special thanks to **Ketsuban**, who is the
wizard that arranged for all of this to be able to happen and laid out the
details of the plan to the rest of the world.
## Per System Setup
Obviously you need your computer to have a working rust installation. However,
you'll also need to ensure that you're using a nightly toolchain. You can run
`rustup default nightly` to set nightly as the system wide default toolchain, or
you can use a [toolchain
file](https://github.com/rust-lang-nursery/rustup.rs#the-toolchain-file) to use
nightly just on a specific project, but either way we'll be assuming nightly
from now on.
Next you need [devkitpro](https://devkitpro.org/wiki/Getting_Started). They've
got a graphical installer for Windows, and `pacman` support on Linux. We'll be
using a few of their binutils for the `arm-none-eabi` target, and we'll also be
using some of their tools that are specific to GBA development, so _even if_ you
already have the right binutils for whatever reason, you'll still want devkitpro
for the `gbafix` utility.
* On Windows you'll want something like `C:\devkitpro\devkitARM\bin` and
`C:\devkitpro\tools\bin` to be [added to your
PATH](https://stackoverflow.com/q/44272416/455232), depending on where you
installed it to and such.
* On Linux you'll also want it to be added to your path, but if you're using
Linux I'll just assume you know how to do all that.
Finally, you'll need `cargo-xbuild`. Just run `cargo install cargo-xbuild` and
cargo will figure it all out for you.
## Per Project Setup
Now you'll need some particular files each time you want to start a new project.
You can find them in the root of the [rust-console/gba
repo](https://github.com/rust-console/gba).
* `thumbv4-none-agb.json` describes the overall GBA to cargo-xbuild so it knows
what to do. This is actually a somewhat made up target name since there's no
official target name. The GBA is essentially the same as a normal
`thumbv4-none-eabi` device, but we give it the "agb" signifier so that later
on we'll be able to use rust's `cfg` ability to allow our code to know if it's
specifically targeting a GBA or some other similar device (like an NDS).
* `crt0.s` describes some ASM startup stuff. If you have more ASM to place here
later on this is where you can put it. You also need to build it into a
`crt0.o` file before it can actually be used, but we'll cover that below.
* `linker.ld` tells the linker more critical info about the layout expectations
that the GBA has about our program.
## Compiling
The next steps only work once you've got some source code to build. If you need
a quick test, copy the `hello1.rs` file from our examples directory in the
repository.
Once you've got something to build, you perform the following steps:
* `arm-none-eabi-as crt0.s -o crt0.o`
* This builds your text format `crt0.s` file into object format `crt0.o`. You
don't need to perform it every time, only when `crt0.s` changes, but you
might as well do it every time so that you never forget to because it's a
practically instant operation.
* `cargo xbuild --target thumbv4-none-agb.json`
* This builds your Rust source. It accepts _most of_ the normal options, such
as `--release`, and options, such as `--bin foo` or `--examples`, that you'd
expect `cargo` to accept.
* You **can not** build and run tests this way, because they require `std`,
which the GBA doesn't have. You can still run some of your project's tests
with `cargo test`, but that builds for your local machine, so anything
specific to the GBA (such as reading and writing registers) won't be
testable that way. If you want to isolate and try out some piece code
running on the GBA you'll unfortunately have to make a demo for it in your
`examples/` directory and then run the demo in an emulator and see if it
does what you expect.
* The file extension is important. `cargo xbuild` takes it as a flag to
compile dependencies with the same sysroot, so you can include crates
normally. Well, creates that work in the GBA's limited environment, but you
get the idea.
At this point you have an ELF binary that some emulators can execute directly.
This is helpful because it'll have debug symbols and all that, assuming a debug
build. Specifically, [mgba 0.7 beta
1](https://mgba.io/2018/09/24/mgba-0.7-beta1/) can do it, and perhaps other
emulators can also do it.
However, if you want a "real" ROM that works in all emulators and that you could
transfer to a flash cart there's a little more to do.
* `arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/MODE/BIN_NAME target/ROM_NAME.gba`
* This will perform an [objcopy](https://linux.die.net/man/1/objcopy) on our
program. Here I've named the program `arm-none-eabi-objcopy`, which is what
devkitpro calls their version of `objcopy` that's specific to the GBA in the
Windows install. If the program isn't found under that name, have a look in
your installation directory to see if it's under a slightly different name
or something.
* As you can see from reading the man page, the `-O binary` option takes our
lovely ELF file with symbols and all that and strips it down to basically a
bare memory dump of the program.
* The next argument is the input file. You might not be familiar with how
`cargo` arranges stuff in the `target/` directory, and between RLS and
`cargo doc` and stuff it gets kinda crowded, so it goes like this:
* Since our program was built for a non-local target, first we've got a
directory named for that target, `thumbv4-none-agb/`
* Next, the "MODE" is either `debug/` or `release/`, depending on if we had
the `--release` flag included. You'll probably only be packing release
mode programs all the way into GBA roms, but it works with either mode.
* Finally, the name of the program. If your program is something out of the
project's `src/bin/` then it'll be that file's name, or whatever name you
configured for the bin in the `Cargo.toml` file. If your program is
something out of the project's `examples/` directory there will be a
similar `examples/` sub-directory first, and then the example's name.
* The final argument is the output of the `objcopy`, which I suggest putting
at just the top level of the `target/` directory. Really it could go
anywhere, but if you're using git then it's likely that your `.gitignore`
file is already setup to exclude everything in `target/`, so this makes sure
that your intermediate game builds don't get checked into your git.
* `gbafix target/ROM_NAME.gba`
* The `gbafix` tool also comes from devkitpro. The GBA is very picky about a
ROM's format, and `gbafix` patches the ROM's header and such so that it'll
work right. Unlike `objcopy`, this tool is custom built for GBA development,
so it works just perfectly without any arguments beyond the file name. The
ROM is patched in place, so we don't even need to specify a new destination.
And you're finally done!
Of course, you probably want to make a script for all that, but it's up to you.

193
book/src/ch1/hello1.md Normal file
View file

@ -0,0 +1,193 @@
# hello1
Ready? Here goes:
`hello1.rs`
```rust
#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
(0x04000000 as *mut u16).write_volatile(0x0403);
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
loop {}
}
}
```
Throw that into your project, build the program (as described back in Chapter
0), and give it a run. You should see a red, green, and blue dot close-ish to
the middle of the screen. If you don't, something already went wrong. Double
check things, phone a friend, write your senators, try asking Ketsuban on the
[Rust Community Discord](https://discordapp.com/invite/aVESxV8), until you're
able to get your three dots going.
## Explaining hello1
So, what just happened? Even if you're used to Rust that might look pretty
strange. We'll go over each part extra carefully.
```rust
#![feature(start)]
```
This enables the [start
feature](https://doc.rust-lang.org/beta/unstable-book/language-features/start.html),
which you would normally be able to read about in the unstable book, except that
the book tells you nothing at all except to look at the [tracking
issue](https://github.com/rust-lang/rust/issues/29633).
Basically, a GBA game is even more low-level than the _normal_ amount of
low-level that you get from Rust, so we have to tell the compiler to account for
that by specifying a `#[start]`, and we need this feature on to do that.
```rust
#![no_std]
```
There's no standard library available on the GBA, so we'll have to live a core
only life.
```rust
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
```
This sets our [panic
handler](https://doc.rust-lang.org/nightly/nomicon/panic-handler.html).
Basically, if we somehow trigger a panic, this is where the program goes.
However, right now we don't know how to get any sort of message out to the user
so... we do nothing at all. We _can't even return_ from here, so we just sit in
an infinite loop. The player will have to reset the universe from the outside.
The `#[cfg(not(test))]` part makes this item only exist in the program when
we're _not_ in a test build. This is so that `cargo test` and such work right as
much as possible.
```rust
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
```
This is our `#[start]`. We call it `main`, but it's not like a `main` that you'd
see in a Rust program. It's _more like_ the sort of `main` that you'd see in a C
program, but it's still **not** that either. If you compile a `#[start]` program
for a target with an OS such as `arm-none-eabi-nm` you can open up the debug
info and see that your result will have the symbol for the C `main` along side
the symbol for the start `main` that we write here. Our start `main` is just its
own unique thing, and the inputs and outputs have to be like that because that's
how `#[start]` is specified to work in Rust.
If you think about it for a moment you'll probably realize that, those inputs
and outputs are totally useless to us on a GBA. There's no OS on the GBA to call
our program, and there's no place for our program to "return to" when it's done.
Side note: if you want to learn more about stuff "before main gets called" you
can watch a great [CppCon talk](https://www.youtube.com/watch?v=dOfucXtyEsU) by
Matt Godbolt (yes, that Godbolt) where he delves into quite a bit of it. The
talk doesn't really apply to the GBA, but it's pretty good.
```rust
unsafe {
```
I hope you're all set for some `unsafe`, because there's a lot of it to be had.
```rust
(0x04000000 as *mut u16).write_volatile(0x0403);
```
Sure!
```rust
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
```
Ah, of course.
```rust
loop {}
}
}
```
And, as mentioned above, there's no place for a GBA program to "return to", so
we can't ever let `main` try to return there. Instead, we go into an infinite
`loop` that does nothing. The fact that this doesn't ever return an `isize`
value doesn't seem to bother Rust, because I guess we're at least not returning
any other type of thing instead.
Fun fact: unlike in C++, an infinite loop with no side effects isn't Undefined
Behavior for us rustaceans... _semantically_. In truth LLVM has a [known
bug](https://github.com/rust-lang/rust/issues/28728) in this area, so we won't
actually be relying on empty loops in any future programs.
## All Those Magic Numbers
Alright, I cheated quite a bit in the middle there. The program works, but I
didn't really tell you why because I didn't really tell you what any of those
magic numbers mean or do.
* `0x04000000` is the address of an IO Register called the Display Control.
* `0x06000000` is the start of Video RAM.
So we write some magic to the display control register once, then we write some
other magic to three locations of magic to the Video RAM. We get three dots,
each in their own location... so that second part makes sense at least.
We'll get into the magic number details in the other sections of this chapter.
## Sidebar: Volatile
We'll get into what all that is in a moment, but first let's ask ourselves: Why
are we doing _volatile_ writes? You've probably never used it before at all.
What is volatile anyway?
Well, the optimizer is pretty aggressive some of the time, and so it'll skip
reads and writes when it thinks can. Like if you write to a pointer once, and
then again a moment later, and it didn't see any other reads in between, it'll
think that it can just skip doing that first write since it'll get overwritten
anyway. Sometimes that's right, but sometimes it's wrong.
Marking a read or write as _volatile_ tells the compiler that it really must do
that action, and in the exact order that we wrote it out. It says that there
might even be special hardware side effects going on that the compiler isn't
aware of. In this case, the write to the display control register sets a video
mode, and the writes to the Video RAM set pixels that will show up on the
screen.
Similar to "atomic" operations you might have heard about, all volatile
operations are enforced to happen in the exact order that you specify them, but
only relative to other volatile operations. So something like
```rust
c.volatile_write(5);
a += b;
d.volatile_write(7);
```
might end up changing `a` either before or after the change to `c` (since the
value of `a` doesn't affect the write to `c`), but the write to `d` will
_always_ happen after the write to `c` even though the compiler doesn't see any
direct data dependency there.
If you ever use volatile stuff on other platforms it's important to note that
volatile doesn't make things thread-safe, you still need atomic for that.
However, the GBA doesn't have threads, so we don't have to worry about thread
safety concerns.

114
book/src/ch1/hello2.md Normal file
View file

@ -0,0 +1,114 @@
# hello2
Okay so let's have a look again:
`hello1`
```rust
#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
(0x04000000 as *mut u16).write_volatile(0x0403);
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
loop {}
}
}
```
Now let's clean this up so that it's clearer what's going on.
First we'll label that display control stuff:
```rust
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
```
Next we make some const values for the actual pixel drawing
```rust
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
```
And then we want a small helper function for putting together a color value.
Happily, this one can even be declared as a const function. At the time of
writing, we've got the "minimal const fn" support in nightly. It really is quite
limited, but I'm happy to let rustc and LLVM pre-compute as much as they can
when it comes to the GBA's tiny CPU.
```rust
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
blue << 10 | green << 5 | red
}
```
Finally, we'll make a function for drawing a pixel in Mode 3. Even though it's
just a one-liner, having the "important parts" be labeled as function arguments
usually helps you think about it a lot better.
```rust
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}
```
So now we've got this:
`hello2`
```rust
#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
DISPCNT.write_volatile(MODE3 | BG2);
mode3_pixel(120, 80, rgb16(31, 0, 0));
mode3_pixel(136, 80, rgb16(0, 31, 0));
mode3_pixel(120, 96, rgb16(0, 0, 31));
loop {}
}
}
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
blue << 10 | green << 5 | red
}
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}
```
Exact same program that we started with, but much easier to read.
Of course, in the full `gba` crate that this book is a part of we have these and
other elements all labeled and sorted out for you. Still, for educational
purposes it's often best to do it yourself at least once.

10
book/src/ch1/index.md Normal file
View file

@ -0,0 +1,10 @@
# Ch 1: Hello GBA
Traditionally a person writes a "hello, world" program so that they can test
that their development environment is setup properly and to just get a feel for
using the tools involved. To get an idea of what a small part of a source file
will look like. All that stuff.
Normally, you write a program that prints "hello, world" to the terminal. The
GBA has no terminal, but it does have a screen, so instead we're going to draw
three dots to the screen.

View file

@ -0,0 +1,33 @@
# IO Registers
The GBA has a large number of **IO Registers** (not to be confused with CPU
registers). These are special memory locations from `0x04000000` to
`0x040003FE`. GBATEK has a [full
list](http://problemkaputt.de/gbatek.htm#gbaiomap), but we only need to learn
about a few of them at a time as we go, so don't be worried.
The important facts to know about IO Registers are these:
* Each has their own specific size. Most are `u16`, but some are `u32`.
* All of them must be accessed in a `volatile` style.
* Each register is specifically readable or writable or both. Actually, with
some registers there are even individual bits that are read-only or
write-only.
* If you write to a read-only position, those writes are simply ignored. This
mostly matters if a writable register contains a read-only bit (such as the
Display Control, next section).
* If you read from a write-only position, you get back values that are
[basically
nonsense](http://problemkaputt.de/gbatek.htm#gbaunpredictablethings). There
aren't really any registers that mix writable bits with read only bits, so
you're basically safe here. The only (mild) concern is that when you write a
value into a write-only register you need to keep track of what you wrote
somewhere else if you want to know what you wrote (such to adjust an offset
value by +1, or whatever).
* You can always check GBATEK to be sure, but if I don't mention it then a bit
is probably both read and write.
* Some registers have invalid bit patterns. For example, the lowest three bits
of the Display Control register can't legally be set to the values 6 or 7.
When talking about bit positions, the numbers are _zero indexed_ just like an
array index is.

View file

@ -0,0 +1,112 @@
# The Display Control Register
The display control register is our first actual IO Register. GBATEK gives it the
shorthand [DISPCNT](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol), so
you might see it under that name if you read other guides.
Among IO Registers, it's one of the simpler ones, but it's got enough complexity
that we can get a hint of what's to come.
Also it's the one that you basically always need to set at least once in every
GBA game, so it's a good starting one to go over for that reason too.
The display control register holds a `u16` value, and is located at `0x0400_0000`.
Many of the bits here won't mean much to you right now. **That is fine.** You do
NOT need to memorize them all or what they all do right away. We'll just skim
over all the parts of this register to start, and then we'll go into more detail
in later chapters when we need to come back and use more of the bits.
## Video Modes
The lowest three bits (0-2) let you select from among the GBA's six video modes.
You'll notice that 3 bits allows for eight modes, but the values 6 and 7 are
prohibited.
Modes 0, 1, and 2 are "tiled" modes. These are actually the modes that you
should eventually learn to use as much as possible. It lets the GBA's limited
video hardware do as much of the work as possible, leaving more of your CPU time
for gameplay computations. However, they're also complex enough to deserve their
own demos and chapters later on, so that's all we'll say about them for now.
Modes 3, 4, and 5 are "bitmap" modes. These let you write individual pixels to
locations on the screen.
* **Mode 3** is full resolution (240w x 160h) RGB15 color. You might not be used
to RGB15, since modern computers have 24 or 32 bit colors. In RGB15, there's 5
bits for each color channel stored within a `u16` value, and the highest bit is
simply ignored.
* **Mode 4** is full resolution paletted color. Instead of being a `u16` color, each
pixel value is a `u8` palette index entry, and then the display uses the
palette memory (which we'll talk about later) to store the actual color data.
Since each pixel is half sized, we can fit twice as many. This lets us have
two "pages". At any given moment only one page is active, and you can draw to
the other page without the user noticing. You set which page to show with
another bit we'll get to in a moment.
* **Mode 5** is full color, but also with pages. This means that we must have a
reduced resolution to compensate (video memory is only so big!). The screen is
effectively only 160w x 128h in this mode.
## CGB Mode
Bit 3 is effectively read only. Technically it can be flipped using a BIOS call,
but when you write to the display control register normally it won't write to
this bit, so we'll call it effectively read only.
This bit is on if the CPU is in CGB mode.
## Page Flipping
Bit 4 lets you pick which page to use. This is only relevent in video modes 4 or
5, and is just ignored otherwise. It's very easy to remember: when the bit is 0
the 0th page is used, and when the bit is 1 the 1st page is used.
The second page always starts at `0x0600_A000`.
## OAM, VRAM, and Blanking
Bit 5 lets you access OAM during HBlank if enabled. This is cool, but it reduces
the maximum sprites per scanline, so it's not default.
Bit 6 lets you adjust if the GBA should treat Object Character VRAM as being 2d
(off) or 1d (on). This particular control can be kinda tricky to wrap your head
around, so we'll be sure to have some extra diagrams in the chapter that deals
with it.
Bit 7 forces the screen to stay in VBlank as long as it's set. This allows the
fastest use of the VRAM, Palette, and Object Attribute Memory. Obviously if you
leave this on for too long the player will notice a blank screen, but it might
be okay to use for a moment or two every once in a while.
## Screen Layers
Bits 8 through 11 control if Background layers 0 through 3 should be active.
Bit 12 affects the Object layer.
Note that not all background layers are available in all video modes:
* Mode 0: all
* Mode 1: 0/1/2
* Mode 2: 2/3
* Mode 3/4/5: 2
Bit 13 and 14 enable the display of Windows 0 and 1, and Bit 15 enables the
object display window. We'll get into how windows work later on, they let you do
some nifty graphical effects.
## In Conclusion...
So what did we do to the display control register in `hello1`?
```rust
(0x04000000 as *mut u16).write_volatile(0x0403);
```
First let's [convert that to
binary](https://www.wolframalpha.com/input/?i=0x0403), and we get
`0b100_0000_0011`. So, that's setting Mode 3 with background 2 enabled and
nothing else special.
However, I think we can do better than that. This is a prime target for more
newtyping as we attempt a `hello2` program.

View file

@ -0,0 +1,113 @@
# Video Memory Intro
The GBA's Video RAM is 96k stretching from `0x0600_0000` to `0x0601_7FFF`.
The Video RAM can only be accessed totally freely during a Vertical Blank (aka
"VBlank", though sometimes I forget and don't capitalize it properly). At other
times, if the CPU tries to touch the same part of video memory as the display
controller is accessing then the CPU gets bumped by a cycle to avoid a clash.
Annoyingly, VRAM can only be properly written to in 16 and 32 bit segments (same
with PALRAM and OAM). If you try to write just an 8 bit segment, then both parts
of the 16 bit segment get the same value written to them. In other words, if you
write the byte `5` to `0x0600_0000`, then both `0x0600_0000` and ALSO
`0x0600_0001` will have the byte `5` in them. We have to be extra careful when
trying to set an individual byte, and we also have to be careful if we use
`memcopy` or `memset` as well, because they're byte oriented by default and
don't know to follow the special rules.
## RGB15
As I said before, RGB15 stores a color within a `u16` value using 5 bits for
each color channel.
```rust
pub const RED: u16 = 0b0_00000_00000_11111;
pub const GREEN: u16 = 0b0_00000_11111_00000;
pub const BLUE: u16 = 0b0_11111_00000_00000;
```
In Mode 3 and Mode 5 we write direct color values into VRAM, and in Mode 4 we
write palette index values, and then the color values go into the PALRAM.
## Mode 3
Mode 3 is pretty easy. We have a full resolution grid of rgb15 pixels. There's
160 rows of 240 pixels each, with the base address being the top left corner. A
particular pixel uses normal "2d indexing" math:
```rust
let row_five_col_seven = 5 + (7 * SCREEN_WIDTH);
```
To draw a pixel, we just write a value at the address for the row and col that
we want to draw to.
## Mode 4
Mode 4 introduces page flipping. Instead of one giant page at `0x0600_0000`,
there's Page 0 at `0x0600_0000` and then Page 1 at `0x0600_A000`. The resolution
for each page is the same as above, but instead of writing `u16` values, the
memory is treated as `u8` indexes into PALRAM. The PALRAM starts at
`0x0500_0000`, and there's enough space for 256 palette entries (each a `u16`).
To set the color of a palette entry we just do a normal `u16` write_volatile.
```rust
(0x0500_0000 as *mut u16).offset(target_index).write_volatile(new_color)
```
To draw a pixel we set the palette entry that we want the pixel to use. However,
we must remember the "minimum size" write limitation that applies to VRAM. So,
if we want to change just a single pixel at a time we must
1) Read the full `u16` it's a part of.
2) Clear the half of the `u16` we're going to replace
3) Write the half of the `u16` we're going to replace with the new value
4) Write that result back to the address.
So, the math for finding a byte offset is the same as Mode 3 (since they're both
a 2d grid). If the byte offset is EVEN it'll be the high bits of the `u16` at
half the byte offset rounded down. If the offset is ODD it'll be the low bits of
the `u16` at half the byte.
Does that make sense?
* If we want to write pixel (0,0) the byte offset is 0, so we change the high
bits of `u16` offset 0. Then we want to write to (1,0), so the byte offset is
1, so we change the low bits of `u16` offset 0. The pixels are next to each
other, and the target bytes are next to each other, good so far.
* If we want to write to (5,6) that'd be byte `5 + 6 * 240 = 1445`, so we'd
target the low bits of `u16` offset `floor(1445/2) = 722`.
As you can see, trying to write individual pixels in Mode 4 is mostly a bad
time. Fret not! We don't _have_ to write individual bytes. If our data is
arranged correctly ahead of time we can just write `u16` or `u32` values
directly. The video hardware doesn't care, it'll get along just fine.
## Mode 5
Mode 5 is also a two page mode, but instead of compressing the size of a pixel's
data to fit in two pages, we compress the resolution.
Mode 5 is full `u16` color, but only 160w x 128h per page.
## In Conclusion...
So what got written into VRAM in `hello1`?
```rust
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
```
So at pixels `(120,80)`, `(136,80)`, and `(120,96)` we write three values. Once
again we probably need to [convert them](https://www.wolframalpha.com/) into
binary to make sense of it.
* 0x001F: 0b11111
* 0x03E0: 0b11111_00000
* 0x7C00: 0b11111_00000_00000
Ah, of course, a red pixel, a green pixel, and a blue pixel.

22
book/src/ch2/index.md Normal file
View file

@ -0,0 +1,22 @@
# Ch 2: User Input
It's all well and good to draw three pixels, but they don't do anything yet. We
want them to do something, and for that we need to get some input from the user.
The GBA, as I'm sure you know, has an arrow pad, A and B, L and R, Start and
Select. That's a little more than the NES/GB/CGB had, and a little less than the
SNES had. As you can guess, we get key state info from an IO register.
Also, we will need a way to keep the program from running "too fast". On a
modern computer or console you do this with vsync info from the GPU and Monitor,
and on the GBA we'll be using vsync info from an IO register that tracks what
the display hardware is doing.
As a way to apply our knowledge We'll make a simple "light cycle" game where
your dot leaves a trail behind them and you die if you go off the screen or if
you touch your own trail. We just make a copy of `hello2.rs` named
`light_cycle.rs` and then fill it in as we go through the chapter. Normally you
might not place the entire program into a single source file, particularly as it
grows over time, but since these are small examples it's much better to have
them be completely self contained than it is to have them be "properly
organized" for the long term.

119
book/src/ch2/light_cycle.md Normal file
View file

@ -0,0 +1,119 @@
# light_cycle
Now let's make a game of "light_cycle" with our new knowledge.
## Gameplay
`light_cycle` is pretty simple, and very obvious if you've ever seen Tron. The
player moves around the screen with a trail left behind them. They die if they
go off the screen or if they touch their own trail.
## Operations
We need some better drawing operations this time around.
```rust
pub unsafe fn mode3_clear_screen(color: u16) {
let color = color as u32;
let bulk_color = color << 16 | color;
let mut ptr = VRAM as *mut u32;
for _ in 0..SCREEN_HEIGHT {
for _ in 0..(SCREEN_WIDTH / 2) {
ptr.write_volatile(bulk_color);
ptr = ptr.offset(1);
}
}
}
pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}
pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read_volatile()
}
```
The draw pixel and read pixel are both pretty obvious. What's new is the clear
screen operation. It changes the `u16` color into a `u32` and then packs the
value in twice. Then we write out `u32` values the whole way through screen
memory. This means we have to do less write operations overall, and so the
screen clear is twice as fast.
Now we just have to fill in the main function:
```rust
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
DISPCNT.write_volatile(MODE3 | BG2);
}
let mut px = SCREEN_WIDTH / 2;
let mut py = SCREEN_HEIGHT / 2;
let mut color = rgb16(31, 0, 0);
loop {
// read the input for this frame
let this_frame_keys = read_key_input();
// adjust game state and wait for vblank
px += 2 * this_frame_keys.column_direction() as isize;
py += 2 * this_frame_keys.row_direction() as isize;
wait_until_vblank();
// draw the new game and wait until the next frame starts.
unsafe {
if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT {
// out of bounds, reset the screen and position.
mode3_clear_screen(0);
color = color.rotate_left(5);
px = SCREEN_WIDTH / 2;
py = SCREEN_HEIGHT / 2;
} else {
let color_here = mode3_read_pixel(px, py);
if color_here != 0 {
// crashed into our own line, reset the screen
mode3_clear_screen(0);
color = color.rotate_left(5);
} else {
// draw the new part of the line
mode3_draw_pixel(px, py, color);
mode3_draw_pixel(px, py + 1, color);
mode3_draw_pixel(px + 1, py, color);
mode3_draw_pixel(px + 1, py + 1, color);
}
}
}
wait_until_vdraw();
}
}
```
Oh that's a lot more than before!
First we set Mode 3 and Background 2, we know about that.
Then we're going to store the player's x and y, along with a color value for
their light cycle. Then we enter the core loop.
We read the keys for input, and then do as much as we can without touching video
memory. Since we're using video memory as the place to store the player's light
trail, we can't do much, we just update their position and wait for VBlank to
start. The player will be a 2x2 square, so the arrows will move you 2 pixels per
frame.
Once we're in VBlank we check to see what kind of drawing we're doing. If the
player has gone out of bounds, we clear the screen, rotate their color, and then
reset their position. Why rotate the color? Just because it's fun to have
different colors.
Next, if the player is in bounds we read the video memory for their position. If
it's not black that means we've been here before and the player has crashed into
their own line. In this case, we reset the game without moving them to a new
location.
Finally, if the player is in bounds and they haven't crashed, we write their color into memory at this position.
Regardless of how it worked out, we hold here until vdraw starts before going to
the next loop.

View file

@ -0,0 +1,211 @@
# The Key Input Register
The Key Input Register is our next IO register. Its shorthand name is
[KEYINPUT](http://problemkaputt.de/gbatek.htm#gbakeypadinput) and it's a `u16`
at `0x4000130`. The entire register is obviously read only, you can't tell the
GBA what buttons are pressed.
Each button is exactly one bit:
| Bit | Button |
|:---:|:------:|
| 0 | A |
| 1 | B |
| 2 | Select |
| 3 | Start |
| 4 | Right |
| 5 | Left |
| 6 | Up |
| 7 | Down |
| 8 | R |
| 9 | L |
The higher bits above are not used at all.
Similar to other old hardware devices, the convention here is that a button's
bit is **clear when pressed, active when released**. In other words, when the
user is not touching the device at all the KEYINPUT value will read
`0b0000_0011_1111_1111`. There's similar values for when the user is pressing as
many buttons as possible, but since the left/right and up/down keys are on an
arrow pad the value can never be 0 since you can't ever press every single key
at once.
When dealing with key input, the register always shows the exact key values at
any moment you read it. Obviously that's what it should do, but what it means to
you as a programmer is that you should usually gather input once at the top of a
game frame and then use that single input poll as the input values across the
whole game frame.
Of course, you might want to know if a user's key state changed from frame to
frame. That's fairly easy too: We just store the last frame keys as well as the
current frame keys (it's only a `u16`) and then we can xor the two values.
Anything that shows up in the xor result is a key that changed. If it's changed
and it's now down, that means it was pushed this frame. If it's changed and it's
now up, that means it was released this frame.
The other major thing you might frequently want is to know "which way" the arrow
pad is pointing: Up/Down/None and Left/Right/None. Sounds like an enum to me.
Except that often time we'll have situations where the direction just needs to
be multiplied by a speed and applied as a delta to a position. We want to
support that as well as we can too.
## Key Input Code
Let's get down to some code. First we want to make a way to read the address as
a `u16` and then wrap that in our newtype which will implement methods for
reading and writing the key bits.
```rust
pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16;
/// A newtype over the key input state of the GBA.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(transparent)]
pub struct KeyInputSetting(u16);
pub fn read_key_input() -> KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.read_volatile()) }
}
```
Now we want a way to check if a key is _being pressed_, since that's normally
how we think of things as a game designer and even as a player. That is, usually
you'd say "if you press A, then X happens" instead of "if you don't press A,
then X does not happen".
Normally we'd pick a constant for the bit we want, `&` it with our value, and
then check for `val != 0`. Since the bit we're looking for is `0` in the "true"
state we still pick the same constant and we still do the `&`, but we test with
`== 0`. Practically the same, right? Well, since I'm asking a rhetorical
question like that you can probably already guess that it's not the same. I was
shocked to learn this too.
All we have to do is ask our good friend
[Godbolt](https://rust.godbolt.org/z/d-8oCe) what's gonna happen when the code
compiles. The link there has the page set for the `stable` 1.30 compiler just so
that the link results stay consistent if you read this book in a year or
something. Also, we've set the target to `thumbv6m-none-eabi`, which is a
slightly later version of ARM than the actual GBA, but it's close enough for
just checking. Of course, in a full program small functions like these will
probably get inlined into the calling code and disappear entirely as they're
folded and refolded by the compiler, but we can just check.
It turns out that the `!=0` test is 4 instructions and the `==0` test is 6
instructions. Since we want to get savings where we can, and we'll probably
check the keys of an input often enough, we'll just always use a `!=0` test and
then adjust how we initially read the register to compensate.
```rust
pub fn read_key_input() -> KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.read_volatile() ^ 0b1111_1111_1111_1111) }
}
```
Now we add a method for seeing if a key is pressed. In the full library there's
a more advanced version of this that's built up via macro, but for this example
we'll just name a bunch of `const` values and then have a method that takes a
value and says if that bit is on.
```rust
pub const KEY_A: u16 = 1 << 0;
pub const KEY_B: u16 = 1 << 1;
pub const KEY_SELECT: u16 = 1 << 2;
pub const KEY_START: u16 = 1 << 3;
pub const KEY_RIGHT: u16 = 1 << 4;
pub const KEY_LEFT: u16 = 1 << 5;
pub const KEY_UP: u16 = 1 << 6;
pub const KEY_DOWN: u16 = 1 << 7;
pub const KEY_R: u16 = 1 << 8;
pub const KEY_L: u16 = 1 << 9;
impl KeyInputSetting {
pub fn contains(&self, key: u16) -> bool {
(self.0 & key) != 0
}
}
```
Because each key is a unique bit you can even check for more than one key at
once by just adding two key values together.
```rust
let input_contains_a_and_l = input.contains(KEY_A + KEY_L);
```
And we wanted to save the state of an old frame and compare it to the current
frame to see what was different:
```rust
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
KeyInputSetting(self.0 ^ other.0)
}
```
Anything that's "in" the difference output is a key that _changed_, and then if
the key reads as pressed this frame that means it was just pressed. The exact
mechanics of all the ways you might care to do something based on new key
presses is obviously quite varied, but it might be something like this:
```rust
let this_frame_diff = this_frame_input.difference(last_frame_input);
if this_frame_diff.contains(KEY_B) && this_frame_input.contains(KEY_B) {
// the user just pressed B, react in some way
}
```
And for the arrow pad, we'll make an enum that easily casts into `i32`. Whenever
we're working with stuff we can try to use `i32` / `isize` as often as possible
just because it's easier on the GBA's CPU if we stick to its native number size.
Having it be an enum lets us use `match` and be sure that we've covered all our
cases.
```rust
/// A "tribool" value helps us interpret the arrow pad.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(i32)]
pub enum TriBool {
Minus = -1,
Neutral = 0,
Plus = +1,
}
```
Now, how do we determine _which way_ is plus or minus? Well... I don't know.
Really. I'm not sure what the best one is because the GBA really wants the
origin at 0,0 with higher rows going down and higher cols going right. On the
other hand, all the normal math you and I learned in school is oriented with
increasing Y being upward on the page. So, at least for this demo, we're going
to go with what the GBA wants us to do and give it a try. If we don't end up
confusing ourselves then we can stick with that. Maybe we can cover it over
somehow later on.
```rust
pub fn column_direction(&self) -> TriBool {
if self.contains(KEY_RIGHT) {
TriBool::Plus
} else if self.contains(KEY_LEFT) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
pub fn row_direction(&self) -> TriBool {
if self.contains(KEY_DOWN) {
TriBool::Plus
} else if self.contains(KEY_UP) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
```
So then in our game, every frame we can check for `column_direction` and
`row_direction` and then apply those to the player's current position to make
them move around the screen.
With that settled I think we're all done with user input for now. There's some
other things to eventually know about like key interrupts that you can set and
stuff, but we'll cover that later on because it's not necessary right now.

View file

@ -0,0 +1,71 @@
# The VCount Register
There's an IO register called
[VCOUNT](http://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus) that shows
you, what else, the Vertical (row) COUNT(er). It's a `u16` at address
`0x0400_0006`, and it's how we'll be doing our very poor quality vertical sync
code to start.
* **What makes it poor?** Well, we're just going to read from the vcount value as
often as possible every time we need to wait for a specific value to come up,
and then proceed once it hits the point we're looking for.
* **Why is this bad?** Because we're making the CPU do a lot of useless work,
which uses a lot more power that necessary. Even if you're not on an actual
GBA you might be running inside an emulator on a phone or other handheld. You
wanna try to save battery if all you're doing with that power use is waiting
instead of making a game actually do something.
* **Can we do better?** We can, but not yet. The better way to do things is to
use a BIOS call to put the CPU into low power mode until a VBlank interrupt
happens. However, we don't know about interrupts yet, and we don't know about
BIOS calls yet, so we'll do the basic thing for now and then upgrade later.
So the way that display hardware actually displays each frame is that it moves a
tiny pointer left to right across each pixel row one pixel at a time. When it's
within the actual screen width (240px) it's drawing out those pixels. Then it
goes _past_ the edge of the screen for 68px during a period known as the
"horizontal blank" (HBlank). Then it starts on the next row and does that loop
over again. This happens for the whole screen height (160px) and then once again
it goes past the last row for another 68px into a "vertical blank" (VBlank)
period.
* One pixel is 4 CPU cycles
* HDraw is 240 pixels, HBlank is 68 pixels (1,232 cycles per full scanline)
* VDraw is 150 scanlines, VBlank is 68 scanlines (280,896 cycles per full refresh)
Now you may remember some stuff from the display control register section where
it was mentioned that some parts of memory are best accessed during VBlank, and
also during hblank with a setting applied. These blanking periods are what was
being talked about. At other times if you attempt to access video or object
memory you (the CPU) might try touching the same memory that the display device
is trying to use, in which case you get bumped back a cycle so that the display
can finish what it's doing. Also, if you really insist on doing video memory
changes while the screen is being drawn then you might get some visual glitches.
If you can, just prepare all your changes ahead of time and then assign then all
quickly during the blank period.
So first we want a way to check the vcount value at all:
```rust
pub const VCOUNT: *mut u16 = 0x0400_0006 as *mut u16;
pub fn read_vcount() -> u16 {
unsafe { VCOUNT.read_volatile() }
}
```
Then we want two little helper functions to wait until VBlank and vdraw.
```rust
pub const SCREEN_HEIGHT: isize = 160;
pub fn wait_until_vblank() {
while read_vcount() < SCREEN_HEIGHT as u16 {}
}
pub fn wait_until_vdraw() {
while read_vcount() >= SCREEN_HEIGHT as u16 {}
}
```
And... that's it. No special types to be made this time around, it's just a
number we read out of memory.

28
book/src/introduction.md Normal file
View file

@ -0,0 +1,28 @@
# Introduction
Here's a book that'll help you program in Rust on the GBA.
It's very "work in progress". At the moment there's only one demo program.
## Getting Help
If you want to contact us you should join the [Rust Community
Discord](https://discordapp.com/invite/aVESxV8) and ask in the `#gamedev`
channel. `Ketsuban` is the wizard who knows much more about how it all works,
and `Lokathor` is the fool who decided to write a crate and book for it.
If it's _not_ a GBA specific question then you can also ask any of the few
hundred other folks in the server as well.
## Other Works
If you want to read more about developing on the GBA there are some other good resources as well:
* [Tonc](https://www.coranac.com/tonc/text/toc.htm), a tutorial series written
for C, but it's what I based the ordering of this book's sections on.
* [GBATEK](http://problemkaputt.de/gbatek.htm), a homebrew tech manual for
GBA/NDS/DSi. We will regularly link to parts of it when talking about various
bits of the GBA.
* [CowBite](https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm) is another
tech specification that's more GBA specific. It's sometimes got more ASCII
art diagrams and example C struct layouts than GBATEK does.

14
build.bat Normal file
View file

@ -0,0 +1,14 @@
@rem Build the crt0 file before we begin
arm-none-eabi-as crt0.s -o crt0.o
@rem Build all examples, both debug and release
cargo xbuild --examples --target thumbv4-none-agb.json
cargo xbuild --examples --target thumbv4-none-agb.json --release
@echo Packing examples into ROM files...
@for %%I in (.\examples\*.*) do @(
echo %%~nI
arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/release/examples/%%~nI target/example-%%~nI.gba >nul
gbafix target/example-%%~nI.gba >nul
)

34
crt0.s Normal file
View file

@ -0,0 +1,34 @@
.arm
__start:
b .Linit
@ ROM header will be filled in by gbafix
.fill 188, 1, 0
.Linit:
@ set IRQ stack pointer
mov r0, #0x12
msr CPSR_cf, r0
ldr sp, =0x3007fa0
@ set user stack pointer
mov r0, #0x1f
msr CPSR_cf, r0
ldr sp, =0x3007f00
@ copy .data section to IWRAM
ldr r0, =__data_lma @ source address
ldr r1, =__data_start @ destination address
ldr r3, =__data_end
sub r2, r3, r1
beq .Lskip @ don't try to copy an empty .data section
add r2, #3
mov r2, r2, asr #2 @ length (in words)
add r2, #0x04000000 @ copy by words
swi 0xb0000
.Lskip:
@ jump to user code
ldr r0, =main
bx r0
.pool

View file

Before

Width:  |  Height:  |  Size: 348 KiB

After

Width:  |  Height:  |  Size: 348 KiB

View file

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -139,13 +139,6 @@
<a class="header" href="#introduction" id="introduction"><h1>Introduction</h1></a> <a class="header" href="#introduction" id="introduction"><h1>Introduction</h1></a>
<p>Here's a book that'll help you program in Rust on the GBA.</p> <p>Here's a book that'll help you program in Rust on the GBA.</p>
<p>It's very &quot;work in progress&quot;. At the moment there's only one demo program.</p> <p>It's very &quot;work in progress&quot;. At the moment there's only one demo program.</p>
<a class="header" href="#getting-help" id="getting-help"><h2>Getting Help</h2></a>
<p>If you want to contact us you should join the <a href="https://discordapp.com/invite/aVESxV8">Rust Community
Discord</a> and ask in the <code>#gamedev</code>
channel. <code>Ketsuban</code> is the wizard who knows much more about how it all works,
and <code>Lokathor</code> is the fool who decided to write a crate and book for it.</p>
<p>If it's <em>not</em> a GBA specific question then you can also ask any of the few
hundred other folks in the server as well.</p>
<a class="header" href="#other-works" id="other-works"><h2>Other Works</h2></a> <a class="header" href="#other-works" id="other-works"><h2>Other Works</h2></a>
<p>If you want to read more about developing on the GBA there are some other good resources as well:</p> <p>If you want to read more about developing on the GBA there are some other good resources as well:</p>
<ul> <ul>

View file

@ -139,13 +139,6 @@
<a class="header" href="#introduction" id="introduction"><h1>Introduction</h1></a> <a class="header" href="#introduction" id="introduction"><h1>Introduction</h1></a>
<p>Here's a book that'll help you program in Rust on the GBA.</p> <p>Here's a book that'll help you program in Rust on the GBA.</p>
<p>It's very &quot;work in progress&quot;. At the moment there's only one demo program.</p> <p>It's very &quot;work in progress&quot;. At the moment there's only one demo program.</p>
<a class="header" href="#getting-help" id="getting-help"><h2>Getting Help</h2></a>
<p>If you want to contact us you should join the <a href="https://discordapp.com/invite/aVESxV8">Rust Community
Discord</a> and ask in the <code>#gamedev</code>
channel. <code>Ketsuban</code> is the wizard who knows much more about how it all works,
and <code>Lokathor</code> is the fool who decided to write a crate and book for it.</p>
<p>If it's <em>not</em> a GBA specific question then you can also ask any of the few
hundred other folks in the server as well.</p>
<a class="header" href="#other-works" id="other-works"><h2>Other Works</h2></a> <a class="header" href="#other-works" id="other-works"><h2>Other Works</h2></a>
<p>If you want to read more about developing on the GBA there are some other good resources as well:</p> <p>If you want to read more about developing on the GBA there are some other good resources as well:</p>
<ul> <ul>

View file

View file

@ -139,13 +139,6 @@
<a class="header" href="#introduction" id="introduction"><h1>Introduction</h1></a> <a class="header" href="#introduction" id="introduction"><h1>Introduction</h1></a>
<p>Here's a book that'll help you program in Rust on the GBA.</p> <p>Here's a book that'll help you program in Rust on the GBA.</p>
<p>It's very &quot;work in progress&quot;. At the moment there's only one demo program.</p> <p>It's very &quot;work in progress&quot;. At the moment there's only one demo program.</p>
<a class="header" href="#getting-help" id="getting-help"><h2>Getting Help</h2></a>
<p>If you want to contact us you should join the <a href="https://discordapp.com/invite/aVESxV8">Rust Community
Discord</a> and ask in the <code>#gamedev</code>
channel. <code>Ketsuban</code> is the wizard who knows much more about how it all works,
and <code>Lokathor</code> is the fool who decided to write a crate and book for it.</p>
<p>If it's <em>not</em> a GBA specific question then you can also ask any of the few
hundred other folks in the server as well.</p>
<a class="header" href="#other-works" id="other-works"><h2>Other Works</h2></a> <a class="header" href="#other-works" id="other-works"><h2>Other Works</h2></a>
<p>If you want to read more about developing on the GBA there are some other good resources as well:</p> <p>If you want to read more about developing on the GBA there are some other good resources as well:</p>
<ul> <ul>

1
docs/searchindex.js Normal file

File diff suppressed because one or more lines are too long

1
docs/searchindex.json Normal file

File diff suppressed because one or more lines are too long

19
examples/hello1.rs Normal file
View file

@ -0,0 +1,19 @@
#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
(0x04000000 as *mut u16).write_volatile(0x0403);
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
loop {}
}
}

34
examples/hello2.rs Normal file
View file

@ -0,0 +1,34 @@
#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
DISPCNT.write_volatile(MODE3 | BG2);
mode3_pixel(120, 80, rgb16(31, 0, 0));
mode3_pixel(136, 80, rgb16(0, 31, 0));
mode3_pixel(120, 96, rgb16(0, 0, 31));
loop {}
}
}
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
blue << 10 | green << 5 | red
}
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}

161
examples/light_cycle.rs Normal file
View file

@ -0,0 +1,161 @@
#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
DISPCNT.write_volatile(MODE3 | BG2);
}
let mut px = SCREEN_WIDTH / 2;
let mut py = SCREEN_HEIGHT / 2;
let mut color = rgb16(31, 0, 0);
loop {
// read the input for this frame
let this_frame_keys = read_key_input();
// adjust game state and wait for vblank
px += 2 * this_frame_keys.column_direction() as isize;
py += 2 * this_frame_keys.row_direction() as isize;
wait_until_vblank();
// draw the new game and wait until the next frame starts.
unsafe {
if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT {
// out of bounds, reset the screen and position.
mode3_clear_screen(0);
color = color.rotate_left(5);
px = SCREEN_WIDTH / 2;
py = SCREEN_HEIGHT / 2;
} else {
let color_here = mode3_read_pixel(px, py);
if color_here != 0 {
// crashed into our own line, reset the screen
mode3_clear_screen(0);
color = color.rotate_left(5);
} else {
// draw the new part of the line
mode3_draw_pixel(px, py, color);
mode3_draw_pixel(px, py + 1, color);
mode3_draw_pixel(px + 1, py, color);
mode3_draw_pixel(px + 1, py + 1, color);
}
}
}
wait_until_vdraw();
}
}
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
pub const SCREEN_HEIGHT: isize = 160;
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
blue << 10 | green << 5 | red
}
pub unsafe fn mode3_clear_screen(color: u16) {
let color = color as u32;
let bulk_color = color << 16 | color;
let mut ptr = VRAM as *mut u32;
for _ in 0..SCREEN_HEIGHT {
for _ in 0..(SCREEN_WIDTH / 2) {
ptr.write_volatile(bulk_color);
ptr = ptr.offset(1);
}
}
}
pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}
pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read_volatile()
}
pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16;
/// A newtype over the key input state of the GBA.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(transparent)]
pub struct KeyInputSetting(u16);
/// A "tribool" value helps us interpret the arrow pad.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum TriBool {
Minus = -1,
Neutral = 0,
Plus = 1,
}
pub fn read_key_input() -> KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.read_volatile() ^ 0b1111_1111_1111_1111) }
}
pub const KEY_A: u16 = 1 << 0;
pub const KEY_B: u16 = 1 << 1;
pub const KEY_SELECT: u16 = 1 << 2;
pub const KEY_START: u16 = 1 << 3;
pub const KEY_RIGHT: u16 = 1 << 4;
pub const KEY_LEFT: u16 = 1 << 5;
pub const KEY_UP: u16 = 1 << 6;
pub const KEY_DOWN: u16 = 1 << 7;
pub const KEY_R: u16 = 1 << 8;
pub const KEY_L: u16 = 1 << 9;
impl KeyInputSetting {
pub fn contains(&self, key: u16) -> bool {
(self.0 & key) != 0
}
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
KeyInputSetting(self.0 ^ other.0)
}
pub fn column_direction(&self) -> TriBool {
if self.contains(KEY_RIGHT) {
TriBool::Plus
} else if self.contains(KEY_LEFT) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
pub fn row_direction(&self) -> TriBool {
if self.contains(KEY_DOWN) {
TriBool::Plus
} else if self.contains(KEY_UP) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
}
pub const VCOUNT: *mut u16 = 0x0400_0006 as *mut u16;
pub fn read_vcount() -> u16 {
unsafe { VCOUNT.read_volatile() }
}
pub fn wait_until_vblank() {
while read_vcount() < SCREEN_HEIGHT as u16 {}
}
pub fn wait_until_vdraw() {
while read_vcount() >= SCREEN_HEIGHT as u16 {}
}

63
linker.ld Normal file
View file

@ -0,0 +1,63 @@
ENTRY(__start)
MEMORY {
ewram (w!x) : ORIGIN = 0x2000000, LENGTH = 256K
iwram (w!x) : ORIGIN = 0x3000000, LENGTH = 32K
rom (rx) : ORIGIN = 0x8000000, LENGTH = 32M
}
SECTIONS {
.text : {
KEEP(crt0.o(.text));
*(.text .text.*);
. = ALIGN(4);
} >rom = 0xff
.rodata : {
*(.rodata .rodata.*);
. = ALIGN(4);
} >rom = 0xff
__data_lma = .;
.data : {
__data_start = ABSOLUTE(.);
*(.data .data.*);
. = ALIGN(4);
__data_end = ABSOLUTE(.);
} >iwram AT>rom = 0xff
/* debugging sections */
/* Stabs */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
/* discard anything not already mentioned */
/DISCARD/ : { *(*) }
}

8
rustfmt.toml Normal file
View file

@ -0,0 +1,8 @@
error_on_line_overflow = false
fn_args_density = "Compressed"
merge_imports = true
reorder_imports = true
use_try_shorthand = true
tab_spaces = 2
max_width = 150
color = "Never"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

50
src/core_extras.rs Normal file
View file

@ -0,0 +1,50 @@
//! Things that I wish were in core, but aren't.
/// A simple wrapper to any `*mut T` so that the basic "read" and "write"
/// operations are volatile.
///
/// Accessing the GBA's IO registers and video ram and specific other places on
/// **must** be done with volatile operations. Having this wrapper makes that
/// more clear for all the global const values into IO registers.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct VolatilePtr<T>(pub *mut T);
impl<T> core::fmt::Pointer for VolatilePtr<T> {
/// Formats exactly like the inner `*mut T`.
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{:p}", self.0)
}
}
impl<T> VolatilePtr<T> {
/// Performs a volatile read.
///
/// # Safety
///
/// This method adds absolutely no additional safety, so all safety concerns
/// for a normal raw pointer volatile read apply.
pub unsafe fn read(&self) -> T {
core::ptr::read_volatile(self.0)
}
/// Performs a volatile write.
///
/// # Safety
///
/// This method adds absolutely no additional safety, so all safety concerns
/// for a normal raw pointer volatile write apply.
pub unsafe fn write(&self, data: T) {
core::ptr::write_volatile(self.0, data);
}
/// Offsets this address by the amount given.
///
/// # Safety
///
/// This is a standard offset, so all safety concerns of a normal raw pointer
/// offset apply.
pub unsafe fn offset(self, count: isize) -> Self {
VolatilePtr(self.0.offset(count))
}
}

477
src/io_registers.rs Normal file
View file

@ -0,0 +1,477 @@
//! The module for all things relating to the IO Register portion of the GBA's
//! memory map.
//!
//! Here we define many constants for the volatile pointers to the various IO
//! registers. Each raw register constant is named according to the name given
//! to it in GBATEK's [GBA I/O
//! Map](http://problemkaputt.de/gbatek.htm#gbaiomap). They program in C, and so
//! of course all the names terrible and missing as many vowels as possible.
//! However, being able to look it up online is the most important thing here,
//! so oh well.
//!
//! In addition to the const `VolatilePtr` values, we will over time be adding
//! safe wrappers around each register, including newtypes and such so that you
//! can easily work with whatever each specific register is doing.
// TODO(lokathor): IO Register newtypes.
use gba_proc_macro::register_bit;
use super::*;
/// LCD Control. Read/Write.
///
/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol)
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(0x4000000 as *mut u16);
/// A newtype over the various display control options that you have on a GBA.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(transparent)]
pub struct DisplayControlSetting(u16);
#[allow(missing_docs)]
impl DisplayControlSetting {
pub const BG_MODE_MASK: u16 = 0b111;
pub fn mode(&self) -> DisplayControlMode {
match self.0 & Self::BG_MODE_MASK {
0 => DisplayControlMode::Tiled0,
1 => DisplayControlMode::Tiled1,
2 => DisplayControlMode::Tiled2,
3 => DisplayControlMode::Bitmap3,
4 => DisplayControlMode::Bitmap4,
5 => DisplayControlMode::Bitmap5,
_ => unreachable!(),
}
}
pub fn set_mode(&mut self, new_mode: DisplayControlMode) {
self.0 &= !Self::BG_MODE_MASK;
self.0 |= match new_mode {
DisplayControlMode::Tiled0 => 0,
DisplayControlMode::Tiled1 => 1,
DisplayControlMode::Tiled2 => 2,
DisplayControlMode::Bitmap3 => 3,
DisplayControlMode::Bitmap4 => 4,
DisplayControlMode::Bitmap5 => 5,
};
}
register_bit!(CGB_MODE_BIT, u16, 0b1000, cgb_mode, read);
register_bit!(PAGE_SELECT_BIT, u16, 0b1_0000, page1_enabled, read_write);
register_bit!(HBLANK_INTERVAL_FREE_BIT, u16, 0b10_0000, hblank_interval_free, read_write);
register_bit!(OBJECT_MEMORY_1D, u16, 0b100_0000, object_memory_1d, read_write);
register_bit!(FORCE_BLANK_BIT, u16, 0b1000_0000, force_blank, read_write);
register_bit!(DISPLAY_BG0_BIT, u16, 0b1_0000_0000, display_bg0, read_write);
register_bit!(DISPLAY_BG1_BIT, u16, 0b10_0000_0000, display_bg1, read_write);
register_bit!(DISPLAY_BG2_BIT, u16, 0b100_0000_0000, display_bg2, read_write);
register_bit!(DISPLAY_BG3_BIT, u16, 0b1000_0000_0000, display_bg3, read_write);
register_bit!(DISPLAY_OBJECT_BIT, u16, 0b1_0000_0000_0000, display_object, read_write);
register_bit!(DISPLAY_WINDOW0_BIT, u16, 0b10_0000_0000_0000, display_window0, read_write);
register_bit!(DISPLAY_WINDOW1_BIT, u16, 0b100_0000_0000_0000, display_window1, read_write);
register_bit!(OBJECT_WINDOW_BIT, u16, 0b1000_0000_0000_0000, display_object_window, read_write);
}
/// The six display modes available on the GBA.
pub enum DisplayControlMode {
/// This basically allows for the most different things at once (all layers,
/// 1024 tiles, two palette modes, etc), but you can't do affine
/// transformations.
Tiled0,
/// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`,
/// and BG2 runs as if in `Tiled2`.
Tiled1,
/// This allows affine transformations, but only uses BG2 and BG3.
Tiled2,
/// This is the basic bitmap draw mode. The whole screen is a single bitmap.
/// Uses BG2 only.
Bitmap3,
/// This uses _paletted color_ so that there's enough space to have two pages
/// at _full resolution_, allowing page flipping. Uses BG2 only.
Bitmap4,
/// This uses _reduced resolution_ so that there's enough space to have two
/// pages with _full color_, allowing page flipping. Uses BG2 only.
Bitmap5,
}
/// Assigns the given display control setting.
pub fn set_display_control(setting: DisplayControlSetting) {
unsafe {
DISPCNT.write(setting.0);
}
}
/// Obtains the current display control setting.
pub fn display_control() -> DisplayControlSetting {
unsafe { DisplayControlSetting(DISPCNT.read()) }
}
/// General LCD Status (STAT,LYC)
pub const DISPSTAT: VolatilePtr<u16> = VolatilePtr(0x4000004 as *mut u16);
/// Vertical Counter (LY)
pub const VCOUNT: VolatilePtr<u16> = VolatilePtr(0x4000006 as *mut u16);
/// BG0 Control
pub const BG0CNT: VolatilePtr<u16> = VolatilePtr(0x4000008 as *mut u16);
/// BG1 Control
pub const BG1CNT: VolatilePtr<u16> = VolatilePtr(0x400000A as *mut u16);
/// BG2 Control
pub const BG2CNT: VolatilePtr<u16> = VolatilePtr(0x400000C as *mut u16);
/// BG3 Control
pub const BG3CNT: VolatilePtr<u16> = VolatilePtr(0x400000E as *mut u16);
/// BG0 X-Offset
pub const BG0HOFS: VolatilePtr<u16> = VolatilePtr(0x4000010 as *mut u16);
/// BG0 Y-Offset
pub const BG0VOFS: VolatilePtr<u16> = VolatilePtr(0x4000012 as *mut u16);
/// BG1 X-Offset
pub const BG1HOFS: VolatilePtr<u16> = VolatilePtr(0x4000014 as *mut u16);
/// BG1 Y-Offset
pub const BG1VOFS: VolatilePtr<u16> = VolatilePtr(0x4000016 as *mut u16);
/// BG2 X-Offset
pub const BG2HOFS: VolatilePtr<u16> = VolatilePtr(0x4000018 as *mut u16);
/// BG2 Y-Offset
pub const BG2VOFS: VolatilePtr<u16> = VolatilePtr(0x400001A as *mut u16);
/// BG3 X-Offset
pub const BG3HOFS: VolatilePtr<u16> = VolatilePtr(0x400001C as *mut u16);
/// BG3 Y-Offset
pub const BG3VOFS: VolatilePtr<u16> = VolatilePtr(0x400001E as *mut u16);
/// BG2 Rotation/Scaling Parameter A (dx)
pub const BG2PA: VolatilePtr<u16> = VolatilePtr(0x4000020 as *mut u16);
/// BG2 Rotation/Scaling Parameter B (dmx)
pub const BG2PB: VolatilePtr<u16> = VolatilePtr(0x4000022 as *mut u16);
/// BG2 Rotation/Scaling Parameter C (dy)
pub const BG2PC: VolatilePtr<u16> = VolatilePtr(0x4000024 as *mut u16);
/// BG2 Rotation/Scaling Parameter D (dmy)
pub const BG2PD: VolatilePtr<u16> = VolatilePtr(0x4000026 as *mut u16);
/// BG2 Reference Point X-Coordinate
pub const BG2X: VolatilePtr<u32> = VolatilePtr(0x4000028 as *mut u32);
/// BG2 Reference Point Y-Coordinate
pub const BG2Y: VolatilePtr<u32> = VolatilePtr(0x400002C as *mut u32);
/// BG3 Rotation/Scaling Parameter A (dx)
pub const BG3PA: VolatilePtr<u16> = VolatilePtr(0x4000030 as *mut u16);
/// BG3 Rotation/Scaling Parameter B (dmx)
pub const BG3PB: VolatilePtr<u16> = VolatilePtr(0x4000032 as *mut u16);
/// BG3 Rotation/Scaling Parameter C (dy)
pub const BG3PC: VolatilePtr<u16> = VolatilePtr(0x4000034 as *mut u16);
/// BG3 Rotation/Scaling Parameter D (dmy)
pub const BG3PD: VolatilePtr<u16> = VolatilePtr(0x4000036 as *mut u16);
/// BG3 Reference Point X-Coordinate
pub const BG3X: VolatilePtr<u32> = VolatilePtr(0x4000038 as *mut u32);
/// BG3 Reference Point Y-Coordinate
pub const BG3Y: VolatilePtr<u32> = VolatilePtr(0x400003C as *mut u32);
/// Window 0 Horizontal Dimensions
pub const WIN0H: VolatilePtr<u16> = VolatilePtr(0x4000040 as *mut u16);
/// Window 1 Horizontal Dimensions
pub const WIN1H: VolatilePtr<u16> = VolatilePtr(0x4000042 as *mut u16);
/// Window 0 Vertical Dimensions
pub const WIN0V: VolatilePtr<u16> = VolatilePtr(0x4000044 as *mut u16);
/// Window 1 Vertical Dimensions
pub const WIN1V: VolatilePtr<u16> = VolatilePtr(0x4000046 as *mut u16);
/// Inside of Window 0 and 1
pub const WININ: VolatilePtr<u16> = VolatilePtr(0x4000048 as *mut u16);
/// Inside of OBJ Window & Outside of Windows
pub const WINOUT: VolatilePtr<u16> = VolatilePtr(0x400004A as *mut u16);
/// Mosaic Size
pub const MOSAIC: VolatilePtr<u16> = VolatilePtr(0x400004C as *mut u16);
/// Color Special Effects Selection
pub const BLDCNT: VolatilePtr<u16> = VolatilePtr(0x4000050 as *mut u16);
/// Alpha Blending Coefficients
pub const BLDALPHA: VolatilePtr<u16> = VolatilePtr(0x4000052 as *mut u16);
/// Brightness (Fade-In/Out) Coefficient
pub const BLDY: VolatilePtr<u16> = VolatilePtr(0x4000054 as *mut u16);
/// Channel 1 Sweep register (NR10)
pub const UND1CNT_L: VolatilePtr<u16> = VolatilePtr(0x4000060 as *mut u16);
/// Channel 1 Duty/Length/Envelope (NR11, NR12)
pub const UND1CNT_H: VolatilePtr<u16> = VolatilePtr(0x4000062 as *mut u16);
/// Channel 1 Frequency/Control (NR13, NR14)
pub const UND1CNT_X: VolatilePtr<u16> = VolatilePtr(0x4000064 as *mut u16);
/// Channel 2 Duty/Length/Envelope (NR21, NR22)
pub const UND2CNT_L: VolatilePtr<u16> = VolatilePtr(0x4000068 as *mut u16);
/// Channel 2 Frequency/Control (NR23, NR24)
pub const UND2CNT_H: VolatilePtr<u16> = VolatilePtr(0x400006C as *mut u16);
/// Channel 3 Stop/Wave RAM select (NR30)
pub const UND3CNT_L: VolatilePtr<u16> = VolatilePtr(0x4000070 as *mut u16);
/// Channel 3 Length/Volume (NR31, NR32)
pub const UND3CNT_H: VolatilePtr<u16> = VolatilePtr(0x4000072 as *mut u16);
/// Channel 3 Frequency/Control (NR33, NR34)
pub const UND3CNT_X: VolatilePtr<u16> = VolatilePtr(0x4000074 as *mut u16);
/// Channel 4 Length/Envelope (NR41, NR42)
pub const UND4CNT_L: VolatilePtr<u16> = VolatilePtr(0x4000078 as *mut u16);
/// Channel 4 Frequency/Control (NR43, NR44)
pub const UND4CNT_H: VolatilePtr<u16> = VolatilePtr(0x400007C as *mut u16);
/// Control Stereo/Volume/Enable (NR50, NR51)
pub const UNDCNT_L: VolatilePtr<u16> = VolatilePtr(0x4000080 as *mut u16);
/// Control Mixing/DMA Control
pub const UNDCNT_H: VolatilePtr<u16> = VolatilePtr(0x4000082 as *mut u16);
/// Control Sound on/off (NR52)
pub const UNDCNT_X: VolatilePtr<u16> = VolatilePtr(0x4000084 as *mut u16);
/// Sound PWM Control
pub const UNDBIAS: VolatilePtr<u16> = VolatilePtr(0x4000088 as *mut u16);
/// Channel 3 Wave Pattern RAM (W/R)
pub const WAVE_RAM0_L: VolatilePtr<u16> = VolatilePtr(0x4000090 as *mut u16);
/// Channel 3 Wave Pattern RAM (W/R)
pub const WAVE_RAM0_H: VolatilePtr<u16> = VolatilePtr(0x4000092 as *mut u16);
/// Channel 3 Wave Pattern RAM (W/R)
pub const WAVE_RAM1_L: VolatilePtr<u16> = VolatilePtr(0x4000094 as *mut u16);
/// Channel 3 Wave Pattern RAM (W/R)
pub const WAVE_RAM1_H: VolatilePtr<u16> = VolatilePtr(0x4000096 as *mut u16);
/// Channel 3 Wave Pattern RAM (W/R)
pub const WAVE_RAM2_L: VolatilePtr<u16> = VolatilePtr(0x4000098 as *mut u16);
/// Channel 3 Wave Pattern RAM (W/R)
pub const WAVE_RAM2_H: VolatilePtr<u16> = VolatilePtr(0x400009A as *mut u16);
/// Channel 3 Wave Pattern RAM (W/R)
pub const WAVE_RAM3_L: VolatilePtr<u16> = VolatilePtr(0x400009C as *mut u16);
/// Channel 3 Wave Pattern RAM (W/R)
pub const WAVE_RAM3_H: VolatilePtr<u16> = VolatilePtr(0x400009E as *mut u16);
/// Channel A FIFO, Data 0-3
pub const FIFO_A: VolatilePtr<u32> = VolatilePtr(0x40000A0 as *mut u32);
/// Channel B FIFO, Data 0-3
pub const FIFO_B: VolatilePtr<u32> = VolatilePtr(0x40000A4 as *mut u32);
/// DMA 0 Source Address
pub const DMA0SAD: VolatilePtr<u32> = VolatilePtr(0x40000B0 as *mut u32);
/// DMA 0 Destination Address
pub const DMA0DAD: VolatilePtr<u32> = VolatilePtr(0x40000B4 as *mut u32);
/// DMA 0 Word Count
pub const DMA0CNT_L: VolatilePtr<u16> = VolatilePtr(0x40000B8 as *mut u16);
/// DMA 0 Control
pub const DMA0CNT_H: VolatilePtr<u16> = VolatilePtr(0x40000BA as *mut u16);
/// DMA 1 Source Address
pub const DMA1SAD: VolatilePtr<u32> = VolatilePtr(0x40000BC as *mut u32);
/// DMA 1 Destination Address
pub const DMA1DAD: VolatilePtr<u32> = VolatilePtr(0x40000C0 as *mut u32);
/// DMA 1 Word Count
pub const DMA1CNT_L: VolatilePtr<u16> = VolatilePtr(0x40000C4 as *mut u16);
/// DMA 1 Control
pub const DMA1CNT_H: VolatilePtr<u16> = VolatilePtr(0x40000C6 as *mut u16);
/// DMA 2 Source Address
pub const DMA2SAD: VolatilePtr<u32> = VolatilePtr(0x40000C8 as *mut u32);
/// DMA 2 Destination Address
pub const DMA2DAD: VolatilePtr<u32> = VolatilePtr(0x40000CC as *mut u32);
/// DMA 2 Word Count
pub const DMA2CNT_L: VolatilePtr<u16> = VolatilePtr(0x40000D0 as *mut u16);
/// DMA 2 Control
pub const DMA2CNT_H: VolatilePtr<u16> = VolatilePtr(0x40000D2 as *mut u16);
/// DMA 3 Source Address
pub const DMA3SAD: VolatilePtr<u32> = VolatilePtr(0x40000D4 as *mut u32);
/// DMA 3 Destination Address
pub const DMA3DAD: VolatilePtr<u32> = VolatilePtr(0x40000D8 as *mut u32);
/// DMA 3 Word Count
pub const DMA3CNT_L: VolatilePtr<u16> = VolatilePtr(0x40000DC as *mut u16);
/// DMA 3 Control
pub const DMA3CNT_H: VolatilePtr<u16> = VolatilePtr(0x40000DE as *mut u16);
/// Timer 0 Counter/Reload
pub const TM0CNT_L: VolatilePtr<u16> = VolatilePtr(0x4000100 as *mut u16);
/// Timer 0 Control
pub const TM0CNT_H: VolatilePtr<u16> = VolatilePtr(0x4000102 as *mut u16);
/// Timer 1 Counter/Reload
pub const TM1CNT_L: VolatilePtr<u16> = VolatilePtr(0x4000104 as *mut u16);
/// Timer 1 Control
pub const TM1CNT_H: VolatilePtr<u16> = VolatilePtr(0x4000106 as *mut u16);
/// Timer 2 Counter/Reload
pub const TM2CNT_L: VolatilePtr<u16> = VolatilePtr(0x4000108 as *mut u16);
/// Timer 2 Control
pub const TM2CNT_H: VolatilePtr<u16> = VolatilePtr(0x400010A as *mut u16);
/// Timer 3 Counter/Reload
pub const TM3CNT_L: VolatilePtr<u16> = VolatilePtr(0x400010C as *mut u16);
/// Timer 3 Control
pub const TM3CNT_H: VolatilePtr<u16> = VolatilePtr(0x400010E as *mut u16);
/// SIO Data (Normal-32bit Mode; shared with below)
pub const SIODATA32: VolatilePtr<u32> = VolatilePtr(0x4000120 as *mut u32);
/// SIO Data 0 (Parent) (Multi-Player Mode)
pub const SIOMULTI0: VolatilePtr<u16> = VolatilePtr(0x4000120 as *mut u16);
/// SIO Data 1 (1st Child) (Multi-Player Mode)
pub const SIOMULTI1: VolatilePtr<u16> = VolatilePtr(0x4000122 as *mut u16);
/// SIO Data 2 (2nd Child) (Multi-Player Mode)
pub const SIOMULTI2: VolatilePtr<u16> = VolatilePtr(0x4000124 as *mut u16);
/// SIO Data 3 (3rd Child) (Multi-Player Mode)
pub const SIOMULTI3: VolatilePtr<u16> = VolatilePtr(0x4000126 as *mut u16);
/// SIO Control Register
pub const SIOCNT: VolatilePtr<u16> = VolatilePtr(0x4000128 as *mut u16);
/// D SIO Data (Local of MultiPlayer; shared below)
pub const SIOMLT_SEN: VolatilePtr<u16> = VolatilePtr(0x400012A as *mut u16);
/// SIO Data (Normal-8bit and UART Mode)
pub const SIODATA8: VolatilePtr<u16> = VolatilePtr(0x400012A as *mut u16);
/// Key Status
pub const KEYINPUT: VolatilePtr<u16> = VolatilePtr(0x4000130 as *mut u16);
/// A newtype over the key input state of the GBA.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(transparent)]
pub struct KeyInputSetting(u16);
/// A "tribool" value helps us interpret the arrow pad.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
#[allow(missing_docs)]
pub enum TriBool {
Minus = -1,
Neutral = 0,
Plus = 1,
}
#[allow(missing_docs)]
impl KeyInputSetting {
register_bit!(A_BIT, u16, 1 << 0, a_pressed, read_write);
register_bit!(B_BIT, u16, 1 << 1, b_pressed, read_write);
register_bit!(SELECT_BIT, u16, 1 << 2, select_pressed, read_write);
register_bit!(START_BIT, u16, 1 << 3, start_pressed, read_write);
register_bit!(RIGHT_BIT, u16, 1 << 4, right_pressed, read_write);
register_bit!(LEFT_BIT, u16, 1 << 5, left_pressed, read_write);
register_bit!(UP_BIT, u16, 1 << 6, up_pressed, read_write);
register_bit!(DOWN_BIT, u16, 1 << 7, down_pressed, read_write);
register_bit!(R_BIT, u16, 1 << 8, r_pressed, read_write);
register_bit!(L_BIT, u16, 1 << 9, l_pressed, read_write);
/// Takes the difference between these keys and another set of keys.
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
KeyInputSetting(self.0 ^ other.0)
}
/// Gives the arrow pad value as a tribool, with Plus being increased column
/// value (right).
pub fn column_direction(&self) -> TriBool {
if self.right_pressed() {
TriBool::Plus
} else if self.left_pressed() {
TriBool::Minus
} else {
TriBool::Neutral
}
}
/// Gives the arrow pad value as a tribool, with Plus being increased row
/// value (down).
pub fn row_direction(&self) -> TriBool {
if self.down_pressed() {
TriBool::Plus
} else if self.up_pressed() {
TriBool::Minus
} else {
TriBool::Neutral
}
}
}
/// Gets the current state of the keys
pub fn read_key_input() -> KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.read() ^ 0b1111_1111_1111_1111) }
}
/// Key Interrupt Control
pub const KEYCNT: VolatilePtr<u16> = VolatilePtr(0x4000132 as *mut u16);
/// SIO Mode Select/General Purpose Data
pub const RCNT: VolatilePtr<u16> = VolatilePtr(0x4000134 as *mut u16);
/// SIO JOY Bus Control
pub const JOYCNT: VolatilePtr<u16> = VolatilePtr(0x4000140 as *mut u16);
/// SIO JOY Bus Receive Data
pub const JOY_RECV: VolatilePtr<u32> = VolatilePtr(0x4000150 as *mut u32);
/// SIO JOY Bus Transmit Data
pub const JOY_TRANS: VolatilePtr<u32> = VolatilePtr(0x4000154 as *mut u32);
/// SIO JOY Bus Receive Status
pub const JOYSTAT: VolatilePtr<u16> = VolatilePtr(0x4000158 as *mut u16);
/// Interrupt Enable Register
pub const IE: VolatilePtr<u16> = VolatilePtr(0x4000200 as *mut u16);
/// Interrupt Request Flags / IRQ Acknowledge
pub const IF: VolatilePtr<u16> = VolatilePtr(0x4000202 as *mut u16);
/// Game Pak Waitstate Control
pub const WAITCNT: VolatilePtr<u16> = VolatilePtr(0x4000204 as *mut u16);
/// Interrupt Master Enable Register
pub const IME: VolatilePtr<u16> = VolatilePtr(0x4000208 as *mut u16);

27
src/lib.rs Normal file
View file

@ -0,0 +1,27 @@
#![no_std]
#![warn(missing_docs)]
//! This crate helps you write GBA ROMs.
//!
//! # SAFETY POLICY
//!
//! Some parts of this crate are safe wrappers around unsafe operations. This is
//! good, and what you'd expect from a Rust crate.
//!
//! However, the safe wrappers all assume that you will _only_ attempt to
//! execute this crate on a GBA or in a GBA Emulator.
//!
//! **Do not** use this crate in programs that aren't running on the GBA. If you
//! do, it's a giant bag of Undefined Behavior.
pub mod core_extras;
pub(crate) use crate::core_extras::*;
pub mod io_registers;
pub mod video_ram;
/// Combines the Red, Blue, and Green provided into a single color value.
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
blue << 10 | green << 5 | red
}

8
src/macros.rs Normal file
View file

@ -0,0 +1,8 @@
//! Module for all macros.
//!
//! Macros are the only thing in Rust where declaration order matters, so we
//! place all of them here regardless of what they do so that the macros module
//! can appear at the "top" of the library and all other modules can see them
//! properly.
// no macros yet!

49
src/video_ram.rs Normal file
View file

@ -0,0 +1,49 @@
//! Module for all things relating to the Video RAM.
//!
//! Note that the GBA has six different display modes available, and the
//! _meaning_ of Video RAM depends on which display mode is active. In all
//! cases, Video RAM is **96kb** from `0x0600_0000` to `0x0601_7FFF`.
//!
//! # Safety
//!
//! Note that all possible bit patterns are technically allowed within Video
//! Memory. If you write the "wrong" thing into video memory you don't crash the
//! GBA, instead you just get graphical glitches (or perhaps nothing at all).
//! Accordingly, the "safe" functions here will check that you're in bounds, but
//! they won't bother to check that you've set the video mode they're designed
//! for.
/// The physical width in pixels of the GBA screen.
pub const SCREEN_WIDTH: isize = 240;
/// The physical height in pixels of the GBA screen.
pub const SCREEN_HEIGHT: isize = 160;
/// The start of VRAM.
///
/// Depending on what display mode is currently set there's different ways that
/// your program should interpret the VRAM space. Accordingly, we give the raw
/// value as just being a `usize`.
pub const VRAM_BASE_ADDRESS: usize = 0x0600_0000;
/// Draws a pixel to the screen while in Display Mode 3, with bounds checks.
pub fn mode3_pixel(col: isize, row: isize, color: u16) {
assert!(col >= 0 && col < SCREEN_WIDTH);
assert!(row >= 0 && row < SCREEN_HEIGHT);
unsafe { mode3_pixel_unchecked(col, row, color) }
}
/// Draws a pixel to the screen while in Display Mode 3.
///
/// Coordinates are relative to the top left corner.
///
/// If you're in another mode you'll get something weird drawn, but it's memory
/// safe in the rust sense as long as you stay in bounds.
///
/// # Safety
///
/// * `col` must be in `0..SCREEN_WIDTH`
/// * `row` must be in `0..SCREEN_HEIGHT`
pub unsafe fn mode3_pixel_unchecked(col: isize, row: isize, color: u16) {
core::ptr::write_volatile((VRAM_BASE_ADDRESS as *mut u16).offset(col + row * SCREEN_WIDTH), color);
}

34
thumbv4-none-agb.json Normal file
View file

@ -0,0 +1,34 @@
{
"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": "",
"executables": true,
"features": "+soft-float,+strict-align",
"linker": "arm-none-eabi-ld",
"linker-flavor": "ld",
"linker-is-gnu": true,
"llvm-target": "thumbv4-none-agb",
"os": "none",
"panic-strategy": "abort",
"pre-link-args": {
"ld": [
"-Tlinker.ld"
]
},
"relocation-model": "static",
"target-c-int-width": "32",
"target-endian": "little",
"target-pointer-width": "32",
"vendor": ""
}