├── .cargo └── config.toml ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── README.md ├── build.rs ├── linker_script.ld └── src ├── bin └── main.rs ├── lib.rs └── rsrt0.S /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv4t-none-eabi" 3 | rustflags = ["-Clink-arg=-Tlinker_script.ld"] 4 | 5 | [target.thumbv4t-none-eabi] 6 | runner = "mgba" 7 | 8 | [unstable] 9 | build-std = ["core"] 10 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: {} 5 | pull_request: {} 6 | schedule: 7 | # Min Hr Day Month Weekday; so this should be 1:05am each day. 8 | - cron: '5 1 * * *' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | rust: 16 | - { toolchain: nightly } 17 | steps: 18 | - uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: ${{ matrix.rust.toolchain }} 22 | default: true 23 | - uses: actions/checkout@v1 24 | - name: Get ARM Binutils 25 | run: sudo apt-get update && sudo apt-get install binutils-arm-none-eabi --assume-yes 26 | - name: Install Rust Source 27 | run: rustup component add rust-src 28 | - uses: actions-rs/cargo@v1 29 | with: 30 | toolchain: ${{ matrix.rust.toolchain }} 31 | command: build 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.checkOnSave.allTargets": false, 3 | "rust-analyzer.checkOnSave.extraArgs": [ 4 | "--lib" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "min-gba" 3 | version = "0.1.0" 4 | authors = ["Lokathor "] 5 | edition = "2018" 6 | license = "Zlib OR Apache-2.0 OR MIT" 7 | 8 | publish = false 9 | 10 | [dependencies] 11 | 12 | [profile.dev] 13 | panic = "abort" 14 | opt-level = 3 15 | [profile.release] 16 | panic = "abort" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # min-gba 2 | 3 | A minimal setup to build Rust into a GBA ROM. 4 | 5 | This crate is not for general use! 6 | 7 | If you'd like to program a GBA game try the [gba](https://github.com/rust-console/gba) crate. 8 | The point of this repo is just to ensure that crater runs which break the thumbv4t-none-eabi target get caught. 9 | 10 | ## What's Needed 11 | 12 | * `rustup default nightly` (or run all commands with `cargo +nightly`) 13 | * `rustup component add rust-src` 14 | * `arm-none-eabi` binutils (either via [The ARM Website][1], or your linux package manager). 15 | * The following files: 16 | * `.cargo/config.toml` 17 | * `src/rsrt0.S` 18 | * `linker_script.ld` 19 | * `build.rs` 20 | 21 | [1]: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm 22 | 23 | ## Rust Analyzer 24 | 25 | It'll go nuts, here's how you fix it: 26 | 27 | `.vscode/settings.json`: 28 | ```json 29 | { 30 | "rust-analyzer.checkOnSave.allTargets": false, 31 | "rust-analyzer.checkOnSave.extraArgs": [ 32 | "--lib" 33 | ] 34 | } 35 | ``` 36 | 37 | ## Final ROM 38 | 39 | What you get with the project setup is an executable ELF file, but [mgba](https://mgba.io/) can run it directly, so that's fine. 40 | 41 | For a finalized ROM there's some post-processing steps: 42 | 43 | * `objcopy` must be used to extact just the binary guts from the ELF file. 44 | * The command you want is probably something like this (change `main` to your binary's own name): 45 | * `arm-none-eabi-objcopy -O binary target/thumbv4t-none-eabi/release/main target/main.gba` 46 | 47 | * `gbafix` should be used to apply the correct header. 48 | * You can get a Rust version of `gbafix` via cargo: `cargo install gbafix` 49 | * You can also get the C version of gbafix from the DevKitPro folks. 50 | * Either way, `gbafix main.gba` will ensure that the gba file has the correct header info. 51 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let out_file = "rsrt0.o"; 3 | let out_dir = std::env::var("OUT_DIR").unwrap(); 4 | let out_dir_file = format!("{}/{}", out_dir, out_file); 5 | let as_output = std::process::Command::new("arm-none-eabi-as") 6 | .args(&["-o", out_dir_file.as_str()]) 7 | .arg("-mthumb-interwork") 8 | .arg("-mcpu=arm7tdmi") 9 | .arg("src/rsrt0.S") 10 | .output() 11 | .expect("failed to run arm-none-eabi-as"); 12 | if !as_output.status.success() { 13 | panic!("{}", String::from_utf8_lossy(&as_output.stderr)); 14 | } 15 | // 16 | println!("cargo:rustc-link-search={}", out_dir); 17 | } 18 | -------------------------------------------------------------------------------- /linker_script.ld: -------------------------------------------------------------------------------- 1 | ENTRY(__start) 2 | 3 | MEMORY { 4 | ewram (w!x) : ORIGIN = 0x2000000, LENGTH = 256K 5 | iwram (w!x) : ORIGIN = 0x3000000, LENGTH = 32K 6 | rom (rx) : ORIGIN = 0x8000000, LENGTH = 32M 7 | } 8 | 9 | SECTIONS { 10 | .text : { 11 | KEEP(rsrt0.o(.text)); 12 | *(.text .text.*); 13 | . = ALIGN(4); 14 | } >rom = 0xff 15 | 16 | .rodata : { 17 | KEEP(rsrt0.o(.rodata)); 18 | *(.rodata .rodata.*); 19 | . = ALIGN(4); 20 | } >rom = 0xff 21 | 22 | __data_rom_start = .; 23 | .data : { 24 | __data_iwram_start = ABSOLUTE(.); 25 | KEEP(rsrt0.o(.data)); 26 | *(.data .data.*); 27 | . = ALIGN(4); 28 | __data_iwram_end = ABSOLUTE(.); 29 | } >iwram AT>rom = 0xff 30 | 31 | .bss : { 32 | __bss_iwram_start = ABSOLUTE(.); 33 | KEEP(rsrt0.o(.bss)); 34 | *(.bss .bss.*); 35 | . = ALIGN(4); 36 | __bss_iwram_end = ABSOLUTE(.); 37 | } >iwram 38 | 39 | /* discard anything not already mentioned */ 40 | /DISCARD/ : { *(*) } 41 | } 42 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | #[panic_handler] 5 | fn panic(_info: &core::panic::PanicInfo) -> ! { 6 | unsafe { 7 | let x = 0_i32; 8 | loop { 9 | (&x as *const i32).read_volatile(); 10 | } 11 | } 12 | } 13 | 14 | #[no_mangle] 15 | pub fn main() { 16 | unsafe { 17 | (0x0400_0000 as *mut u16).write_volatile(3 | (1 << 10)); 18 | (0x0600_0000 as *mut u16).add(240).add(1).write_volatile(0b11111); 19 | panic!() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | -------------------------------------------------------------------------------- /src/rsrt0.S: -------------------------------------------------------------------------------- 1 | 2 | @ linker entry point 3 | .global __start 4 | 5 | .arm 6 | __start: b init 7 | @ this is replaced with correct header info by `gbafix` 8 | .space 188 9 | 10 | init: 11 | @ We boot in Supervisor mode, change to System mode. 12 | mov r0, #0x1f 13 | msr CPSR_c, r0 14 | 15 | @ Set stack pointer. 16 | ldr sp, =0x3007F00 17 | 18 | @ call Rust `main` 19 | ldr r2, =main 20 | bx r2 21 | 22 | @ `main` should never return. 23 | 1: b 1b 24 | --------------------------------------------------------------------------------