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}