mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-23 10:51:30 +11:00
Revert "Deploy rust-console/gba to github.com/rust-console/gba.git:master"
This reverts commit 2f1c243742
.
This commit is contained in:
parent
2f1c243742
commit
a4b6a4e8b2
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal 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
30
.travis.yml
Normal 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
17
Cargo.toml
Normal 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
201
LICENSE-APACHE2.txt
Normal 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
17
README.md
Normal 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
8
book/book.toml
Normal 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
15
book/src/SUMMARY.md
Normal 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
134
book/src/ch0/index.md
Normal 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
193
book/src/ch1/hello1.md
Normal 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
114
book/src/ch1/hello2.md
Normal 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
10
book/src/ch1/index.md
Normal 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.
|
33
book/src/ch1/io_registers.md
Normal file
33
book/src/ch1/io_registers.md
Normal 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.
|
112
book/src/ch1/the_display_control_register.md
Normal file
112
book/src/ch1/the_display_control_register.md
Normal 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.
|
113
book/src/ch1/video_memory_intro.md
Normal file
113
book/src/ch1/video_memory_intro.md
Normal 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
22
book/src/ch2/index.md
Normal 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
119
book/src/ch2/light_cycle.md
Normal 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.
|
211
book/src/ch2/the_key_input_register.md
Normal file
211
book/src/ch2/the_key_input_register.md
Normal 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.
|
71
book/src/ch2/the_vcount_register.md
Normal file
71
book/src/ch2/the_vcount_register.md
Normal 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
28
book/src/introduction.md
Normal 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
14
build.bat
Normal 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
34
crt0.s
Normal 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
|
Before Width: | Height: | Size: 348 KiB After Width: | Height: | Size: 348 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
@ -139,13 +139,6 @@
|
|||
<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>It's very "work in progress". 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>
|
||||
<p>If you want to read more about developing on the GBA there are some other good resources as well:</p>
|
||||
<ul>
|
|
@ -139,13 +139,6 @@
|
|||
<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>It's very "work in progress". 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>
|
||||
<p>If you want to read more about developing on the GBA there are some other good resources as well:</p>
|
||||
<ul>
|
0
mark.min.js → docs/mark.min.js
vendored
0
mark.min.js → docs/mark.min.js
vendored
|
@ -139,13 +139,6 @@
|
|||
<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>It's very "work in progress". 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>
|
||||
<p>If you want to read more about developing on the GBA there are some other good resources as well:</p>
|
||||
<ul>
|
1
docs/searchindex.js
Normal file
1
docs/searchindex.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/searchindex.json
Normal file
1
docs/searchindex.json
Normal file
File diff suppressed because one or more lines are too long
19
examples/hello1.rs
Normal file
19
examples/hello1.rs
Normal 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
34
examples/hello2.rs
Normal 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
161
examples/light_cycle.rs
Normal 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
63
linker.ld
Normal 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
8
rustfmt.toml
Normal 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
50
src/core_extras.rs
Normal 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
477
src/io_registers.rs
Normal 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
27
src/lib.rs
Normal 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
8
src/macros.rs
Normal 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
49
src/video_ram.rs
Normal 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
34
thumbv4-none-agb.json
Normal 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": ""
|
||||
}
|
Loading…
Reference in a new issue