Skip to main content

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#![warn(missing_docs)]
27
28//! # agb
29//! `agb` is a library for making games on the Game Boy Advance using rust.
30//!
31//! The library's main focus is to provide an abstraction that allows you to develop games which take advantage of the GBA's
32//! capabilities without needing to have extensive knowledge of its low-level implementation.
33//!
34//! `agb` provides the following features:
35//! * Simple build process with minimal dependencies
36//! * Built in importing of sprites, backgrounds, music and sound effects
37//! * High performance audio mixer
38//! * Easy to use sprite and tiled background usage
39//! * A global allocator allowing for use of both core and alloc
40//!
41//! A more detailed walkthrough can be found in [the book](https://agbrs.dev/book), or you can play with the
42//! [interactive examples](https://agbrs.dev/examples) to get a better feel of what's possible.
43
44// needed to be able to refer to this crate as `agb`
45extern crate self as agb;
46
47/// Include background tiles from a png, bmp or aseprite file.
48///
49/// This macro is used to convert a png, bmp or aseprite file into a format usable by the Game Boy Advance.
50///
51/// Suppose you have a file in `examples/gfx/beach-background.aseprite` which contains some tiles you'd like to use.
52///
53/// You import them using:
54/// ```rust,no_run
55/// # #![no_std]
56/// # #![no_main]
57/// agb::include_background_gfx!(
58///     mod backgrounds,
59///     BEACH => "examples/gfx/beach-background.aseprite"
60/// );
61/// ```
62///
63/// This will generate something along the lines of the following:
64///
65/// ```rust,ignore
66/// // module name comes from the first argument, name of the constant from the arrow
67/// mod backgrounds {
68///     pub static BEACH: TileData = /* ... */;
69///     pub static PALETTES: Palette16[] = /* ... */;
70/// }
71/// ```
72///
73/// And `BEACH` will be an instance of [`TileData`][crate::display::tile_data::TileData]
74///
75/// You can import multiple files at once, and the palette data will be combined so they can all be visible.
76///
77/// ```rust,no_run
78/// # #![no_std]
79/// # #![no_main]
80/// agb::include_background_gfx!(
81///     mod backgrounds,
82///     BEACH => "examples/gfx/beach-background.aseprite",
83///     HUD => "examples/gfx/hud.aseprite",
84/// );
85/// ```
86///
87/// # Palettes
88///
89/// The Game Boy Advance, in 16-colour mode can have at most 16 palettes each of size 16.
90/// Each tile can only refer to a single one of those palettes.
91/// `include_background_gfx!` will try its best to arrange the colours in the palettes
92/// such that the passed background file can be displayed.
93///
94/// However, this isn't always possible if your background has too many colours in one tile,
95/// or too many varieties of palettes between the individual tiles.
96/// If this happens, then the call to `include_background_gfx!` will fail at compile time.
97/// You can fix this by either importing as 256 colours, or by changing your backgrounds to
98/// use fewer colour variations.
99///
100/// # Transparent backgrounds
101///
102/// The GBA supports a single transparent colour. Any pixels marked with full alpha transparency
103/// in the background will be mapped to the first colour of the relevant palette, which is displayed
104/// as transparent.
105///
106/// However, that transparency colour will be the one shown behind any background so any space which
107/// has no tiles, or you can see all the way through will be shown using that colour.
108///
109/// You can configure which colour that will be with the optional second argument to `include_background_gfx!`
110///
111/// ```rust,no_run
112/// # #![no_std]
113/// # #![no_main]
114/// agb::include_background_gfx!(
115///     mod backgrounds,
116///     "00bdfe", // the sky colour hex code
117///     BEACH => "examples/gfx/beach-background.aseprite",
118///     HUD => "examples/gfx/hud.aseprite",
119/// );
120/// ```
121///
122/// # Deduplication
123///
124/// If your background has a large number of repeated 8x8 tiles (like the beach background above),
125/// then you can let the tile importing do the hard bit of deduplicating those tiles and with that
126/// you'll save some video RAM, which will then allow you to use even more tiles.
127///
128/// Note that once you've used deduplication, you need to use the [`TileData::settings`](display::display_data::TileData.settings)
129/// field in order to be able to actually display your given tiles. This is because the tiles
130/// could be flipped horizontally or vertically (or both) and combined with other tiles.
131///
132/// ```rust,no_run
133/// # #![no_std]
134/// # #![no_main]
135/// agb::include_background_gfx!(
136///     mod backgrounds,
137///     BEACH => deduplicate "examples/gfx/beach-background.aseprite",
138/// );
139/// ```
140///
141/// # 256 colours
142///
143/// The Game Boy Advance supports both 16-colour and 256-colour tiles. If you're using 256 colours
144/// in some (or all of) your backgrounds, you'll have to include them in 256 colour mode. You are
145/// required to use 256 colour backgrounds with affine tiles.
146///
147/// ```rust,no_run
148/// # #![no_std]
149/// # #![no_main]
150/// agb::include_background_gfx!(
151///     mod backgrounds,
152///     BEACH => 256 "examples/gfx/beach-background.aseprite",
153///     HUD => "examples/gfx/hud.aseprite", // you can still import 16-colour backgrounds at the same time
154/// );
155/// ```
156///
157/// # Module visibility
158///
159/// The resulting module that's being exported can have a different visibility if you want it to.
160/// So for instance you could make the resulting module `pub` or `pub(crate)` as follows:
161///
162/// ```rust,no_run
163/// # #![no_std]
164/// # #![no_main]
165/// agb::include_background_gfx!(
166///     pub mod backgrounds,
167///     BEACH => "examples/gfx/beach-background.aseprite",
168/// );
169///
170/// agb::include_background_gfx!(
171///     pub(crate) mod backgrounds2,
172///     BEACH => "examples/gfx/beach-background.aseprite",
173/// );
174/// ```
175///
176/// # `$OUT_DIR`
177///
178/// You may be generating the backgrounds as part of your `build.rs` file. If you're doing that, you'll
179/// want to put the generated files in `$OUT_DIR`. You can refer to this as part of the file name:
180///
181/// ```rust,ignore
182/// # #![no_std]
183/// # #![no_main]
184/// # use agb::include_background_gfx;
185/// include_background_gfx!(mod generated_background, "000000", DATA => "$OUT_DIR/generated_background.aseprite");
186/// ```
187///
188/// # Examples
189///
190/// ## `fill_with` and displaying a full screen background
191///
192/// This example uses [`RegularBackground::fill_with`](display::tiled::RegularBackground::fill_with)
193/// to fill the screen with a screen-sized image.
194///
195/// ```rust
196/// # #![no_std]
197/// # #![no_main]
198/// use agb::{
199///     display::{
200///         tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, RegularBackground},
201///         Priority,
202///     },
203///     include_background_gfx,
204/// };
205///
206/// agb::include_background_gfx!(
207///     pub mod backgrounds,
208///     BEACH => "examples/gfx/beach-background.aseprite",
209/// );
210///
211/// # #[agb::doctest]
212/// # fn test(mut gba: agb::Gba) {
213/// let mut gfx = gba.graphics.get();
214/// gfx.set_background_palettes(backgrounds::PALETTES);
215///
216/// let mut bg = RegularBackground::new(
217///     Priority::P0,
218///     RegularBackgroundSize::Background32x32,
219///     TileFormat::FourBpp,
220/// );
221/// bg.fill_with(&backgrounds::BEACH);
222/// # }
223/// ```
224///
225/// ## Combining modifiers
226///
227/// Modifiers can be combined, so you can import and deduplicate a 256 colour background.
228///
229/// ```rust,no_run
230/// # #![no_std]
231/// # #![no_main]
232/// agb::include_background_gfx!(
233///     mod backgrounds,
234///     BEACH => 256 deduplicate "examples/gfx/beach-background.aseprite",
235///     HUD => deduplicate "examples/gfx/hud.aseprite", // you can still import 16-colour backgrounds at the same time
236/// );
237/// ```
238pub use agb_image_converter::include_background_gfx;
239
240#[doc(hidden)]
241pub use agb_image_converter::include_aseprite_inner;
242
243#[doc(hidden)]
244pub use agb_image_converter::include_font as include_font_inner;
245
246#[doc(hidden)]
247pub use agb_image_converter::include_colours_inner;
248
249#[doc(hidden)]
250pub use agb_image_converter::include_aseprite_256_inner;
251
252#[macro_export]
253/// Includes a font to be usable by dynamic font rendering.
254///
255/// # TTF fonts
256///
257/// The first parameter is the path to a `.ttf` file and the second is the point size.
258///
259/// ```rust
260/// # #![no_std]
261/// # #![no_main]
262/// use agb::{display::font::Font, include_font};
263///
264/// static FONT: Font = include_font!("fnt/ark-pixel-10px-proportional-latin.ttf", 10);
265///
266/// # #[agb::doctest]
267/// # fn test(gba: agb::Gba) {}
268/// ```
269///
270/// # Pixel fonts from JSON
271///
272/// If you have a pixel font as an image, you can use
273/// [YAL's Pixel Font Converter](https://yal.cc/tools/pixel-font/) to generate a
274/// JSON description of it. Pass the path to the `.json` file as the first argument
275/// and the path to the image file as the second argument.
276///
277/// ```rust
278/// # #![no_std]
279/// # #![no_main]
280/// use agb::{display::font::Font, include_font};
281///
282/// static FONT: Font = include_font!(
283///     "examples/font/Dungeon Puzzler Font.json",
284///     "examples/font/Dungeon Puzzler Font.aseprite"
285/// );
286/// #
287/// # #[agb::doctest]
288/// # fn test(gba: agb::Gba) {}
289/// ```
290macro_rules! include_font {
291    ($font_path: literal, $font_size: literal) => {{
292        use $crate::display::font::{Font, FontLetter};
293        $crate::include_font_inner!($font_path, $font_size)
294    }};
295}
296
297/// This macro declares the entry point to your game written using `agb`.
298///
299/// It is already included in the template, but your `main` function must be annotated with `#[agb::entry]`, takes 1 argument and never returns.
300/// Doing this will ensure that `agb` can correctly set up the environment to call your rust function on start up.
301///
302/// # Examples
303/// ```no_run,rust
304/// #![no_std]
305/// #![no_main]
306///
307/// use agb::Gba;
308///
309/// #[agb::entry]
310/// fn main(mut gba: Gba) -> ! {
311///     loop {}
312/// }
313/// ```
314pub use agb_macros::entry;
315
316/// This macro can be used to write a doc test
317///
318/// If you want to write a doc test using agb, use this macro.
319/// You probably want to hide the use of `doctest` in your actual code (see the other examples in the agb codebase).
320/// It works very similarly to [`agb::entry`], except that the function should return or the test will run forever.
321///
322/// You still need to include the `#![no_std]` and `#![no_main]` or it won't compile.
323///
324/// ```rust
325/// #![no_std]
326/// #![no_main]
327///
328/// #[agb::doctest]
329/// fn test(gba: agb::Gba) {
330///     assert_eq!(1, 1);
331/// }
332/// ```
333pub use agb_macros::doctest;
334
335#[doc(hidden)]
336pub use agb_sound_converter::include_wav as include_wav_inner;
337
338/// Include a wav file to be used for sound effects or music.
339///
340/// The parameter is the path to the sound file relative to the root of your crate.
341/// This macro can be thought of returning a [`SoundData`](sound::mixer::SoundData).
342///
343/// The `include_wav` macro does not do any resampling, so it is up to you to make
344/// sure that the frequency of the wav file matches the one you've configured the
345/// mixer with. If there is a mismatch, the audio will play at the wrong speed
346/// resulting in a higher or lower pitch.
347///
348/// You can import stereo, but you need to call [`SoundChannel::stereo()`](sound::mixer::SoundChannel::stereo)
349/// or it'll play as mono at half speed.
350///
351/// ```rust
352/// # #![no_std]
353/// # #![no_main]
354/// use agb::{sound::mixer::SoundData, include_wav};
355///
356/// static JUMP_SOUND: SoundData = include_wav!("examples/sfx/jump.wav");
357/// # #[agb::doctest]
358/// # fn test(gba: agb::Gba) {}
359/// ```
360#[macro_export]
361macro_rules! include_wav {
362    ($filepath: literal) => {{
363        use $crate::sound::mixer::SoundData;
364        $crate::include_wav_inner!($filepath)
365    }};
366}
367
368extern crate alloc;
369mod agb_alloc;
370
371mod agbabi;
372#[cfg(feature = "backtrace")]
373mod backtrace;
374pub mod display;
375/// Provides access to the GBA's direct memory access (DMA) for advanced graphical effects.
376pub mod dma;
377/// Button inputs to the system.
378pub mod input;
379/// Interacting with the GBA interrupts.
380pub mod interrupt;
381mod memory_mapped;
382/// Implements logging to the mgba emulator.
383pub(crate) mod mgba;
384#[doc(inline)]
385pub use agb_fixnum as fixnum;
386#[doc(inline)]
387pub use agb_hashmap as hash_map;
388#[cfg(feature = "backtrace")]
389mod panics_render;
390#[doc(hidden)]
391pub mod print;
392pub(crate) mod refcount;
393/// Simple random number generator.
394pub mod rng;
395pub mod save;
396mod single;
397/// Implements sound output.
398pub mod sound;
399/// A module containing functions and utilities useful for synchronizing state.
400mod sync;
401/// System BIOS calls / syscalls.
402pub(crate) mod syscall;
403/// Interactions with the internal timers.
404pub mod timer;
405pub(crate) mod util;
406
407mod no_game;
408pub use no_game::no_game;
409
410mod global_asm;
411
412/// Re-exports of situationally useful crates for GBA development
413///
414/// `agb` will refer to these types directly, so if you need anything from
415/// any of the referred to crates, you can use these references to avoid needing
416/// to match version numbers in your game vs. the `agb` crate's version.
417pub mod external {
418    pub use critical_section;
419    pub use once_cell;
420    pub use portable_atomic;
421}
422
423use crate::display::tiled::VRAM_MANAGER;
424
425pub use {agb_alloc::ExternalAllocator, agb_alloc::InternalAllocator};
426
427#[cfg(any(test, feature = "testing", feature = "backtrace"))]
428#[panic_handler]
429fn panic_implementation(info: &core::panic::PanicInfo) -> ! {
430    avoid_double_panic(info);
431
432    #[cfg(feature = "backtrace")]
433    let frames = backtrace::unwind_exception();
434
435    #[cfg(feature = "testing")]
436    if let Some(mut mgba) = mgba::Mgba::new() {
437        let _ = mgba.print(format_args!("[failed]"), mgba::DebugLevel::Error);
438    }
439
440    #[cfg(feature = "backtrace")]
441    crate::panics_render::render_backtrace(&frames, info);
442
443    #[cfg(not(feature = "backtrace"))]
444    if let Some(mut mgba) = mgba::Mgba::new() {
445        let _ = mgba.print(format_args!("{info}"), mgba::DebugLevel::Fatal);
446    }
447
448    #[cfg(not(feature = "backtrace"))]
449    loop {
450        halt();
451    }
452}
453
454// If we panic during the panic handler, then there isn't much we can do any more. So this code
455// just infinite loops halting the CPU.
456fn avoid_double_panic(info: &core::panic::PanicInfo) {
457    static IS_PANICKING: portable_atomic::AtomicBool = portable_atomic::AtomicBool::new(false);
458
459    if IS_PANICKING.load(portable_atomic::Ordering::SeqCst) {
460        if let Some(mut mgba) = mgba::Mgba::new() {
461            let _ = mgba.print(
462                format_args!("Double panic: {info}"),
463                mgba::DebugLevel::Fatal,
464            );
465        }
466        loop {
467            halt();
468        }
469    } else {
470        IS_PANICKING.store(true, portable_atomic::Ordering::SeqCst);
471    }
472}
473
474/// Controls access to the Game Boy Advance's hardware.
475///
476/// This struct exists to make it the borrow checker's responsibility to ensure no clashes of global resources.
477/// It will be created for you via the [`#[agb::entry]`][entry] attribute.
478///
479/// # Examples
480///
481/// ```no_run,rust
482/// #![no_std]
483/// #![no_main]
484///
485/// use agb::Gba;
486///
487/// #[agb::entry]
488/// fn main(mut gba: Gba) -> ! {
489///     // Do whatever you need to do with gba
490///
491///     loop {
492///         agb::halt();
493///     }
494/// }
495/// ```
496#[non_exhaustive]
497pub struct Gba {
498    /// Manages access to the Game Boy Advance's display hardware
499    pub graphics: display::GraphicsDist,
500    /// Manages access to the Game Boy Advance's direct sound mixer for playing raw wav files.
501    pub mixer: sound::mixer::MixerController,
502    /// Manages access to the Game Boy Advance cartridge's save chip.
503    pub save: save::SaveManager,
504    /// Manages access to the Game Boy Advance's 4 timers.
505    pub timers: timer::TimerController,
506}
507
508impl Gba {
509    #[doc(hidden)]
510    #[must_use]
511    /// # Safety
512    ///
513    /// May only be called a single time. It is not needed to call this due to
514    /// it being called internally by the [`entry`] macro.
515    pub unsafe fn new_in_entry() -> Self {
516        unsafe {
517            VRAM_MANAGER.init();
518        }
519
520        unsafe { Self::single_new() }
521    }
522
523    const unsafe fn single_new() -> Self {
524        Self {
525            graphics: display::GraphicsDist,
526            mixer: sound::mixer::MixerController::new(),
527            save: save::SaveManager::new(),
528            timers: timer::TimerController::new(),
529        }
530    }
531}
532
533/// Halts the CPU until an interrupt occurs.
534///
535/// The CPU is switched to a low-power mode but all other subsystems continue running.
536/// You would mainly use this if you are stopping the game, and want to put an infinite loop without
537/// using 100% of the CPU.
538///
539/// Once an interrupt occurs, this function will return.
540///
541/// ```rust,no_run
542/// #![no_std]
543/// #![no_main]
544///
545/// use agb::Gba;
546///
547/// #[agb::entry]
548/// fn main(mut gba: Gba) -> ! {
549///     // your game code here
550///
551///     loop {
552///         agb::halt();
553///     }
554/// }
555/// ```
556pub fn halt() {
557    syscall::halt();
558}
559
560#[cfg(any(test, feature = "testing"))]
561/// *Unstable* support for running tests using `agb`.
562///
563/// In order to use this, you need to enable the unstable `custom_test_framework` feature and copy-paste
564/// the following into the top of your application:
565///
566/// ```rust,ignore
567/// #![cfg_attr(test, feature(custom_test_frameworks))]
568/// #![cfg_attr(test, reexport_test_harness_main = "test_main")]
569/// #![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
570/// ```
571///
572/// With this support, you will be able to write tests which you can run using `mgba-test-runner`.
573/// Tests are written using `#[test_case]` rather than `#[test]`.
574///
575/// ```rust,ignore
576/// #[test_case]
577/// fn dummy_test(_gba: &mut Gba) {
578///     assert_eq!(1, 1);
579/// }
580/// ```
581///
582/// You can run the tests using `cargo test`, but it will work better through `mgba-test-runner` by
583/// running something along the lines of `CARGO_TARGET_THUMBV4T_NONE_EABI_RUNNER=mgba-test-runner cargo test`.
584pub mod test_runner {
585    use util::SyncUnsafeCell;
586
587    use super::*;
588
589    #[doc(hidden)]
590    pub trait Testable {
591        fn run(&self, gba: &mut Gba);
592    }
593
594    impl<T> Testable for T
595    where
596        T: Fn(&mut Gba),
597    {
598        fn run(&self, gba: &mut Gba) {
599            let mut mgba = mgba::Mgba::new().unwrap();
600            mgba.print(
601                format_args!("{}...", core::any::type_name::<T>()),
602                mgba::DebugLevel::Info,
603            )
604            .unwrap();
605            mgba::test_runner_measure_cycles();
606            self(gba);
607            mgba::test_runner_measure_cycles();
608
609            mgba.print(format_args!("[ok]"), mgba::DebugLevel::Info)
610                .unwrap();
611        }
612    }
613
614    static TEST_GBA: SyncUnsafeCell<Option<Gba>> = SyncUnsafeCell::new(None);
615
616    #[doc(hidden)]
617    pub fn test_runner(tests: &[&dyn Testable]) {
618        let mut mgba = mgba::Mgba::new().unwrap();
619        mgba.print(
620            format_args!("Running {} tests", tests.len()),
621            mgba::DebugLevel::Info,
622        )
623        .unwrap();
624
625        let gba = unsafe { &mut *TEST_GBA.get() }.as_mut().unwrap();
626
627        for test in tests {
628            test.run(gba);
629        }
630
631        mgba.print(
632            format_args!("Tests finished successfully"),
633            mgba::DebugLevel::Info,
634        )
635        .unwrap();
636    }
637
638    #[cfg(test)]
639    #[entry]
640    fn agb_test_main(_gba: Gba) -> ! {
641        #[allow(clippy::empty_loop)]
642        loop {} // full implementation provided by the #[entry]
643    }
644
645    #[doc(hidden)]
646    pub fn agb_start_tests(gba: Gba, test_main: impl Fn()) -> ! {
647        *unsafe { &mut *TEST_GBA.get() } = Some(gba);
648        test_main();
649        #[allow(clippy::empty_loop)]
650        loop {}
651    }
652
653    /// Asserts that the current screen matches the provided image
654    ///
655    /// Uses capabilities built into `mgba-test-runner` to assert that the
656    /// current content of the screen matches the image located at the provided
657    /// path. Does nothing if you are not using `mgba-test-runner`.
658    ///
659    /// If the image does not exist, `mgba-test-runner` will write the screen to
660    /// the file and fail.
661    pub fn assert_image_output(image: &str) {
662        display::busy_wait_for_vblank();
663        display::busy_wait_for_vblank();
664        let mut mgba = crate::mgba::Mgba::new().unwrap();
665        mgba.print(format_args!("image:{image}"), crate::mgba::DebugLevel::Info)
666            .unwrap();
667        display::busy_wait_for_vblank();
668    }
669}
670
671#[cfg(test)]
672mod test {
673    use core::ptr::addr_of_mut;
674
675    use super::Gba;
676
677    #[test_case]
678    #[allow(clippy::eq_op)]
679    fn trivial_test(_gba: &mut Gba) {
680        assert_eq!(1, 1);
681    }
682
683    #[test_case]
684    fn gba_struct_is_zero_sized(_gba: &mut Gba) {
685        use core::mem;
686        assert_eq!(mem::size_of::<Gba>(), 0);
687    }
688
689    #[test_case]
690    fn wait_30_frames(_gba: &mut Gba) {
691        let vblank = crate::interrupt::VBlank::get();
692        let mut counter = 0;
693        loop {
694            if counter > 30 {
695                break;
696            }
697            vblank.wait_for_vblank();
698            counter += 1;
699        }
700    }
701
702    #[unsafe(link_section = ".ewram")]
703    static mut EWRAM_TEST: u32 = 5;
704    #[test_case]
705    fn ewram_static_test(_gba: &mut Gba) {
706        unsafe {
707            let ewram_ptr = addr_of_mut!(EWRAM_TEST);
708            let content = ewram_ptr.read_volatile();
709            assert_eq!(content, 5, "expected data in ewram to be 5");
710            ewram_ptr.write_volatile(content + 1);
711            let content = ewram_ptr.read_volatile();
712            assert_eq!(content, 6, "expected data to have increased by one");
713            let address = ewram_ptr as usize;
714            assert!(
715                (0x0200_0000..0x0204_0000).contains(&address),
716                "ewram is located between 0x0200_0000 and 0x0204_0000, address was actually found to be {address:#010X}",
717            );
718        }
719    }
720
721    #[unsafe(link_section = ".iwram")]
722    static mut IWRAM_EXPLICIT: u32 = 9;
723    #[test_case]
724    fn iwram_explicit_test(_gba: &mut Gba) {
725        unsafe {
726            let iwram_ptr = addr_of_mut!(IWRAM_EXPLICIT);
727            let address = iwram_ptr as usize;
728            assert!(
729                (0x0300_0000..0x0300_8000).contains(&address),
730                "iwram is located between 0x0300_0000 and 0x0300_8000, but was actually found to be at {address:#010X}"
731            );
732            let c = iwram_ptr.read_volatile();
733            assert_eq!(c, 9, "expected content to be 9");
734            iwram_ptr.write_volatile(u32::MAX);
735            let c = iwram_ptr.read_volatile();
736            assert_eq!(c, u32::MAX, "expected content to be {}", u32::MAX);
737        }
738    }
739
740    static mut IMPLICIT_STORAGE: u32 = 9;
741    #[test_case]
742    fn implicit_data_test(_gba: &mut Gba) {
743        unsafe {
744            let iwram_ptr = addr_of_mut!(IMPLICIT_STORAGE);
745            let address = iwram_ptr as usize;
746            assert!(
747                (0x0200_0000..0x0204_0000).contains(&address),
748                "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}"
749            );
750            let c = iwram_ptr.read_volatile();
751            assert_eq!(c, 9, "expected content to be 9");
752            iwram_ptr.write_volatile(u32::MAX);
753            let c = iwram_ptr.read_volatile();
754            assert_eq!(c, u32::MAX, "expected content to be {}", u32::MAX);
755        }
756    }
757}