From 3c055d49e9b4da28473f5a1edcf6d86a951ef21f Mon Sep 17 00:00:00 2001 From: Kat Inskip Date: Thu, 24 Jul 2025 19:42:45 -0700 Subject: [PATCH] feat: make build :o --- .cargo/config.toml | 8 +++ .gitignore | 4 ++ Cargo.lock | 56 +++++++++++++++++ Cargo.toml | 13 ++++ build.rs | 6 ++ build/linker_scripts/mono_boot.ld | 99 +++++++++++++++++++++++++++++ flake.lock | 98 +++++++++++++++++++++++++++++ flake.nix | 64 +++++++++++++++++++ package.nix | 56 +++++++++++++++++ rust-toolchain.toml | 5 ++ shell.nix | 10 +++ src/foo.txt | 1 + src/main.rs | 101 ++++++++++++++++++++++++++++++ 13 files changed, 521 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 build/linker_scripts/mono_boot.ld create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 package.nix create mode 100644 rust-toolchain.toml create mode 100644 shell.nix create mode 100644 src/foo.txt create mode 100644 src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..76a4860 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,8 @@ +[build] +target = "thumbv4t-none-eabi" + +[unstable] +build-std = ["core"] + +[target.thumbv4t-none-eabi] +runner = "mgba-qt" # sets the emulator to run bins/examples with diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ffa93ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +/result +/.direnv +/vendor diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a194692 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,56 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitfrob" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de3bf9292416bc27b97603d1af580e4253138851ce3898f878807b898e90d2d" + +[[package]] +name = "bracer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00248d542917c4ef013367c0907300e9befbbc1f99b12938c9e5a356ab50582d" + +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" + +[[package]] +name = "gba" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff72a04df599de9991069ab6807f00e02dc24840e13d16f92dfa2929c4e6950" +dependencies = [ + "bitfrob", + "bracer", + "bytemuck", + "voladdress", +] + +[[package]] +name = "gbafix" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e47af9d5377b8b0def53d9916f6c28521f2e5b97c5fec137797417c26ab4b7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "katgba" +version = "0.1.0" +dependencies = [ + "gba", + "gbafix", +] + +[[package]] +name = "voladdress" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f4a9a7c7e717541992e2405d44d12fb019db4117ec9fcca84b784060b27084" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a1ebd83 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "katgba" +description = "kat video game" +version = "0.1.0" +edition = "2024" +include = ["src/foo.txt", "/build/linker_scripts"] +build = "build.rs" + +[dependencies] +gba = "0.14.1" + +[build-dependencies] +gbafix = "1.0.4" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..0150d1f --- /dev/null +++ b/build.rs @@ -0,0 +1,6 @@ +fn main() { + println!("cargo:rustc-link-arg=-Tbuild/linker_scripts/mono_boot.ld"); + println!("cargo:rustc-linker=arm-none-eabi-ld"); + println!("cargo::rerun-if-changed=build.rs"); + println!("cargo::rerun-if-changed=src/foo.txt"); +} diff --git a/build/linker_scripts/mono_boot.ld b/build/linker_scripts/mono_boot.ld new file mode 100644 index 0000000..ff0233b --- /dev/null +++ b/build/linker_scripts/mono_boot.ld @@ -0,0 +1,99 @@ +/* THIS LINKER SCRIPT FILE IS RELEASED TO THE PUBLIC DOMAIN (SPDX: CC0-1.0) */ + +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 : { + /* be sure that the ROM header is the very first */ + *(.text.gba_rom_header); + *(.text .text.*); + . = ALIGN(4); + } >rom = 0x00 + + .rodata : { + *(.rodata .rodata.*); + . = ALIGN(4); + } >rom = 0x00 + + . = ALIGN(4); + __iwram_position_in_rom = .; + .data : { + __iwram_start = ABSOLUTE(.); + + *(.data .data.*); + *(.iwram .iwram.*); + . = ALIGN(4); + + __iwram_end = ABSOLUTE(.); + } >iwram AT>rom = 0x00 + + . = ALIGN(4); + __ewram_position_in_rom = __iwram_position_in_rom + (__iwram_end - __iwram_start); + .ewram : { + __ewram_start = ABSOLUTE(.); + + *(.ewram .ewram.*); + . = ALIGN(4); + + __ewram_end = ABSOLUTE(.); + } >ewram AT>rom = 0x00 + + . = ALIGN(4); + __bss_position_in_rom = __ewram_position_in_rom + (__ewram_end - __ewram_start); + .bss : { + __bss_start = ABSOLUTE(.); + + *(.bss .bss.*); + . = ALIGN(4); + + __bss_end = ABSOLUTE(.); + } >iwram + + __iwram_word_copy_count = (__iwram_end - __iwram_start) / 4; + __ewram_word_copy_count = (__ewram_end - __ewram_start) / 4; + __bss_word_clear_count = (__bss_end - __bss_start) / 4; + + /* rust-lld demands we keep the `section header string table` */ + .shstrtab 0 : { *(.shstrtab) } + + /* 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/ : { *(*) } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6c4e264 --- /dev/null +++ b/flake.lock @@ -0,0 +1,98 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1752946753, + "narHash": "sha256-g5uP3jIj+STUcfTJDKYopxnSijs2agRg13H0SGL5iE4=", + "owner": "ipetkov", + "repo": "crane", + "rev": "544d09fecc8c2338542c57f3f742f1a0c8c71e13", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1753151930, + "narHash": "sha256-XSQy6wRKHhRe//iVY5lS/ZpI/Jn6crWI8fQzl647wCg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "83e677f31c84212343f4cc553bab85c2efcad60a", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1753238793, + "narHash": "sha256-jmQeEpgX+++MEgrcikcwoSiI7vDZWLP0gci7XiWb9uQ=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "0ad7ab4ca8e83febf147197e65c006dff60623ab", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..f527d73 --- /dev/null +++ b/flake.nix @@ -0,0 +1,64 @@ +{ + description = "gba"; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + crane.url = "github:ipetkov/crane"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { + nixpkgs, + flake-utils, + rust-overlay, + crane, + ... + }: + let + inherit (nixpkgs) lib; + rustTriple = "thumbv4t-none-eabi"; + nixTriple = "arm-none-eabi"; + in flake-utils.lib.eachDefaultSystem (system: let + pkgs = import nixpkgs { + inherit system; + overlays = [ (import rust-overlay) ]; + }; + pkgsCross = import nixpkgs { + inherit system; + config = { + allowUnsupportedSystem = true; + #replaceStdenv = ({ pkgs }: pkgs.clangStdenvNoLibs ); + }; + crossSystem = { + config = nixTriple; + libc = "newlib"; + #rust.rustcTarget = rustTriple; + gcc = { + arch = "armv4t"; + }; + }; + }; + + rustToolchainFor = + p: + p.rust-bin.selectLatestNightlyWith ( + toolchain: + toolchain.minimal.override { + extensions = [ "rust-src" ]; + targets = [ ]; + } + ); + rustToolchain = rustToolchainFor pkgs; + + craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchainFor; + + myPackage = pkgs.callPackage ./package.nix { inherit craneLib rustToolchain rustTriple; }; + in { + inherit pkgsCross; + packages.default = myPackage; + }); +} diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..a65f696 --- /dev/null +++ b/package.nix @@ -0,0 +1,56 @@ +{ lib, craneLib, rustToolchain, rustTriple, gcc-arm-embedded }: let + linkerFilter = path: _type: builtins.match ".ld$" path != null; + txtFilter = path: _type: builtins.match ".txt$" path != null; + linkerOrCargo = path: type: + (linkerFilter path type) || (txtFilter path type ) || (craneLib.filterCargoSources path type); + commonArgs = let + unfilteredRoot = ./.; + src = lib.fileset.toSource { + root = unfilteredRoot; + fileset = lib.fileset.unions [ + # Default files from crane (Rust and cargo files) + (craneLib.fileset.commonCargoSources unfilteredRoot) + # Also keep any linker files + (lib.fileset.fileFilter (file: file.hasExt "ld") unfilteredRoot) + # Also keep any txt files + (lib.fileset.fileFilter (file: file.hasExt "txt") unfilteredRoot) + # Example of a folder for images, icons, etc + (lib.fileset.maybeMissing ./src/foo.txt) + (lib.fileset.maybeMissing ./build.rs) + ]; + }; + in { + inherit src; + strictDeps = true; + cargoVendorDir = craneLib.vendorMultipleCargoDeps rec { + inherit (craneLib.findCargoFiles src) cargoConfigs; + cargoLockList = [ + "${unfilteredRoot}/Cargo.lock" + # Unfortunately this approach requires IFD (import-from-derivation) + # otherwise Nix will refuse to read the Cargo.lock from our toolchain + # (unless we build with `--impure`). + # + # Another way around this is to manually copy the rustlib `Cargo.lock` + # to the repo and import it with `./path/to/rustlib/Cargo.lock` which + # will avoid IFD entirely but will require manually keeping the file + # up to date! + "${rustToolchain.passthru.availableComponents.rust-src}/lib/rustlib/src/rust/library/Cargo.lock" + ]; + }; + + cargoExtraArgs = "-Z build-std --target ${rustTriple}"; + nativeBuildInputs = [ + # Add additional build inputs here + gcc-arm-embedded + ]; + doCheck = false; + }; + artifacts = craneLib.buildDepsOnly(commonArgs // { + pname = "katgba-deps"; + }); + package = craneLib.buildPackage (commonArgs // rec{ + cargoArtifacts = artifacts; + + doCheck = false; + }); + in package diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..0d2b020 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly" +targets = ["thumbv4t-none-eabi"] +profile = "minimal" +components = ["rust-src", "clippy", "rustfmt"] diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..1c89303 --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +{ + mkShell, + mgba, + rust-bin +}: mkShell { + packages = [ + rust-bin.selectLatestNightlyWith (toolchain: toolchain.default) + ]; +}; + diff --git a/src/foo.txt b/src/foo.txt new file mode 100644 index 0000000..c305048 --- /dev/null +++ b/src/foo.txt @@ -0,0 +1 @@ +usedinhello.rs \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..67fbd78 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,101 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; +use gba::prelude::*; + +#[panic_handler] +fn panic_handler(info: &core::panic::PanicInfo) -> ! { + #[cfg(debug_assertions)] + if let Ok(mut logger) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Fatal) { + writeln!(logger, "{info}").ok(); + } + loop {} +} + +#[allow(dead_code)] +const FOO: Align4<[u8; 14]> = include_aligned_bytes!("foo.txt"); + +#[unsafe(link_section = ".ewram")] +static FRAME_KEYS: GbaCell = GbaCell::new(KeyInput::new()); + +#[unsafe(link_section = ".iwram")] +extern "C" fn irq_handler(_: IrqBits) { + // We'll read the keys during vblank and store it for later. + FRAME_KEYS.write(KEYINPUT.read()); +} + +#[unsafe(no_mangle)] +extern "C" fn main() -> ! { + RUST_IRQ_HANDLER.write(Some(irq_handler)); + DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true)); + IE.write(IrqBits::VBLANK); + IME.write(true); + + if let Ok(mut logger) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Debug) { + writeln!(logger, "hello!").ok(); + + let fx_u: Fixed = + Fixed::::wrapping_from(7) + Fixed::::from_bits(12); + writeln!(logger, "fixed unsigned: {fx_u:?}").ok(); + + let fx_i1: Fixed = + Fixed::::wrapping_from(8) + Fixed::::from_bits(15); + writeln!(logger, "fixed signed positive: {fx_i1:?}").ok(); + + let fx_i2: Fixed = Fixed::::wrapping_from(0) + - Fixed::::wrapping_from(3) + - Fixed::::from_bits(17); + writeln!(logger, "fixed signed negative: {fx_i2:?}").ok(); + } + + { + // get our tile data into memory. + Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0); + } + + { + // set up the tilemap + let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap(); + for y in 0..16 { + let row = tsb.get_row(y).unwrap(); + for (x, addr) in row.iter().enumerate().take(16) { + let te = TextEntry::new().with_tile((y * 16 + x) as u16); + addr.write(te); + } + } + } + + { + // Set BG0 to use the tilemap we just made, and set it to be shown. + BG0CNT.write(BackgroundControl::new().with_screenblock(31)); + DISPCNT.write(DisplayControl::new().with_show_bg0(true)); + } + + let mut x_off = 0_u32; + let mut y_off = 0_u32; + let mut backdrop_color = Color(0); + loop { + VBlankIntrWait(); + // show current frame + BACKDROP_COLOR.write(backdrop_color); + BG0HOFS.write(x_off as u16); + BG0VOFS.write(y_off as u16); + + // prep next frame + let k = FRAME_KEYS.read(); + backdrop_color = Color(k.to_u16()); + if k.up() { + y_off = y_off.wrapping_add(1); + } + if k.down() { + y_off = y_off.wrapping_sub(1); + } + if k.left() { + x_off = x_off.wrapping_add(1); + } + if k.right() { + x_off = x_off.wrapping_sub(1); + } + } +}