agb/
lib.rs

1#![no_std]
2// This appears to be needed for testing to work
3#![cfg_attr(any(test, feature = "testing"), no_main)]
4#![cfg_attr(any(test, feature = "testing"), feature(custom_test_frameworks))]
5#![cfg_attr(
6    any(test, feature = "testing"),
7    test_runner(crate::test_runner::test_runner)
8)]
9#![cfg_attr(
10    any(test, feature = "testing"),
11    reexport_test_harness_main = "test_main"
12)]
13#![feature(allocator_api)]
14#![warn(clippy::all)]
15#![allow(clippy::needless_pass_by_ref_mut)]
16#![deny(clippy::must_use_candidate)]
17#![deny(clippy::trivially_copy_pass_by_ref)]
18#![deny(clippy::semicolon_if_nothing_returned)]
19#![deny(clippy::map_unwrap_or)]
20#![deny(clippy::needless_pass_by_value)]
21#![deny(clippy::redundant_closure_for_method_calls)]
22#![deny(clippy::cloned_instead_of_copied)]
23#![deny(rustdoc::broken_intra_doc_links)]
24#![deny(rustdoc::private_intra_doc_links)]
25#![deny(rustdoc::invalid_html_tags)]
26
27//! # agb
28//! `agb` is a library for making games on the Game Boy Advance using the Rust
29//! programming language. It attempts to be a high level abstraction over the
30//! internal workings of the Game Boy Advance whilst still being high
31//! performance and memory efficient.
32//!
33//! To get started with agb, you should clone the [template repo](https://github.com/agbrs/template) and work from there.
34
35/// This macro is used to convert a png, bmp or aseprite file into a format usable by the Game Boy Advance.
36///
37/// Suppose you have a file in `examples/water_tiles.png` which contains some tiles you'd like to use.
38///
39/// You import them using:
40/// ```rust,no_run
41/// ##![no_std]
42/// ##![no_main]
43/// agb::include_background_gfx!(water_tiles, tiles => "examples/water_tiles.png");
44/// ```
45///
46/// This will generate something along the lines of the following:
47///
48/// ```rust,ignore
49/// // module name comes from the first argument, name of the constant from the arrow
50/// mod water_tiles {
51///     pub static tiles = /* ... */;
52/// }
53/// ```
54///
55/// And tiles will be an instance of [`TileData`][crate::display::tile_data::TileData]
56///
57/// You can import multiple files at once, and the palette data will be combined so they can all be visible.
58///
59/// # Examples
60///
61/// Assume the tiles are loaded as above
62///
63/// In `src/main.rs`:
64/// ```rust,no_run
65/// ##![no_std]
66/// ##![no_main]
67/// #
68/// use agb::{
69///     display::{
70///         tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, Tiled0, TiledMap, VRamManager},
71///         Priority,
72///     },
73///     include_background_gfx,
74/// };
75///
76/// agb::include_background_gfx!(water_tiles, tiles => "examples/water_tiles.png");
77///
78/// # fn load_tileset(mut gfx: Tiled0, mut vram: VRamManager) {
79/// let tileset = &water_tiles::tiles.tiles;
80///
81/// vram.set_background_palettes(water_tiles::PALETTES);
82///
83/// let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32, tileset.format());
84///
85/// for y in 0..20u16 {
86///     for x in 0..30u16 {
87///         bg.set_tile(
88///             &mut vram,
89///             (x, y),
90///             tileset,
91///             water_tiles::tiles.tile_settings[0],
92///         );
93///     }
94/// }
95/// bg.commit(&mut vram);
96/// bg.set_visible(true);
97/// # }
98/// ```
99///
100/// Including from the out directory is supported through the `$OUT_DIR` token.
101///
102/// ```rust,ignore
103/// # #![no_std]
104/// # #![no_main]
105/// # use agb::include_background_gfx;
106/// include_background_gfx!(generated_background, "000000", DATA => "$OUT_DIR/generated_background.aseprite");
107/// ```
108///
109/// You can also make the exported background a public module which will allow other modules access them. The following
110/// will declare `water_tiles` as a `pub mod` rather than a `mod`.
111///
112/// ```rust,no_run
113/// ##![no_std]
114/// ##![no_main]
115/// agb::include_background_gfx!(pub water_tiles, tiles => "examples/water_tiles.png");
116/// ```
117pub use agb_image_converter::include_background_gfx;
118
119#[doc(hidden)]
120pub use agb_image_converter::include_aseprite_inner;
121
122#[doc(hidden)]
123pub use agb_image_converter::include_font as include_font_inner;
124
125#[doc(hidden)]
126pub use agb_image_converter::include_colours_inner;
127
128#[macro_export]
129macro_rules! include_font {
130    ($font_path: literal, $font_size: literal) => {{
131        use $crate::display;
132        $crate::include_font_inner!($font_path, $font_size)
133    }};
134}
135
136/// This macro declares the entry point to your game written using `agb`.
137///
138/// It is already included in the template, but your `main` function must be annotated with `#[agb::entry]`, takes 1 argument and never returns.
139/// Doing this will ensure that `agb` can correctly set up the environment to call your rust function on start up.
140///
141/// # Examples
142/// ```no_run,rust
143/// #![no_std]
144/// #![no_main]
145///
146/// use agb::Gba;
147///
148/// #[agb::entry]
149/// fn main(mut gba: Gba) -> ! {
150///     loop {}
151/// }
152/// ```
153pub use agb_macros::entry;
154
155pub use agb_sound_converter::include_wav;
156
157extern crate alloc;
158mod agb_alloc;
159
160mod agbabi;
161#[cfg(feature = "backtrace")]
162mod backtrace;
163mod bitarray;
164/// Implements everything relating to things that are displayed on screen.
165pub mod display;
166/// Provides access to the GBA's direct memory access (DMA) which is used for advanced effects
167pub mod dma;
168/// Button inputs to the system.
169pub mod input;
170/// Interacting with the GBA interrupts
171pub mod interrupt;
172mod memory_mapped;
173/// Implements logging to the mgba emulator.
174pub mod mgba;
175#[doc(inline)]
176pub use agb_fixnum as fixnum;
177/// Contains an implementation of a hashmap which suits the gameboy advance's hardware.
178pub use agb_hashmap as hash_map;
179#[cfg(feature = "backtrace")]
180mod panics_render;
181/// Simple random number generator
182pub mod rng;
183pub mod save;
184mod single;
185/// Implements sound output.
186pub mod sound;
187/// A module containing functions and utilities useful for synchronizing state.
188mod sync;
189/// System BIOS calls / syscalls.
190pub mod syscall;
191/// Interactions with the internal timers
192pub mod timer;
193pub(crate) mod util;
194
195mod no_game;
196
197/// Default game
198pub use no_game::no_game;
199
200pub(crate) mod arena;
201mod global_asm;
202
203pub mod external {
204    pub use critical_section;
205    pub use once_cell;
206    pub use portable_atomic;
207}
208
209pub use {agb_alloc::ExternalAllocator, agb_alloc::InternalAllocator};
210
211#[cfg(any(test, feature = "testing", feature = "backtrace"))]
212#[panic_handler]
213fn panic_implementation(info: &core::panic::PanicInfo) -> ! {
214    avoid_double_panic(info);
215
216    #[cfg(feature = "backtrace")]
217    let frames = backtrace::unwind_exception();
218
219    #[cfg(feature = "testing")]
220    if let Some(mut mgba) = mgba::Mgba::new() {
221        let _ = mgba.print(format_args!("[failed]"), mgba::DebugLevel::Error);
222    }
223
224    #[cfg(feature = "backtrace")]
225    crate::panics_render::render_backtrace(&frames, info);
226
227    #[cfg(not(feature = "backtrace"))]
228    if let Some(mut mgba) = mgba::Mgba::new() {
229        let _ = mgba.print(format_args!("{info}"), mgba::DebugLevel::Fatal);
230    }
231
232    #[cfg(not(feature = "backtrace"))]
233    loop {
234        syscall::halt();
235    }
236}
237
238// If we panic during the panic handler, then there isn't much we can do any more. So this code
239// just infinite loops halting the CPU.
240fn avoid_double_panic(info: &core::panic::PanicInfo) {
241    static IS_PANICKING: portable_atomic::AtomicBool = portable_atomic::AtomicBool::new(false);
242
243    if IS_PANICKING.load(portable_atomic::Ordering::SeqCst) {
244        if let Some(mut mgba) = mgba::Mgba::new() {
245            let _ = mgba.print(
246                format_args!("Double panic: {info}"),
247                mgba::DebugLevel::Fatal,
248            );
249        }
250        loop {
251            syscall::halt();
252        }
253    } else {
254        IS_PANICKING.store(true, portable_atomic::Ordering::SeqCst);
255    }
256}
257
258/// The Gba struct is used to control access to the Game Boy Advance's hardware in a way which makes it the
259/// borrow checker's responsibility to ensure no clashes of global resources.
260///
261/// This is will be created for you via the [`#[agb::entry]`][entry] attribute.
262///
263/// # Examples
264///
265/// ```no_run,rust
266/// #![no_std]
267/// #![no_main]
268///
269/// use agb::Gba;
270///
271/// #[agb::entry]
272/// fn main(mut gba: Gba) -> ! {
273///     // Do whatever you need to do with gba
274///
275///     loop {}
276/// }
277/// ```
278#[non_exhaustive]
279pub struct Gba {
280    /// Manages access to the Game Boy Advance's display hardware
281    pub display: display::Display,
282    /// Manages access to the Game Boy Advance's beeps and boops sound hardware as part of the
283    /// original Game Boy's sound chip (the DMG).
284    pub sound: sound::dmg::Sound,
285    /// Manages access to the Game Boy Advance's direct sound mixer for playing raw wav files.
286    pub mixer: sound::mixer::MixerController,
287    /// Manages access to the Game Boy Advance cartridge's save chip.
288    pub save: save::SaveManager,
289    /// Manages access to the Game Boy Advance's 4 timers.
290    pub timers: timer::TimerController,
291    /// Manages access to the Game Boy Advance's DMA
292    pub dma: dma::DmaController,
293}
294
295impl Gba {
296    #[doc(hidden)]
297    #[must_use]
298    /// # Safety
299    ///
300    /// May only be called a single time. It is not needed to call this due to
301    /// it being called internally by the [`entry`] macro.
302    pub unsafe fn new_in_entry() -> Self {
303        Self::single_new()
304    }
305
306    const unsafe fn single_new() -> Self {
307        Self {
308            display: display::Display::new(),
309            sound: sound::dmg::Sound::new(),
310            mixer: sound::mixer::MixerController::new(),
311            save: save::SaveManager::new(),
312            timers: timer::TimerController::new(),
313            dma: dma::DmaController::new(),
314        }
315    }
316}
317
318#[cfg(any(test, feature = "testing"))]
319/// *Unstable* support for running tests using `agb`
320///
321/// In order to use this, you need to enable the unstable `custom_test_framework` feature and copy-paste
322/// the following into the top of your application:
323///
324/// ```rust,ignore
325/// #![cfg_attr(test, feature(custom_test_frameworks))]
326/// #![cfg_attr(test, reexport_test_harness_main = "test_main")]
327/// #![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
328/// ```
329///
330/// With this support, you will be able to write tests which you can run using `mgba-test-runner`.
331/// Tests are written using `#[test_case]` rather than `#[test]`.
332///
333/// ```rust,ignore
334/// #[test_case]
335/// fn test_ping_pong(_gba: &mut Gba) {
336///     assert_eq!(1, 1);
337/// }
338/// ```
339///
340/// You can run the tests using `cargo test`, but it will work better through `mgba-test-runner` by
341/// running something along the lines of `CARGO_TARGET_THUMBV4T_NONE_EABI_RUNNER=mgba-test-runner cargo test`.
342pub mod test_runner {
343    use util::SyncUnsafeCell;
344
345    use super::*;
346
347    #[doc(hidden)]
348    pub trait Testable {
349        fn run(&self, gba: &mut Gba);
350    }
351
352    impl<T> Testable for T
353    where
354        T: Fn(&mut Gba),
355    {
356        fn run(&self, gba: &mut Gba) {
357            let mut mgba = mgba::Mgba::new().unwrap();
358            mgba.print(
359                format_args!("{}...", core::any::type_name::<T>()),
360                mgba::DebugLevel::Info,
361            )
362            .unwrap();
363            mgba::test_runner_measure_cycles();
364            self(gba);
365            mgba::test_runner_measure_cycles();
366
367            mgba.print(format_args!("[ok]"), mgba::DebugLevel::Info)
368                .unwrap();
369        }
370    }
371
372    static TEST_GBA: SyncUnsafeCell<Option<Gba>> = SyncUnsafeCell::new(None);
373
374    #[doc(hidden)]
375    pub fn test_runner(tests: &[&dyn Testable]) {
376        let mut mgba = mgba::Mgba::new().unwrap();
377        mgba.print(
378            format_args!("Running {} tests", tests.len()),
379            mgba::DebugLevel::Info,
380        )
381        .unwrap();
382
383        let gba = unsafe { &mut *TEST_GBA.get() }.as_mut().unwrap();
384
385        for test in tests {
386            test.run(gba);
387        }
388
389        mgba.print(
390            format_args!("Tests finished successfully"),
391            mgba::DebugLevel::Info,
392        )
393        .unwrap();
394    }
395
396    // needed to fudge the #[entry] below
397    #[cfg(test)]
398    mod agb {
399        pub mod test_runner {
400            pub use super::super::agb_start_tests;
401        }
402    }
403
404    #[cfg(test)]
405    #[entry]
406    fn agb_test_main(gba: Gba) -> ! {
407        #[allow(clippy::empty_loop)]
408        loop {} // full implementation provided by the #[entry]
409    }
410
411    #[doc(hidden)]
412    pub fn agb_start_tests(gba: Gba, test_main: impl Fn()) -> ! {
413        *unsafe { &mut *TEST_GBA.get() } = Some(gba);
414        test_main();
415        #[allow(clippy::empty_loop)]
416        loop {}
417    }
418
419    pub fn assert_image_output(image: &str) {
420        display::busy_wait_for_vblank();
421        display::busy_wait_for_vblank();
422        let mut mgba = crate::mgba::Mgba::new().unwrap();
423        mgba.print(format_args!("image:{image}"), crate::mgba::DebugLevel::Info)
424            .unwrap();
425        display::busy_wait_for_vblank();
426    }
427}
428
429#[inline(never)]
430pub(crate) fn program_counter_before_interrupt() -> u32 {
431    extern "C" {
432        static mut agb_rs__program_counter: u32;
433    }
434    unsafe { agb_rs__program_counter }
435}
436
437#[cfg(test)]
438mod test {
439    use core::ptr::addr_of_mut;
440
441    use super::Gba;
442
443    #[test_case]
444    #[allow(clippy::eq_op)]
445    fn trivial_test(_gba: &mut Gba) {
446        assert_eq!(1, 1);
447    }
448
449    #[test_case]
450    fn gba_struct_is_zero_sized(_gba: &mut Gba) {
451        use core::mem;
452        assert_eq!(mem::size_of::<Gba>(), 0);
453    }
454
455    #[test_case]
456    fn wait_30_frames(_gba: &mut Gba) {
457        let vblank = crate::interrupt::VBlank::get();
458        let mut counter = 0;
459        loop {
460            if counter > 30 {
461                break;
462            }
463            vblank.wait_for_vblank();
464            counter += 1;
465        }
466    }
467
468    #[link_section = ".ewram"]
469    static mut EWRAM_TEST: u32 = 5;
470    #[test_case]
471    fn ewram_static_test(_gba: &mut Gba) {
472        unsafe {
473            let ewram_ptr = addr_of_mut!(EWRAM_TEST);
474            let content = ewram_ptr.read_volatile();
475            assert_eq!(content, 5, "expected data in ewram to be 5");
476            ewram_ptr.write_volatile(content + 1);
477            let content = ewram_ptr.read_volatile();
478            assert_eq!(content, 6, "expected data to have increased by one");
479            let address = ewram_ptr as usize;
480            assert!(
481                (0x0200_0000..0x0204_0000).contains(&address),
482                "ewram is located between 0x0200_0000 and 0x0204_0000, address was actually found to be {address:#010X}",
483            );
484        }
485    }
486
487    #[link_section = ".iwram"]
488    static mut IWRAM_EXPLICIT: u32 = 9;
489    #[test_case]
490    fn iwram_explicit_test(_gba: &mut Gba) {
491        unsafe {
492            let iwram_ptr = addr_of_mut!(IWRAM_EXPLICIT);
493            let address = iwram_ptr as usize;
494            assert!(
495                (0x0300_0000..0x0300_8000).contains(&address),
496                "iwram is located between 0x0300_0000 and 0x0300_8000, but was actually found to be at {address:#010X}"
497            );
498            let c = iwram_ptr.read_volatile();
499            assert_eq!(c, 9, "expected content to be 9");
500            iwram_ptr.write_volatile(u32::MAX);
501            let c = iwram_ptr.read_volatile();
502            assert_eq!(c, u32::MAX, "expected content to be {}", u32::MAX);
503        }
504    }
505
506    static mut IMPLICIT_STORAGE: u32 = 9;
507    #[test_case]
508    fn implicit_data_test(_gba: &mut Gba) {
509        unsafe {
510            let iwram_ptr = addr_of_mut!(IMPLICIT_STORAGE);
511            let address = iwram_ptr as usize;
512            assert!(
513                (0x0200_0000..0x0204_0000).contains(&address),
514                "implicit data storage is expected to be in ewram, which is between 0x0300_0000 and 0x0300_8000, but was actually found to be at {address:#010X}"
515            );
516            let c = iwram_ptr.read_volatile();
517            assert_eq!(c, 9, "expected content to be 9");
518            iwram_ptr.write_volatile(u32::MAX);
519            let c = iwram_ptr.read_volatile();
520            assert_eq!(c, u32::MAX, "expected content to be {}", u32::MAX);
521        }
522    }
523}