Compare commits

..

10 commits

Author SHA1 Message Date
Alex Janka 450a033b96 fix 2023-05-22 11:19:28 +10:00
Alex Janka 62e9c30337 Merge remote-tracking branch 'upstream/master' 2023-05-21 12:18:06 +10:00
Alex Janka 911b60bcd1 remove prints 2023-04-16 09:21:38 +10:00
Alex Janka 8e04c415d2 fixed multiboot linker script 2023-04-13 12:50:43 +10:00
Alex Janka 2f57168fb8
Merge branch 'agbrs:master' into master 2023-04-13 11:21:09 +10:00
Alex Janka cd50bf9673 idk what i scrwed up 2023-04-11 19:05:05 +10:00
Alex Janka c7591a3e37
Merge branch 'agbrs:master' into master 2023-04-10 09:54:07 +10:00
Alex Janka 6be8f02fd3 almost implemented normal link port function 2023-04-02 18:13:08 +10:00
Alex Janka e1ac328ce6 embedded-hal 2023-03-15 12:02:34 +11:00
Alex Janka 94228978eb first go @ serial 2023-03-12 19:08:16 +11:00
453 changed files with 12018 additions and 26769 deletions

BIN
.github/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

3
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,3 @@
- [ ] Changelog updated / no changelog update needed

9
.github/renovate.json vendored Normal file
View file

@ -0,0 +1,9 @@
{
"extends": [
"config:base"
],
"ignorePresets": [
":prHourlyLimit2"
],
"dependencyDashboardAutoclose": true
}

20
.github/scripts/update-lockfiles.sh vendored Executable file
View file

@ -0,0 +1,20 @@
#!/usr/bin/env bash
function update_lockfiles() {
find . -name Cargo.lock -execdir cargo update \;
}
update_lockfiles
update_lockfiles
update_lockfiles
update_lockfiles
git add -u
if [ "$(git diff --cached --name-only)" == '' ]; then
echo "No files updated"
exit 0
fi
git -c user.name="GBA bot" -c user.email="gw@ilym.me" commit -m 'Update lockfiles'
git push

27
.github/scripts/update-template-repo.sh vendored Executable file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -e # Fail if any command fails
set -x # print every command before it runs
# Updates the template repository to the content of the template directory
# Requires environment variable
# - GITHUB_USERNAME = a user who has push access to the template repository
# - API_TOKEN_GITHUB = an API token for the user
CLONE_DIR=$(mktemp -d)
git clone --single-branch --branch master "https://$GITHUB_USERNAME:$API_TOKEN_GITHUB@github.com/agbrs/template.git" "$CLONE_DIR"
# Copy the .git directory to a different place so we can ensure that only the relevant template stuff stays
TEMP_DIR=$(mktemp -d)
mv "$CLONE_DIR/.git" "$TEMP_DIR/.git"
cp -rva template/. "$TEMP_DIR"
# git describe will give a unique, friendly name for the current commit to make it easier to track where this came from
VERSION=$(git describe --tags)
COMMIT_MESSAGE="Update to $VERSION"
git -C "$TEMP_DIR" add .
git -C "$TEMP_DIR" -c user.email="gw@ilym.me" -c user.name="GBA bot" commit -m "$COMMIT_MESSAGE"
git -C "$TEMP_DIR" push origin HEAD

47
.github/workflows/build-and-test.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: Rust
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
schedule:
- cron: '10 5 * * *'
env:
CARGO_TERM_COLOR: always
jobs:
build:
name: Just CI
runs-on: ubuntu-20.04
steps:
- name: Install build tools
run: sudo apt-get update && sudo apt-get install build-essential binutils-arm-none-eabi libelf-dev zip -y
- name: Install Miri
run: |
rustup toolchain install nightly --component miri
rustup override set nightly
cargo miri setup
- uses: actions/checkout@v3
- name: Cache
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
~/target
mgba-test-runner/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: install mgba-test-runner
run: cargo install --path mgba-test-runner --verbose
- name: Set CARGO_TARGET_DIR
run: echo "CARGO_TARGET_DIR=$HOME/target" >> $GITHUB_ENV
- uses: extractions/setup-just@v1
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1
with:
mdbook-version: '0.4.13'
- name: Build and test all crates
run: just ci

54
.github/workflows/publish-agb.yml vendored Normal file
View file

@ -0,0 +1,54 @@
name: Publish agb
on:
push:
tags:
- v*
jobs:
build:
runs-on: ubuntu-20.04
steps:
- name: Install build tools
run: sudo apt-get update && sudo apt-get install build-essential binutils-arm-none-eabi zip -y
- name: Check out repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Login to crates.io
run: cargo login ${{ secrets.CRATE_API }}
- uses: extractions/setup-just@v1
- name: Publish crates
run: just publish
- name: Update template repo
env:
GITHUB_USERNAME: gwilymk
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}
run: bash .github/scripts/update-template-repo.sh
- name: Build the examples
run: just build-roms
- name: Upload examples to the release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: examples/target/examples.zip
asset_name: examples.zip
tag: ${{ github.ref }}
overwrite: true
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1
with:
mdbook-version: '0.4.13'
- name: Build the book
run: just build-book
- name: Deploy the book
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: book/book

22
.github/workflows/update-lockfiles.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: Update lockfiles
on:
push:
branches: [ 'renovate/*' ]
pull_request:
branches: [ 'renovate/*' ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-20.04
steps:
- name: Set CARGO_TARGET_DIR
run: echo "CARGO_TARGET_DIR=$HOME/target" >> $GITHUB_ENV
- name: Check out repository
uses: actions/checkout@v3
- uses: extractions/setup-just@v1
- name: Update lock files
run: just update-lockfiles

5
.gitignore vendored
View file

@ -1,5 +1,4 @@
target
/out
*.tiled-session
website/build
Cargo.lock
template/Cargo.lock
agb*/Cargo.lock

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "emulator/mgba-sys/mgba"]
path = emulator/mgba-sys/mgba
url = https://github.com/mgba-emu/mgba.git

View file

@ -1,55 +1,49 @@
{
"folders": [
{
"path": "../agb"
},
{
"path": "../agb-sound-converter"
},
{
"path": "../agb-macros"
},
{
"path": "../agb-image-converter"
},
{
"path": "../agb-fixnum"
},
{
"path": "../examples/the-purple-night"
},
{
"path": "../examples/the-hat-chooses-the-wizard"
},
{
"path": "../examples/hyperspace-roll"
},
{
"path": "../.github"
},
{
"path": "../template"
},
{
"path": "../book"
},
{
"path": "../mgba-test-runner"
},
{
"path": "../tools"
},
{
"path": "../examples/combo"
},
{
"path": "../agb-hashmap"
},
{
"path": "../examples/amplitude"
},
{
"path": "../examples/the-dungeon-puzzlers-lament"
}
]
}
"folders": [
{
"path": "../agb"
},
{
"path": "../agb-sound-converter"
},
{
"path": "../agb-macros"
},
{
"path": "../agb-image-converter"
},
{
"path": "../agb-fixnum"
},
{
"path": "../examples/the-purple-night"
},
{
"path": "../examples/the-hat-chooses-the-wizard"
},
{
"path": "../examples/hyperspace-roll"
},
{
"path": "../.github"
},
{
"path": "../template"
},
{
"path": "../book"
},
{
"path": "../mgba-test-runner"
},
{
"path": "../tools"
},
{
"path": "../examples/combo"
},
{
"path": "../agb-hashmap"
}
]
}

View file

@ -1,5 +1,4 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
@ -8,237 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Optional serde support for agb-hashmap via the `serde` feature flag
### Fixed
- Fixed build error due to breaking change in `xmrs`.
## [0.21.1] - 2024/10/02
### Added
- Added support for s3m and mod format files to `agb-tracker`.
- Added a `HashSet` implementation in `agb::hashmap`.
### Changed
- Changed how 16 colour palettes are optimised to give better average case results. You should find that
either your palettes will always import, or never import correctly.
## [0.21.0] - 2024/09/24
### Added
- Added support for vibrato in `agb-tracker`'s XM format
- Method to stop the tracker
### Changed
- `agb-tracker` now has an `agb` feature which you must enable to use it with `agb`. You won't notice
unless you've been using `default-features = false` in which case we recommend removing that from your `Cargo.toml`.
### Fixed
- There are no longer gaps between tiles in affine graphics modes.
### Added
- Added option to export imported background graphics from `include_background_gfx` as pub.
## [0.20.5] - 2024/06/18
### Fixed
- Resolved incompatibility with a dependency's update in `agb-tracker`. If you
are already using `agb-tracker`, then this won't yet cause an issue as your
lockfile will maintain the working version. However if start a new project, or
update dependencies, cargo will choose the later incompatible version.
## [0.20.4] - 2024/06/13
### Changed
- `manhattan_distance` and `magnitude_squared` no longer require the fixed point number.
## [0.20.3] - 2024/06/12
### Added
- Added `find_colour_index_16` and `find_colour_index_256` to the `VRamManager` to find where a colour is in a palette.
- Added `set_graphics_mode` to unmanaged sprites. This allows you to change to the blending and window modes.
### Fixed
- Affine background center position didn't work outside of the upper left quadrant of the gba's screen.
- Fixed backtrace pointing to the wrong line of code (being out by one).
- Fixes overflow caused by certain font characteristics on boundaries of sprites in the object text renderer.
## [0.20.2] - 2024/05/25
### Fixed
- Fixed the crash screen to show debug text even if the qr code fails to generate.
- Fixed the crash screen to prevent the qr code always failing to generate.
## [0.20.1] - 2024/05/17
### Added
- Added `dot` and `cross` product methods for `Vector2D`.
### Fixed
- Fixed an issue with agb tracker where XM files with linear frequencies were playing the wrong notes
## [0.20.0] - 2024/05/14
### Added
- Added a new crash screen which provides a mechanism for seeing a full stack trace of your program when it panics.
This requires a change to your `.cargo/config.toml`. You must add the rust flag `"-Cforce-frame-pointers=yes"` to
your rustflags field. This can also be disabled by removing the `backtrace` feature.
- Initial unicode support for font rendering.
- Kerning support for font rendering.
- Added `set_next` method to `OamIterator` to avoid repeated boilerplate when dealing with unmanaged objects.
### Fixed
- Export the `dma` module correctly so you can write the types from it and use it in more complex cases.
### Changed
- Many macros now emit statics rather than consts OR can be used as statics OR
have had examples changed to use statics. You should use statics where possible
for assets as consts can lead to them being included multiple times in the
ROM.
- Fixnums are now implemented with `num_traits` trait definitions.
- Rather than having our own sync with Statics, use the standard portable
atomics crate. These are reexported for convenience.
- `Mgba` no longer implements `Write`. You're unlikely to notice as
`agb::println!` is unchanged.
- Writes of long messages to mgba are split over multiple log messages if they
overflow mgba's buffer. On a panic, only the final message will be Fatal with
the preceding ones (if needed) being Info.
## [0.19.1] - 2024/03/06
### Added
- `.abs()` on `Vector2D` and `Rect`
### Fixed
- `InfiniteScrolledMap` can now scroll more than 1 tile in a single frame without corrupting.
## [0.19.0] - 2024/03/06
### Added
- Added `.priority()`, `.set_priority()` and `.is_visible()` to `RegularMap`, `AffineMap` and `InfiniteScrolledMap`.
- Replaced `.show()` and `.hide()` with `.set_visible()`in `RegularMap`, `AffineMap` and `InfiniteScrolledMap`.
- Added `.into_inner()` to `InfiniteScrolledMap` to get the map back once you are done using it in the `InfiniteScrolledMap`.
- Added `.hflip()`, `.vflip()`, `.priority()`, `.position()` to `ObjectUnmanaged` and `Object`.
- An abstraction over hblank DMA to allow for cool effects like gradients and circular windows. See the dma_effect\* examples.
- Expermental and incomplete support for MIDI files with agb-tracker.
- Fixnum now implements [`num::Num`](https://docs.rs/num/0.4/num/trait.Num.html) from the [`num`](https://crates.io/crates/num) crate.
- `Default` implementations for `RandomNumberGenerator`, `InitOnce` and `RawMutex`.
### Changed
- A few functions which previously accepted a `Vector<u16>` now accept an `impl Into<Vector2D<u16>>` instead.
## [0.18.1] - 2024/02/06
### Added
- You can now use include_aseprite and include_background_gfx to include files from the out directory using the `$OUT_DIR` token.
- Added `.pause()` and `.resume()` methods to `SoundChannels` to let you pause and resume from where you left off.
## [0.18.0] - 2023/10/31
### Added
- There is now a multiboot feature which you can use to easily make multiboot ROMs.
- Can now set palette on a TileSetting struct.
### Changed
- You no longer need the gba.ld or gba_mb.ld files in your repository. You should delete these when upgrading.
### Fixed
- Multiboot builds now work on mgba.
- Fixed inaccuracy in cosine implementation caused by accidentally multiplying correction term by zero.
## [0.17.1] - 2023/10/05
### Fixed
- Fixed the build on docs.rs.
## [0.17.0] - 2023/10/03
### Added
- New tracker for playing XM files (see the `agb-tracker` crate).
- You can now declare where looping sound channels should restart.
- Fixnums now have constructors from_f32 and from_f64. This is mainly useful if using agb-fixnum outside of the Game Boy Advance e.g. in build scripts or macros.
- New option when loading a background to automatically deduplicate tiles.
- Methods on tile_setting to toggle its hflip and vflip status.
### Changed
- Sound channel panning and volume options are now `Num<i16, 8>` rather than `Num<i16, 4>` for improved precision and sound quality.
- Due to dependency changes, agb-gbafix is now released under MPL rather than GPL.
- `include_background_gfx!` now produces tile sets and tile settings directly.
### Fixed
- 256-colour backgrounds are better supported.
- Mono looping samples will now correctly play to the end if it doesn't perfectly align with a buffer boundry and short samples now also loop correctly.
- Fixed a bug in bitmap4 that caused setting pixels to be always incorrect.
## [0.16.0] - 2023/07/18
### Added
- New `include_palette` macro for including every colour in an image as a `u16` slice.
- New object based text renderer.
### Changed
- Changed the default template game.
- `DynamicSprite` has a new API which changes the constructor and adds a `set_pixel` and `clear` methods.
- You no longer need to install arm-none-eabi-binutils. In order to write games using `agb`, you now only need to install rust nightly.
- 10% performance improvement with the software mixer.
### Fixed
- Compile error if you tried to import a larger sprite which uses more than 15 colours between frames.
- `DynamicSprite` has a new API which changes the constructor and adds a `set_pixel` method.
## [0.15.0] - 2023/04/25
### Added
- You can now import aseprite files directly (in addition to the already supported png and bmp files) when importing background tiles.
- New additional unmanaged object API for interacting with a more straightforward manner with the underlying hardware.
### Changed
- Importing background tiles has been improved. You no longer need to use `include_gfx!` with the toml file. Instead, use `include_background_gfx`. See the documentation for usage.
- The hashmap implementation is now it its own crate, `agb-hashmap`. There is no change in API, but you can now use this for interop between non-agb code and agb code.
- Importing background tiles has been improved. You no longer need to use `include_background_gfx!` with the toml file. Instead, use `include_background_gfx`. See the documentation for usage.
- The hashmap implementation is now it its own crate, `agb-hashmap`. There is no change in API, but you can now use this for interop between non-agb code and agb code
- Moved the existing object API to be the OamManaged API. The old names persist with deprecated notices on them.
## [0.14.0] - 2023/04/11
### Added
- Added custom `gbafix` implementation which can take the elf file produced by `cargo build` directly, removing the need for the objcopy step.
### Changed
- Made Vector2D::new a const function.
- The template now uses rust 2021 edition by default.
- All objects which should only be created once now have the correct lifetimes to only allow one to exist.
@ -247,8 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Soundness issues with interrupts resolved which makes them unsafe and require the closure to be static (breaking change).
### Fixed
- Alpha channel is now considered by `include_gfx!()` even when `transparent_colour` is absent.
- Alpha channel is now considered by `include_background_gfx!()` even when `transparent_colour` is absent.
- 256 colour backgrounds are now correctly rendered (breaking change).
- The `#[agb::entry]` macro now reports errors better.
- Added the shstrtab section to the linker to ensure that agb builds with lld.
@ -256,17 +46,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.13.0] - 2023/01/19
### Added
- Added missed implementations of `regular()` and `affine()` to `Tiled1` which made `Tiled1` impossible to use.
### Changed
- Text renderer can now be re-used which is useful for rpg style character/word at a time text boxes.
- Audio now automatically uses interrupts, so you can remove the `setup_interrupt_handler` or `after_vblank` calls to the mixer.
- If a vblank happens outside of `wait_for_vblank`, then next call will immediately return.
### Fixed
- Zero volume now plays no sound.
- Fixed issue where volume was incorrect for volumes which were powers of 2.
@ -275,7 +62,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
This is a minor release to fix an alignment issue with background tiles.
### Fixed
- Corrected alignment of background tiles which was causing issues with rendering tiles in some cases.
## [0.12.1] - 2022/10/12
@ -283,7 +69,6 @@ This is a minor release to fix an alignment issue with background tiles.
This is a minor release to fix the build of the docs on [docs.rs/agb](https://docs.rs/agb).
### Fixed
- Fixed the agb crate's docs.rs build
## [0.12.0] - 2022/10/11
@ -296,7 +81,6 @@ This version of `agb` has some exciting new features we'd like to highlight and
We also had a contribution by @ijc8. We can't thank you all enough!
### Added
- Custom allocator support using the `Allocator` trait for `HashMap`. This means the `HashMap` can be used with `InternalAllocator` to allocate to IWRAM or the `ExternalAllocator` to explicitly allocate to EWRAM.
- Support for using windows on the GBA. Windows are used to selectively enable rendering of certain layers or effects.
- Support for the blend mode of the GBA. Blending allows for alpha blending between layers and fading to black and white.
@ -309,7 +93,6 @@ We also had a contribution by @ijc8. We can't thank you all enough!
- Added support for dynamic sprites generated at runtime, some parts of this may change significantly so breaking changes are expected here.
### Changed
- Many of the places that originally disabled IRQs now use the `sync` module, reducing the chance of missed interrupts.
- HashMap iterators now implement `size_hint` which should result in slightly better generation of code using those iterators.
- Transparency of backgrounds is now set once in the toml file rather than once for every image.
@ -318,7 +101,6 @@ We also had a contribution by @ijc8. We can't thank you all enough!
- `testing` is now a default feature, so you no longer need to add a separate `dev-dependencies` line for `agb` in order to enable unit tests for your project.
### Fixed
- Fixed the fast magnitude function in agb_fixnum. This is also used in fast_normalise. Previously only worked for positive (x, y).
- Fixed formatting of fixed point numbers in the range (-1, 0), which previously appeared positive.
@ -327,17 +109,14 @@ We also had a contribution by @ijc8. We can't thank you all enough!
Version 0.11.1 brings documentation for fixed point numbers. We recommend all users upgrade to this version since it also includes fixes to a few functions in fixnum. See changed section for breaking changes.
### Added
- Support for sprites that are not square.
- Docs for fixed point numbers.
### Changed
- `Rect::contains_point` now considers points on the boundary to be part of the rectangle.
- Signature of `Rect::overlapping_rect` changed to return an Option. Returns None if rectangles don't overlap.
### Fixed
- Fixed point sine calculates the sine correctly.
## [0.10.0] - 2022/07/31
@ -345,7 +124,6 @@ Version 0.11.1 brings documentation for fixed point numbers. We recommend all us
Version 0.10.0 brings about many new features. As with most `agb` upgrades, you will need to update your `gba.ld` and `gba_mb.ld` files which you can find in the [template repo](https://github.com/agbrs/template). We would also recommend copying the `[profile.dev]` and `[profile.release]` sections from `Cargo.toml` if you don't have these values already.
### Added
- [Hyperspace roll](https://lostimmortal.itch.io/hyperspace-roll), a new game built for the GMTK Game Jam 2022 using `agb`. The source code can be found in the `examples` directory.
- Started using GitHub discussions as a forum
- Many functions previously undocumented are now documented
@ -362,7 +140,6 @@ Version 0.10.0 brings about many new features. As with most `agb` upgrades, you
- Random number generator in agb::rng
### Changed
- Audio system optimisations - reduced CPU usage by more than 50%
- Background tiles are now removed from Video RAM during `commit()` if they are no longer used rather than immediately reducing flickering
- Improved the README for both the main agb crate and the template
@ -372,11 +149,9 @@ Version 0.10.0 brings about many new features. As with most `agb` upgrades, you
- A few methods accepting `Num<..>` have been changed to accept `impl Into<Num<..>>` to make them easier to use
### Removed
- The ability to use timer0 and timer1 through the `timer` module. This was done in order to fully support 32kHz audio
### Fixed
- Sprite data is now correctly aligned so fast copies will always work
- A few methods which should really be internal have had `pub` removed
- The crate now compiles (but does not run) doctests in CI which pointed out a large number of non-compiling examples

View file

@ -1,49 +0,0 @@
[workspace]
resolver = "2"
# unfortunately we can't include 'agb' or anything which compiles to non-native code
# in the workspace here, and they need to be tracked separately.
members = [
# "agb"
"agb-debug",
"agb-fixnum",
"agb-gbafix",
"agb-hashmap",
"agb-image-converter",
"agb-macros",
"agb-sound-converter",
"tracker/agb-midi",
"tracker/agb-midi-core",
"tracker/agb-tracker-interop",
# "tracker/agb-tracker",
"tracker/agb-xm",
"tracker/agb-xm-core",
"tracker/desktop-player",
"tools",
"emulator/mgba",
"emulator/mgba-sys",
"emulator/test-runner",
"emulator/screenshot-generator",
"website/backtrace",
]
exclude = [
"agb",
"tracker/agb-tracker",
"examples/amplitude",
"examples/combo",
"examples/hyperspace-roll",
"examples/the-dungeon-puzzlers-lament",
"examples/the-hat-chooses-the-wizard",
"examples/the-purple-night",
"book/games/pong",
"template",
]

View file

@ -16,11 +16,11 @@ without needing to have extensive knowledge of its low-level implementation.
agb provides the following features:
- Simple build process with minimal dependencies
- Built in importing of sprites, backgrounds, music and sound effects
- High performance audio mixer and optional tracker which can play `.xm` files
- Easy to use sprite and tiled background usage
- A global allocator allowing for use of both `core` and `alloc`
* Simple build process with minimal dependencies
* Built in importing of sprites, backgrounds, music and sound effects
* High performance audio mixer
* Easy to use sprite and tiled background usage
* A global allocator allowing for use of both `core` and `alloc`
The documentation for the latest release can be found on
[docs.rs](https://docs.rs/agb/latest/agb/).
@ -32,7 +32,7 @@ The best way to get started with agb is to use the template, either within the
Once you have done this, you will find further instructions within the README in the template.
There is an (in progress) tutorial which you can find on the [project website](https://agbrs.dev/).
There is an (in progress) tutorial which you can find on the [project website](https://agbrs.github.io/agb/).
## Help / Support
@ -41,29 +41,35 @@ is a great place to get help from the creators and contributors.
Feel free to [create a new discussion in the Q&A category](https://github.com/agbrs/agb/discussions/new?category=Q-A) and we'll do our best to help!
## Contributing to agb itself
In order to contribute to agb itself, you will need a few extra tools on top of what you would need
to just write games for the Game Boy Advance using this library:
- Recent rustup, see [the rust website](https://www.rust-lang.org/tools/install)
* Recent rustup, see [the rust website](https://www.rust-lang.org/tools/install)
for instructions for your operating system.
- You can update rustup with `rustup self update`, or using your package manager
if you obtained rustup in this way.
- libelf and cmake
- Debian and derivatives: `sudo apt install libelf-dev cmake`
- Arch Linux and derivatives: `pacman -S libelf cmake`
- mgba-test-runner
- Run `cargo install --path emulator/test-runner` inside this directory
- [The 'just' build tool](https://github.com/casey/just)
- Install with `cargo install just`
- [mdbook](https://rust-lang.github.io/mdBook/index.html)
- Install with `cargo install mdbook`
- [miri](https://github.com/rust-lang/miri)
- Some of the unsafe code is tested using miri, install with `rustup component add miri`
* You can update rustup with `rustup update`, or using your package manager
if you obtained rustup in this way.
* arm eabi binutils
* Debian and derivatives: `sudo apt install binutils-arm-none-eabi`
* Arch Linux and derivatives: `pacman -S arm-none-eabi-binutils`
* Windows can apparently use the [GNU Arm Embedded Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads).
Make sure to select "Add path to environment variable" during the install.
* This process has only been tested on Ubuntu and Arch Linux.
* libelf and cmake
* Debian and derivatives: `sudo apt install libelf-dev cmake`
* Arch Linux and derivatives: `pacman -S libelf cmake`
* mgba-test-runner
* Run `cargo install --path mgba-test-runner` inside this directory
* [The 'just' build tool](https://github.com/casey/just)
* Install with `cargo install just`
* [mdbook](https://rust-lang.github.io/mdBook/index.html)
* Install with `cargo install mdbook`
* [miri](https://github.com/rust-lang/miri)
* Some of the unsafe code is tested using miri, install with `rustup component add miri`
With all of this installed, you should be able to run a full build of agb using by running
```sh
just ci
```
@ -72,21 +78,17 @@ Note that before you create a PR, please file an issue so we can discuss what yo
## Structure of the repo
`agb-debug` - a tool you can use to decode agb stacktraces
`agb-fixnum` - a simple fixed point number storage since the GBA doesn't have a floating point unit, so required
for performant decimals.
`agb-gbafix` - a clean-room reimplementation of the gbafix utility that accepts elf files rather than binaries
`agb-hashmap` - an no_std hashmap implementation tuned for use on the game boy advance
`agb-image-converter` - a crate which converts images in normal formats to a format supported by the game boy advance
`agb-macros` - miscellaneous proc-macros which have to be in a different crate
`agb-sound-converter` - a crate which converts wav files into a format supported by the game boy advance
`agb-hashmap` - an no_std hashmap implementation tuned for use on the game boy advance
`agb` - the main library code
`agb/examples` - basic examples often targeting 1 feature, you can run these using `just run-example <example-name>`
@ -95,18 +97,12 @@ for performant decimals.
`book/games` - games made as part of the tutorial
`emulator` - Rust bindings for the [mgba](https://mgba.io) emulator along with the test runner you can use to run unit tests
`examples` - bigger examples of a complete game, made during game jams
`mgba-test-runner` - a wrapper around the [mgba](https://mgba.io) emulator which allows us to write unit tests in rust
`template` - the source for the [template repository](https://github.com/agbrs/template)
`tools` - misc. tools used in the development of `agb` itself
`tracker` - crates that make up the `agb-tracker` library which allows playing of tracker files
`website` - the source of [the website](https://agbrs.dev)
## Stability
While agb is in the pre-1.0 phase, we follow a semi-semantic versioning scheme to ensure compatibility between minor releases.
@ -118,14 +114,17 @@ Once agb reaches version 1.0, we will transition to stronger semantic versioning
agb would not be possible without the help from the following (non-exhaustive) list of projects:
- The amazing work of the [rust-console](https://github.com/rust-console) for making this all possible in the first place
- The [asefile](https://crates.io/crates/asefile) crate for loading aseprite files
- [agbabi](https://github.com/felixjones/agbabi) for providing high performance alternatives to common methods
- [mgba](https://mgba.io) for all the useful debugging / developer tools built in to the emulator
* The amazing work of the [rust-console](https://github.com/rust-console) for making this all possible in the first place
* The [asefile](https://crates.io/crates/asefile) crate for loading aseprite files
* [agbabi](https://github.com/felixjones/agbabi) for providing high performance alternatives to common methods
* [mgba](https://mgba.io) for all the useful debugging / developer tools built in to the emulator
## Licence
agb and all its subcrates are released under MPL version 2.0. See full licence text in the `LICENSE` file.
agb and all its subcrates (except agb-gbafix) are released under MPL version 2.0. See full licence
text in the `LICENSE` file.
agb-gbafix is released under GPL version 3.0. See the full licence in the agb-gbafix/LICENSE file
agb contains a subset of the code from [agbabi](https://github.com/felixjones/agbabi) which is released under a zlib style licence,
details for which you can find under `agb/src/agbabi`.
@ -135,4 +134,4 @@ changes you wish.
The agb logo is released under [Creative Commons Attribution-ShareAlike 4.0](http://creativecommons.org/licenses/by-sa/4.0/)
The music used for the examples is by [Josh Woodward](https://www.joshwoodward.com) and released under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/)
The music used for the examples is by [Josh Woodward](https://www.joshwoodward.com) and released under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/)

View file

@ -1,19 +0,0 @@
[package]
name = "agb-debug"
version = "0.21.1"
edition = "2021"
authors = ["Gwilym Inzani <email@gwilym.dev>"]
license = "MPL-2.0"
description = "CLI utility to convert agb stack trace dumps into human readable stack traces"
repository = "https://github.com/agbrs/agb"
[dependencies]
addr2line = { version = "0.24", default-features = false, features = ["rustc-demangle"] }
gimli = { version = "0.31", default-features = false, features = ["endian-reader", "std"] }
object = { version = "0.36", default-features = false, features = ["read"] }
thiserror = "2"
clap = { version = "4", features = ["derive"] }
colored = "2"
rmp-serde = "1"
lz4_flex = "0.11"

View file

@ -1,166 +0,0 @@
use std::{slice::ChunksExact, sync::OnceLock};
use thiserror::Error;
const ALPHABET: &[u8] = b"0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
#[derive(Debug, Error)]
pub enum GwilymDecodeError {
#[error("Does not contain version")]
NoVersion,
#[error("Only version 1 is supported")]
WrongVersion,
#[error("Input must be a multiple of 3 but have {0}")]
LengthWrong(usize),
}
pub fn gwilym_decode(input: &str) -> Result<GwilymDecodeIter<'_>, GwilymDecodeError> {
GwilymDecodeIter::new(input)
}
pub struct GwilymDecodeIter<'a> {
chunks: ChunksExact<'a, u8>,
}
impl<'a> GwilymDecodeIter<'a> {
fn new(input: &'a str) -> Result<Self, GwilymDecodeError> {
let input = input
.strip_prefix("https://agbrs.dev/crash#")
.unwrap_or(input);
let Some((input, version)) = input.rsplit_once('v') else {
return Err(GwilymDecodeError::NoVersion);
};
if version != "1" {
return Err(GwilymDecodeError::WrongVersion);
}
if input.len() % 3 != 0 {
return Err(GwilymDecodeError::LengthWrong(input.len()));
}
Ok(Self {
chunks: input.as_bytes().chunks_exact(3),
})
}
}
impl Iterator for GwilymDecodeIter<'_> {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
let chunk = self.chunks.next()?;
let value = decode_chunk(chunk);
if value & (1 << 16) != 0 {
let upper_bits = value << 16;
let lower_bits = self.next().unwrap_or(0) & 0xffff;
return Some(upper_bits | lower_bits);
}
Some(value | 0x0800_0000)
}
}
fn decode_chunk(chunk: &[u8]) -> u32 {
let a = get_value_for_char(chunk[0]);
let b = get_value_for_char(chunk[1]);
let c = get_value_for_char(chunk[2]);
(a << (16 - 5)) | (b << (16 - 10)) | c
}
fn get_value_for_char(input: u8) -> u32 {
static REVERSE_ALHPABET: OnceLock<[u8; 128]> = OnceLock::new();
REVERSE_ALHPABET.get_or_init(|| {
let mut result = [0; 128];
for (i, &c) in ALPHABET.iter().enumerate() {
result[c as usize] = i as u8;
}
result
})[input as usize] as u32
}
#[cfg(test)]
mod test {
use super::*;
use std::fmt::Write;
#[test]
fn should_correctly_decode_16s() -> Result<(), GwilymDecodeError> {
assert_eq!(
&gwilym_decode("2QI65Q69306Kv1")?.collect::<Vec<_>>(),
&[0x0800_16d3, 0x0800_315b, 0x0800_3243, 0x0800_0195]
);
Ok(())
}
fn encode_16(input: u16) -> [u8; 3] {
let input = input as usize;
[
ALPHABET[input >> (16 - 5)],
ALPHABET[(input >> (16 - 10)) & 0b11111],
ALPHABET[input & 0b111111],
]
}
fn encode_32(input: u32) -> [u8; 6] {
let input = input as usize;
let output_lower_16 = encode_16(input as u16);
let input_upper_16 = input >> 16;
[
ALPHABET[(input_upper_16 >> (16 - 5)) | (1 << 5)],
ALPHABET[(input_upper_16 >> (16 - 10)) & 0b11111],
ALPHABET[input_upper_16 & 0b111111],
output_lower_16[0],
output_lower_16[1],
output_lower_16[2],
]
}
#[test]
fn should_correctly_decode_16s_and_32s() -> Result<(), Box<dyn std::error::Error>> {
let trace: &[u32] = &[
0x0300_2990,
0x0800_3289,
0x0500_2993,
0x3829_2910,
0xffff_ffff,
0x0000_0000,
];
let mut result = String::new();
for &ip in trace {
if ip & 0xFFFF_0000 == 0x0800_0000 {
let encoded = encode_16(ip as u16);
let encoded_s = std::str::from_utf8(&encoded)?;
write!(&mut result, "{encoded_s}")?
} else {
let encoded = encode_32(ip);
let encoded_s = std::str::from_utf8(&encoded)?;
write!(&mut result, "{encoded_s}")?
}
}
write!(&mut result, "v1")?;
assert_eq!(&gwilym_decode(&result)?.collect::<Vec<_>>(), trace);
Ok(())
}
#[test]
fn should_strip_the_agbrsdev_prefix() -> Result<(), Box<dyn std::error::Error>> {
assert_eq!(
&gwilym_decode("https://agbrs.dev/crash#2QI65Q69306Kv1")?.collect::<Vec<_>>(),
&[0x0800_16d3, 0x0800_315b, 0x0800_3243, 0x0800_0195]
);
Ok(())
}
}

View file

@ -1,93 +0,0 @@
mod gwilym_encoding;
mod load_dwarf;
use addr2line::gimli;
pub use gwilym_encoding::{gwilym_decode, GwilymDecodeError};
pub use load_dwarf::{load_dwarf, GimliDwarf, LoadDwarfError};
use thiserror::Error;
pub use addr2line;
pub struct AddressInfo {
pub location: Location,
pub is_interesting: bool,
pub is_inline: bool,
pub function: String,
}
#[derive(Debug, Error)]
pub enum AddressInfoError {
#[error(transparent)]
Gimli(#[from] gimli::Error),
}
pub struct Location {
pub filename: String,
pub line: u32,
pub col: u32,
}
pub type Addr2LineContext = addr2line::Context<gimli::EndianRcSlice<gimli::RunTimeEndian>>;
impl Default for Location {
fn default() -> Self {
Self {
filename: "??".to_string(),
line: 0,
col: 0,
}
}
}
pub fn address_info(
ctx: &Addr2LineContext,
address: u64,
) -> Result<Vec<AddressInfo>, AddressInfoError> {
let mut frames = ctx.find_frames(address).skip_all_loads()?;
let mut is_first = true;
let mut infos = Vec::new();
while let Some(frame) = frames.next()? {
let function_name = if let Some(ref func) = frame.function {
func.demangle()?.into_owned()
} else {
"unknown function".to_string()
};
let location = frame
.location
.as_ref()
.map(|location| Location {
filename: location.file.unwrap_or("??").to_owned(),
line: location.line.unwrap_or(0),
col: location.column.unwrap_or(0),
})
.unwrap_or_default();
let is_interesting = is_interesting_function(&function_name, &location.filename);
infos.push(AddressInfo {
location,
is_interesting,
is_inline: !is_first,
function: function_name,
});
is_first = false;
}
Ok(infos)
}
fn is_interesting_function(function_name: &str, path: &str) -> bool {
if function_name == "rust_begin_unwind" {
return false; // this is the unwind exception call
}
if path.ends_with("panicking.rs") {
return false; // probably part of rust's internal panic mechanisms
}
true
}

View file

@ -1,95 +0,0 @@
use std::{borrow::Cow, collections::HashMap, io::Cursor, rc::Rc};
use addr2line::gimli;
use object::Object;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum LoadDwarfError {
#[error("Gba file is empty")]
GbaFileEmpty,
#[error("Failed to load debug information from ROM file, it might not have been included?")]
NoDebugInformation,
#[error("Failed to load debug information: {0}")]
DeserializationError(#[from] rmp_serde::decode::Error),
#[error(transparent)]
GimliError(#[from] gimli::Error),
}
pub type GimliDwarf = gimli::Dwarf<gimli::EndianRcSlice<gimli::RunTimeEndian>>;
pub fn load_dwarf(file_content: &[u8]) -> Result<GimliDwarf, LoadDwarfError> {
if let Ok(object) = object::File::parse(file_content) {
return Ok(load_from_object(&object)?);
}
// the file might have been padded, so ensure we skip any padding before continuing
let last_non_zero_byte = file_content
.iter()
.rposition(|&b| b != 0)
.ok_or(LoadDwarfError::GbaFileEmpty)?;
let file_content = &file_content[..last_non_zero_byte + 1];
let last_8_bytes = &file_content[file_content.len() - 8..];
let len = u32::from_le_bytes(
last_8_bytes[0..4]
.try_into()
.or(Err(LoadDwarfError::NoDebugInformation))?,
) as usize;
let version = &last_8_bytes[4..];
if version != b"agb1" {
return Err(LoadDwarfError::NoDebugInformation);
}
let compressed_debug_data = &file_content[file_content.len() - len - 8..file_content.len() - 8];
let decompressing_reader =
lz4_flex::frame::FrameDecoder::new(Cursor::new(compressed_debug_data));
let debug_info: HashMap<String, Vec<u8>> = rmp_serde::decode::from_read(decompressing_reader)?;
let dwarf = gimli::Dwarf::load(|id| {
let data = debug_info
.get(id.name())
.map(|data| Cow::Borrowed(data.as_slice()))
.unwrap_or(Cow::Borrowed(&[]));
Result::<_, gimli::Error>::Ok(gimli::EndianRcSlice::new(
Rc::from(&*data),
gimli::RunTimeEndian::Little,
))
})?;
Ok(dwarf)
}
fn load_from_object<'file>(
object: &object::File<'file, &'file [u8]>,
) -> Result<GimliDwarf, gimli::Error> {
let endian = if object.is_little_endian() {
gimli::RunTimeEndian::Little
} else {
gimli::RunTimeEndian::Big
};
fn load_section<'data, Endian>(
id: gimli::SectionId,
file: &impl object::Object<'data>,
endian: Endian,
) -> Result<gimli::EndianRcSlice<Endian>, gimli::Error>
where
Endian: gimli::Endianity,
{
use object::ObjectSection;
let data = file
.section_by_name(id.name())
.and_then(|section| section.uncompressed_data().ok())
.unwrap_or(Cow::Borrowed(&[]));
Ok(gimli::EndianRcSlice::new(Rc::from(&*data), endian))
}
let dwarf = gimli::Dwarf::load(|id| load_section(id, object, endian))?;
Ok(dwarf)
}

View file

@ -1,123 +0,0 @@
use std::{
borrow::Cow,
error::Error,
fs::{self, File},
io::Read,
path::PathBuf,
time::SystemTime,
};
use agb_debug::{address_info, AddressInfo, Location};
use clap::Parser;
use colored::Colorize;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// The filename of the elf file
elf_path: PathBuf,
/// The output of agb's dump
dump: String,
}
fn main() -> Result<(), Box<dyn Error>> {
let cli = Args::parse();
let modification_time = fs::metadata(&cli.elf_path)?
.modified()
.unwrap_or(SystemTime::UNIX_EPOCH);
let file = fs::read(&cli.elf_path)?;
let dwarf = agb_debug::load_dwarf(&file)?;
let ctx = addr2line::Context::from_dwarf(dwarf)?;
for (i, address) in agb_debug::gwilym_decode(&cli.dump)?.enumerate() {
let infos = address_info(&ctx, address.into())?;
for info in infos {
print_address_info(&info, i, modification_time)?;
}
}
Ok(())
}
fn print_address_info(
info: &AddressInfo,
index: usize,
elf_modification_time: SystemTime,
) -> Result<(), Box<dyn Error>> {
let function_name_to_print = &info.function;
if !info.is_inline {
print!("{index}:\t{function_name_to_print}");
} else {
print!("\t(inlined into) {function_name_to_print}");
}
println!(
" {}:{}",
prettify_path(&info.location.filename).green(),
info.location.line.to_string().green()
);
if info.location.line != 0 && info.is_interesting {
print_line_of_code(&info.location, elf_modification_time)?;
}
Ok(())
}
fn print_line_of_code(
location: &Location,
elf_modification_time: SystemTime,
) -> Result<(), Box<dyn Error>> {
let filename = &location.filename;
let Ok(mut file) = File::open(filename) else {
return Ok(());
};
let modification_time = fs::metadata(filename)?
.modified()
.unwrap_or(SystemTime::UNIX_EPOCH);
if modification_time > elf_modification_time {
eprintln!("Warning: File {filename} modified more recently than the binary, line info may be incorrect");
}
let mut content = String::new();
file.read_to_string(&mut content)?;
let Some(line_of_code) = content.split('\n').nth(location.line as usize - 1) else {
eprintln!("File {filename} does not have line {}", location.line);
return Ok(());
};
let trimmed = line_of_code.trim_start();
let trimmed_len = line_of_code.len() - trimmed.len();
println!("\t\t{}", trimmed);
if location.col != 0 {
println!(
"\t\t{}{}",
" ".repeat(location.col as usize - trimmed_len - 1),
"^".bright_blue()
);
}
Ok(())
}
fn prettify_path(path: &str) -> Cow<'_, str> {
if let Some(src_index) = path.rfind("/src/") {
let crate_name_start = path[0..src_index].rfind('/');
let crate_name = crate_name_start
.map(|crate_name_start| &path[crate_name_start + 1..src_index])
.unwrap_or("<crate>");
Cow::Owned(format!("<{crate_name}>/{}", &path[src_index + 5..]))
} else {
Cow::Borrowed(path)
}
}

View file

@ -1,12 +1,20 @@
[package]
name = "agb_fixnum"
version = "0.21.1"
version = "0.15.0"
edition = "2021"
license = "MPL-2.0"
description = "Library for abstracting over fixed precision numbers. Designed for use with the agb library for the Game Boy Advance"
repository = "https://github.com/agbrs/agb"
keywords = ["no-std", "no-std::no-alloc"]
[dependencies]
agb_macros = { version = "0.21.1", path = "../agb-macros" }
num-traits = { version = "0.2", default-features = false }
agb_macros = { version = "0.15.0", path = "../agb-macros" }
[profile.dev]
opt-level = 3
debug = true
[profile.release]
opt-level = 3
lto = "fat"
debug = true
codegen-units = 1

View file

@ -11,7 +11,6 @@ use core::{
Sub, SubAssign,
},
};
use num_traits::Signed;
#[doc(hidden)]
/// Used internally by the [num!] macro which should be used instead.
@ -33,41 +32,73 @@ macro_rules! num {
/// A trait for everything required to use as the internal representation of the
/// fixed point number.
pub trait Number: Copy + PartialOrd + Ord + num_traits::Num {}
pub trait Number:
Sized
+ Copy
+ PartialOrd
+ Ord
+ PartialEq
+ Eq
+ Add<Output = Self>
+ Sub<Output = Self>
+ Rem<Output = Self>
+ Div<Output = Self>
+ Mul<Output = Self>
{
}
impl<I: FixedWidthUnsignedInteger, const N: usize> Number for Num<I, N> {}
impl<I: FixedWidthUnsignedInteger> Number for I {}
/// A trait for integers that don't implement unary negation
pub trait FixedWidthUnsignedInteger:
Copy
Sized
+ Copy
+ PartialOrd
+ Ord
+ PartialEq
+ Eq
+ Shl<usize, Output = Self>
+ Shr<usize, Output = Self>
+ Add<Output = Self>
+ Sub<Output = Self>
+ Not<Output = Self>
+ BitAnd<Output = Self>
+ Rem<Output = Self>
+ Div<Output = Self>
+ Mul<Output = Self>
+ From<u8>
+ Debug
+ Display
+ num_traits::Num
+ Not<Output = Self>
{
/// Returns the representation of zero
fn zero() -> Self;
/// Returns the representation of one
fn one() -> Self;
/// Returns the representation of ten
fn ten() -> Self;
/// Converts an i32 to it's own representation, panics on failure
fn from_as_i32(v: i32) -> Self;
/// Returns (a * b) >> N
fn upcast_multiply(a: Self, b: Self, n: usize) -> Self;
}
/// Trait for an integer that includes negation
pub trait FixedWidthSignedInteger: FixedWidthUnsignedInteger + num_traits::sign::Signed {}
impl<I: FixedWidthUnsignedInteger + Signed> FixedWidthSignedInteger for I {}
pub trait FixedWidthSignedInteger: FixedWidthUnsignedInteger + Neg<Output = Self> {
#[must_use]
/// Returns the absolute value of the number
fn fixed_abs(self) -> Self;
}
macro_rules! fixed_width_unsigned_integer_impl {
($T: ty, $Upcast: ident) => {
($T: ty) => {
impl FixedWidthUnsignedInteger for $T {
#[inline(always)]
fn zero() -> Self {
0
}
#[inline(always)]
fn one() -> Self {
1
}
#[inline(always)]
fn ten() -> Self {
10
@ -76,88 +107,36 @@ macro_rules! fixed_width_unsigned_integer_impl {
fn from_as_i32(v: i32) -> Self {
v as $T
}
upcast_multiply_impl!($T, $Upcast);
}
};
}
macro_rules! upcast_multiply_impl {
($T: ty, optimised_64_bit) => {
#[inline(always)]
fn upcast_multiply(a: Self, b: Self, n: usize) -> Self {
use num_traits::One;
let mask = (Self::one() << n).wrapping_sub(1);
let a_floor = a >> n;
let a_frac = a & mask;
let b_floor = b >> n;
let b_frac = b & mask;
(a_floor.wrapping_mul(b_floor) << n)
.wrapping_add(
a_floor
.wrapping_mul(b_frac)
.wrapping_add(b_floor.wrapping_mul(a_frac)),
)
.wrapping_add(((a_frac as u32).wrapping_mul(b_frac as u32) >> n) as $T)
}
};
($T: ty, $Upcast: ty) => {
#[inline(always)]
fn upcast_multiply(a: Self, b: Self, n: usize) -> Self {
(((a as $Upcast) * (b as $Upcast)) >> n) as $T
macro_rules! fixed_width_signed_integer_impl {
($T: ty) => {
impl FixedWidthSignedInteger for $T {
#[inline(always)]
fn fixed_abs(self) -> Self {
self.abs()
}
}
};
}
fixed_width_unsigned_integer_impl!(u8, u32);
fixed_width_unsigned_integer_impl!(i16, i32);
fixed_width_unsigned_integer_impl!(u16, u32);
fixed_width_unsigned_integer_impl!(u8);
fixed_width_unsigned_integer_impl!(i16);
fixed_width_unsigned_integer_impl!(u16);
fixed_width_unsigned_integer_impl!(i32);
fixed_width_unsigned_integer_impl!(u32);
fixed_width_unsigned_integer_impl!(usize);
fixed_width_unsigned_integer_impl!(i32, optimised_64_bit);
fixed_width_unsigned_integer_impl!(u32, optimised_64_bit);
fixed_width_signed_integer_impl!(i16);
fixed_width_signed_integer_impl!(i32);
/// A fixed point number represented using `I` with `N` bits of fractional precision
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Num<I: FixedWidthUnsignedInteger, const N: usize>(I);
impl<I: FixedWidthUnsignedInteger, const N: usize> num_traits::Zero for Num<I, N> {
fn zero() -> Self {
Self::new(I::zero())
}
fn is_zero(&self) -> bool {
self.to_raw() == I::zero()
}
}
impl<I: FixedWidthUnsignedInteger, const N: usize> num_traits::One for Num<I, N> {
fn one() -> Self {
Self::new(I::one())
}
}
impl<I: FixedWidthUnsignedInteger + num_traits::Num, const N: usize> num_traits::Num for Num<I, N> {
type FromStrRadixErr = <f64 as num_traits::Num>::FromStrRadixErr;
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
// for some reason, if I don't have this it's an error, and if I do it is unused
#[allow(unused_imports)]
use num_traits::float::FloatCore;
let v: f64 = f64::from_str_radix(str, radix)?;
let integer = v.trunc();
let fractional = v.fract() * (1u64 << 30) as f64;
Ok(Self::new_from_parts((integer as i32, fractional as i32)))
}
}
/// An often convenient representation for the Game Boy Advance using word sized
/// internal representation for maximum efficiency
pub type FixedNum<const N: usize> = Num<i32, N>;
@ -225,7 +204,9 @@ where
{
type Output = Self;
fn mul(self, rhs: Num<I, N>) -> Self::Output {
Num(I::upcast_multiply(self.0, rhs.0, N))
Num(((self.floor() * rhs.floor()) << N)
+ (self.floor() * rhs.frac() + rhs.floor() * self.frac())
+ ((self.frac() * rhs.frac()) >> N))
}
}
@ -364,20 +345,6 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> Num<I, N> {
self.0
}
/// Lossily transforms an f32 into a fixed point representation. This is not const
/// because you cannot currently do floating point operations in const contexts, so
/// you should use the `num!` macro from agb-macros if you want a const from_f32/f64
pub fn from_f32(input: f32) -> Self {
Self::from_raw(I::from_as_i32((input * (1 << N) as f32) as i32))
}
/// Lossily transforms an f64 into a fixed point representation. This is not const
/// because you cannot currently do floating point operations in const contexts, so
/// you should use the `num!` macro from agb-macros if you want a const from_f32/f64
pub fn from_f64(input: f64) -> Self {
Self::from_raw(I::from_as_i32((input * (1 << N) as f64) as i32))
}
/// Truncates the fixed point number returning the integral part
/// ```rust
/// # use agb_fixnum::*;
@ -449,7 +416,6 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> Num<I, N> {
}
#[doc(hidden)]
#[inline(always)]
/// Called by the [num!] macro in order to create a fixed point number
pub fn new_from_parts(num: (i32, i32)) -> Self {
Self(I::from_as_i32(((num.0) << N) + (num.1 >> (30 - N))))
@ -501,7 +467,7 @@ impl<I: FixedWidthSignedInteger, const N: usize> Num<I, N> {
/// assert_eq!(n.abs(), num!(5.5));
/// ```
pub fn abs(self) -> Self {
Num(self.0.abs())
Num(self.0.fixed_abs())
}
/// Calculates the cosine of a fixed point number with the domain of [0, 1].
@ -521,10 +487,17 @@ impl<I: FixedWidthSignedInteger, const N: usize> Num<I, N> {
/// ```
#[must_use]
pub fn cos(self) -> Self {
let one: Self = I::one().into();
let mut x = self;
x -= num!(0.25) + (x + num!(0.25)).floor();
x *= (x.abs() - num!(0.5)) * num!(16.);
x += x * (x.abs() - num!(1.)) * num!(0.225);
let four: I = 4.into();
let two: I = 2.into();
let sixteen: I = 16.into();
let nine: I = 9.into();
let forty: I = 40.into();
x -= one / four + (x + one / four).floor();
x *= (x.abs() - one / two) * sixteen;
x += x * (x.abs() - one) * (nine / forty);
x
}
@ -550,28 +523,6 @@ impl<I: FixedWidthSignedInteger, const N: usize> Num<I, N> {
}
}
impl<I: FixedWidthSignedInteger, const N: usize> num_traits::sign::Signed for Num<I, N> {
fn abs(&self) -> Self {
Self::abs(*self)
}
fn abs_sub(&self, other: &Self) -> Self {
Self(self.0.abs_sub(&other.0))
}
fn signum(&self) -> Self {
Self(self.0.signum())
}
fn is_positive(&self) -> bool {
self.0.is_positive()
}
fn is_negative(&self) -> bool {
self.0.is_negative()
}
}
impl<I: FixedWidthUnsignedInteger, const N: usize> Display for Num<I, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut integral = self.0 >> N;
@ -584,55 +535,26 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> Display for Num<I, N> {
//
// But if you think of a negative number, you'd like it to be `negative number - non negative fraction`
// So we have to add 1 to the integral bit, and take 1 - fractional bit
let sign = if fractional != I::zero() && integral < I::zero() {
if fractional != I::zero() && integral < I::zero() {
integral = integral + I::one();
if integral == I::zero() {
// If the number is in the range (-1, 0), then we just bumped `integral` from -1 to 0,
// so we need to compensate for the missing negative sign.
write!(f, "-")?;
}
fractional = (I::one() << N) - fractional;
-1
} else {
1
};
}
if let Some(precision) = f.precision() {
let precision_multiplier = I::from_as_i32(10_i32.pow(precision as u32));
write!(f, "{integral}")?;
let fractional_as_integer = fractional * precision_multiplier * I::ten();
let mut fractional_as_integer = fractional_as_integer >> N;
if fractional != I::zero() {
write!(f, ".")?;
}
if fractional_as_integer % I::ten() >= I::from_as_i32(5) {
fractional_as_integer = fractional_as_integer + I::ten();
}
let mut fraction_to_write = fractional_as_integer / I::ten();
if fraction_to_write >= precision_multiplier {
integral = integral + I::from_as_i32(sign);
fraction_to_write = fraction_to_write - precision_multiplier;
}
if sign == -1 && integral == I::zero() && fraction_to_write != I::zero() {
write!(f, "-")?;
}
write!(f, "{integral}")?;
if precision != 0 {
write!(f, ".{:#0width$}", fraction_to_write, width = precision)?;
}
} else {
if sign == -1 && integral == I::zero() {
write!(f, "-")?;
}
write!(f, "{integral}")?;
if fractional != I::zero() {
write!(f, ".")?;
}
while fractional & mask != I::zero() {
fractional = fractional * I::ten();
write!(f, "{}", (fractional & !mask) >> N)?;
fractional = fractional & mask;
}
while fractional & mask != I::zero() {
fractional = fractional * I::ten();
write!(f, "{}", (fractional & !mask) >> N)?;
fractional = fractional & mask;
}
Ok(())
@ -648,7 +570,7 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> Debug for Num<I, N> {
}
/// A vector of two points: (x, y) represented by integers or fixed point numbers
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Hash)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct Vector2D<T: Number> {
/// The x coordinate
pub x: T,
@ -736,27 +658,6 @@ impl<T: Number> SubAssign<Self> for Vector2D<T> {
}
}
impl<T: Number + Signed> Vector2D<T> {
/// Calculates the absolute value of the x and y components.
pub fn abs(self) -> Self {
Self {
x: self.x.abs(),
y: self.y.abs(),
}
}
#[must_use]
/// Calculates the manhattan distance, x.abs() + y.abs().
/// ```
/// # use agb_fixnum::*;
/// let v1: Vector2D<Num<i32, 8>> = (num!(3.), num!(4.)).into();
/// assert_eq!(v1.manhattan_distance(), 7.into());
/// ```
pub fn manhattan_distance(self) -> T {
self.x.abs() + self.y.abs()
}
}
impl<I: FixedWidthUnsignedInteger, const N: usize> Vector2D<Num<I, N>> {
#[must_use]
/// Truncates the x and y coordinate, see [Num::trunc]
@ -801,6 +702,28 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> Vector2D<Num<I, N>> {
}
impl<const N: usize> Vector2D<Num<i32, N>> {
#[must_use]
/// Calculates the magnitude squared, ie (x*x + y*y)
/// ```
/// # use agb_fixnum::*;
/// let v1: Vector2D<Num<i32, 8>> = (num!(3.), num!(4.)).into();
/// assert_eq!(v1.magnitude_squared(), 25.into());
/// ```
pub fn magnitude_squared(self) -> Num<i32, N> {
self.x * self.x + self.y * self.y
}
#[must_use]
/// Calculates the manhattan distance, x.abs() + y.abs().
/// ```
/// # use agb_fixnum::*;
/// let v1: Vector2D<Num<i32, 8>> = (num!(3.), num!(4.)).into();
/// assert_eq!(v1.manhattan_distance(), 7.into());
/// ```
pub fn manhattan_distance(self) -> Num<i32, N> {
self.x.abs() + self.y.abs()
}
#[must_use]
/// Calculates the magnitude by square root
/// ```
@ -896,7 +819,7 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> From<Vector2D<I>> for Vector2
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
/// A rectangle with a position in 2d space and a 2d size
pub struct Rect<T: Number> {
/// The position of the rectangle
@ -1049,20 +972,6 @@ impl<T: FixedWidthUnsignedInteger> Rect<T> {
}
}
impl<T: Number + Signed> Rect<T> {
/// Makes a rectangle that represents the equivalent location in space but with a positive size
pub fn abs(self) -> Self {
Self {
position: (
self.position.x + self.size.x.min(T::zero()),
self.position.y + self.size.y.min(T::zero()),
)
.into(),
size: self.size.abs(),
}
}
}
impl<T: Number> Vector2D<T> {
/// Created a vector from the given coordinates
/// ```
@ -1102,48 +1011,6 @@ impl<T: Number> Vector2D<T> {
}
}
#[doc(alias = "scalar_product")]
/// Calculates the dot product / scalar product of two vectors
/// ```
/// use agb_fixnum::Vector2D;
///
/// let v1 = Vector2D::new(3, 5);
/// let v2 = Vector2D::new(7, 11);
///
/// let dot = v1.dot(v2);
/// assert_eq!(dot, 76);
/// ```
/// The dot product for vectors *A* and *B* is defined as
/// > *A*<sub>*x*</sub> × *B*<sub>*x*</sub> + *A*<sub>*y*</sub> × *B*<sub>*y*</sub>.
pub fn dot(self, b: Self) -> T {
self.x * b.x + self.y * b.y
}
#[doc(alias = "vector_product")]
/// Calculates the *z* component of the cross product / vector product of two
/// vectors
/// ```
/// use agb_fixnum::Vector2D;
///
/// let v1 = Vector2D::new(3, 5);
/// let v2 = Vector2D::new(7, 11);
///
/// let dot = v1.cross(v2);
/// assert_eq!(dot, -2);
/// ```
/// The *z* component cross product for vectors *A* and *B* is defined as
/// > *A*<sub>*x*</sub> × *B*<sub>*y*</sub> - *A*<sub>*y*</sub> × *B*<sub>*x*</sub>.
///
///
/// Normally the cross product / vector product is itself a vector. This is
/// in the 3D case where the cross product of two vectors is perpendicular
/// to both vectors. The only vector perpendicular to two 2D vectors is
/// purely in the *z* direction, hence why this method only returns that
/// component. The *x* and *y* components are always zero.
pub fn cross(self, b: Self) -> T {
self.x * b.y - self.y * b.x
}
#[must_use]
/// Swaps the x and y coordinate
/// ```
@ -1157,17 +1024,6 @@ impl<T: Number> Vector2D<T> {
y: self.x,
}
}
#[must_use]
/// Calculates the magnitude squared, ie (x*x + y*y)
/// ```
/// # use agb_fixnum::*;
/// let v1: Vector2D<Num<i32, 8>> = (num!(3.), num!(4.)).into();
/// assert_eq!(v1.magnitude_squared(), 25.into());
/// ```
pub fn magnitude_squared(self) -> T {
self.x * self.x + self.y * self.y
}
}
impl<T: Number + Neg<Output = T>> Neg for Vector2D<T> {
@ -1185,7 +1041,6 @@ mod tests {
use super::*;
use alloc::format;
use num_traits::Num as _;
#[test]
fn formats_whole_numbers_correctly() {
@ -1210,51 +1065,6 @@ mod tests {
assert_eq!(format!("{d}"), "-0.25");
}
mod precision {
use super::*;
macro_rules! num_ {
($n: literal) => {{
let a: Num<i32, 20> = num!($n);
a
}};
}
macro_rules! test_precision {
($TestName: ident, $Number: literal, $Expected: literal) => {
test_precision! { $TestName, $Number, $Expected, 2 }
};
($TestName: ident, $Number: literal, $Expected: literal, $Digits: literal) => {
#[test]
fn $TestName() {
assert_eq!(
format!("{:.width$}", num_!($Number), width = $Digits),
$Expected
);
}
};
}
test_precision!(positive_down, 1.2345678, "1.23");
test_precision!(positive_round_up, 1.237, "1.24");
test_precision!(negative_round_down, -1.237, "-1.24");
test_precision!(trailing_zero, 1.5, "1.50");
test_precision!(leading_zero, 1.05, "1.05");
test_precision!(positive_round_to_next_integer, 3.999, "4.00");
test_precision!(negative_round_to_next_integer, -3.999, "-4.00");
test_precision!(negative_round_to_1, -0.999, "-1.00");
test_precision!(positive_round_to_1, 0.999, "1.00");
test_precision!(positive_round_to_zero, 0.001, "0.00");
test_precision!(negative_round_to_zero, -0.001, "0.00");
test_precision!(zero_precision_negative, -0.001, "0", 0);
test_precision!(zero_precision_positive, 0.001, "0", 0);
}
#[test]
fn sqrt() {
for x in 1..1024 {
@ -1304,26 +1114,6 @@ mod tests {
test_base::<11>();
}
#[test]
fn check_cos_accuracy() {
let n: Num<i32, 8> = Num::new(1) / 32;
assert_eq!(
n.cos(),
Num::from_f64((2. * core::f64::consts::PI / 32.).cos())
);
}
#[test]
fn check_16_bit_precision_i32() {
let a: Num<i32, 16> = num!(1.923);
let b = num!(2.723);
assert_eq!(
a * b,
Num::from_raw(((a.to_raw() as i64 * b.to_raw() as i64) >> 16) as i32)
)
}
#[test]
fn test_numbers() {
// test addition
@ -1502,48 +1292,4 @@ mod tests {
]
);
}
#[test]
fn test_str_radix() {
use alloc::string::ToString;
macro_rules! str_radix_test {
($val:tt) => {
assert_eq!(
Num::<i32, 8>::from_str_radix(stringify!($val), 10).unwrap(),
num!($val)
);
};
(-$val:tt) => {
assert_eq!(
Num::<i32, 8>::from_str_radix(&("-".to_string() + stringify!($val)), 10)
.unwrap(),
num!(-$val)
);
};
}
str_radix_test!(0.1);
str_radix_test!(0.100000);
str_radix_test!(0000.1000);
str_radix_test!(000000.100000);
str_radix_test!(000000.1);
str_radix_test!(138.229);
str_radix_test!(-138.229);
str_radix_test!(-1321.229231);
}
#[cfg(not(debug_assertions))]
#[test]
fn test_all_multiplies() {
use super::*;
for i in 0..u32::MAX {
let fix_num: Num<_, 7> = Num::from_raw(i);
let upcasted = ((i as u64 * i as u64) >> 7) as u32;
assert_eq!((fix_num * fix_num).to_raw(), upcasted);
}
}
}

1
agb-gbafix/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
!Cargo.lock

290
agb-gbafix/Cargo.lock generated Normal file
View file

@ -0,0 +1,290 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "agb-gbafix"
version = "0.15.0"
dependencies = [
"anyhow",
"bytemuck",
"clap",
"elf",
"gbafix",
]
[[package]]
name = "anstream"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is-terminal",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
[[package]]
name = "anstyle-parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytemuck"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "clap"
version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749"
dependencies = [
"anstream",
"anstyle",
"bitflags",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_lex"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "elf"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2b183d6ce6ca4cf30e3db37abf5b52568b5f9015c97d9fbdd7026aa5dcdd758"
[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "gbafix"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7e47af9d5377b8b0def53d9916f6c28521f2e5b97c5fec137797417c26ab4b7"
dependencies = [
"bytemuck",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "io-lifetimes"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "libc"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "linux-raw-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c"
[[package]]
name = "rustix"
version = "0.37.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

View file

@ -1,15 +1,25 @@
[package]
name = "agb-gbafix"
version = "0.21.1"
version = "0.15.0"
edition = "2021"
authors = ["Gwilym Inzani <email@gwilym.dev>"]
license = "MPL-2.0"
license = "GPL-3.0"
description = "CLI utility to convert ELF file to valid GBA ROM"
repository = "https://github.com/agbrs/agb"
[dependencies]
elf = "0.7"
gbafix = "1"
bytemuck = "1"
anyhow = "1"
clap = "4"
rmp-serde = "1"
lz4_flex = "0.11"
[profile.dev]
opt-level = 3
debug = true
[profile.release]
opt-level = 3
lto = "fat"
debug = true
codegen-units = 1

674
agb-gbafix/LICENSE Normal file
View file

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View file

@ -1,179 +0,0 @@
use anyhow::{anyhow, bail, ensure, Result};
use std::{collections::HashMap, io::Write};
const GBA_HEADER_SIZE: usize = 192;
const NINTENDO_LOGO: &[u8] = &[
0x24, 0xFF, 0xAE, 0x51, 0x69, 0x9A, 0xA2, 0x21, 0x3D, 0x84, 0x82, 0x0A, 0x84, 0xE4, 0x09, 0xAD,
0x11, 0x24, 0x8B, 0x98, 0xC0, 0x81, 0x7F, 0x21, 0xA3, 0x52, 0xBE, 0x19, 0x93, 0x09, 0xCE, 0x20,
0x10, 0x46, 0x4A, 0x4A, 0xF8, 0x27, 0x31, 0xEC, 0x58, 0xC7, 0xE8, 0x33, 0x82, 0xE3, 0xCE, 0xBF,
0x85, 0xF4, 0xDF, 0x94, 0xCE, 0x4B, 0x09, 0xC1, 0x94, 0x56, 0x8A, 0xC0, 0x13, 0x72, 0xA7, 0xFC,
0x9F, 0x84, 0x4D, 0x73, 0xA3, 0xCA, 0x9A, 0x61, 0x58, 0x97, 0xA3, 0x27, 0xFC, 0x03, 0x98, 0x76,
0x23, 0x1D, 0xC7, 0x61, 0x03, 0x04, 0xAE, 0x56, 0xBF, 0x38, 0x84, 0x00, 0x40, 0xA7, 0x0E, 0xFD,
0xFF, 0x52, 0xFE, 0x03, 0x6F, 0x95, 0x30, 0xF1, 0x97, 0xFB, 0xC0, 0x85, 0x60, 0xD6, 0x80, 0x25,
0xA9, 0x63, 0xBE, 0x03, 0x01, 0x4E, 0x38, 0xE2, 0xF9, 0xA2, 0x34, 0xFF, 0xBB, 0x3E, 0x03, 0x44,
0x78, 0x00, 0x90, 0xCB, 0x88, 0x11, 0x3A, 0x94, 0x65, 0xC0, 0x7C, 0x63, 0x87, 0xF0, 0x3C, 0xAF,
0xD6, 0x25, 0xE4, 0x8B, 0x38, 0x0A, 0xAC, 0x72, 0x21, 0xD4, 0xF8, 0x07,
];
#[derive(Debug, Default)]
pub struct GbaHeader {
pub start_code: [u8; 4],
pub game_title: [u8; 12],
pub game_code: [u8; 4],
pub maker_code: [u8; 2],
pub software_version: u8,
}
impl GbaHeader {
fn produce_header(&self) -> Vec<u8> {
let mut header_result = vec![];
header_result.extend_from_slice(&self.start_code);
header_result.extend_from_slice(NINTENDO_LOGO);
header_result.extend_from_slice(&self.game_title);
header_result.extend_from_slice(&self.game_code);
header_result.extend_from_slice(&self.maker_code);
header_result.push(0x96); // must be 96
header_result.push(0); // main unit code (0 for current GBA models)
header_result.push(0); // device type, usually 0
header_result.extend_from_slice(&[0; 7]); // reserved area, should be zero filled
header_result.push(self.software_version);
let checksum = Self::calculate_checksum(&header_result);
header_result.push(checksum); // checksum
header_result.extend_from_slice(&[0; 2]); // reserved area, should be zero filled
assert_eq!(header_result.len(), GBA_HEADER_SIZE);
header_result
}
fn calculate_checksum(header: &[u8]) -> u8 {
let mut chk = 0u8;
for value in header.iter().take(0xBC).skip(0xA0) {
chk = chk.wrapping_sub(*value);
}
chk = chk.wrapping_sub(0x19);
chk
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum PaddingBehaviour {
Pad,
#[default]
DoNotPad,
}
pub fn write_gba_file<W: Write>(
input: &[u8],
mut header: GbaHeader,
padding_behaviour: PaddingBehaviour,
include_debug: bool,
output: &mut W,
) -> Result<()> {
let elf_file = elf::ElfBytes::<elf::endian::AnyEndian>::minimal_parse(input)?;
let section_headers = elf_file
.section_headers()
.ok_or_else(|| anyhow!("Failed to parse as elf file"))?;
let mut bytes_written = 0;
for section_header in section_headers {
const SHT_NOBITS: u32 = 8;
const SHT_NULL: u32 = 0;
const SHF_ALLOC: u64 = 2;
if (section_header.sh_type == SHT_NOBITS || section_header.sh_type == SHT_NULL)
|| section_header.sh_flags & SHF_ALLOC == 0
{
continue;
}
let align = bytes_written % section_header.sh_addralign;
if align != 0 {
for _ in 0..(section_header.sh_addralign - align) {
output.write_all(&[0])?;
bytes_written += 1;
}
}
let (mut data, compression) = elf_file.section_data(&section_header)?;
if let Some(compression) = compression {
bail!("Cannot decompress elf content, but got compression header {compression:?}");
}
if bytes_written == 0 {
ensure!(
data.len() > GBA_HEADER_SIZE,
"first section must be at least as big as the gba header"
);
header.start_code = data[0..4].try_into().unwrap();
let header_bytes = header.produce_header();
output.write_all(&header_bytes)?;
data = &data[GBA_HEADER_SIZE..];
bytes_written += GBA_HEADER_SIZE as u64;
}
output.write_all(data)?;
bytes_written += data.len() as u64;
}
if include_debug {
bytes_written += write_debug(&elf_file, output)?;
}
if !bytes_written.is_power_of_two() && padding_behaviour == PaddingBehaviour::Pad {
let required_padding = bytes_written.next_power_of_two() - bytes_written;
for _ in 0..required_padding {
output.write_all(&[0])?;
}
}
Ok(())
}
fn write_debug<W: Write>(
elf_file: &elf::ElfBytes<'_, elf::endian::AnyEndian>,
output: &mut W,
) -> Result<u64> {
let (Some(section_headers), Some(string_table)) = elf_file.section_headers_with_strtab()?
else {
bail!("Could not find either the section headers or the string table");
};
let mut debug_sections = HashMap::new();
for section_header in section_headers {
let section_name = string_table.get(section_header.sh_name as usize)?;
if !section_name.starts_with(".debug") {
continue;
}
let (data, compression) = elf_file.section_data(&section_header)?;
if compression.is_some() {
bail!("Do not support compression in elf files, section {section_name} was compressed");
}
debug_sections.insert(section_name.to_owned(), data);
}
let mut debug_data = vec![];
{
let mut compressed_writer = lz4_flex::frame::FrameEncoder::new(&mut debug_data);
rmp_serde::encode::write(&mut compressed_writer, &debug_sections)?;
compressed_writer.flush()?;
}
output.write_all(&debug_data)?;
output.write_all(&(debug_data.len() as u32).to_le_bytes())?;
output.write_all(b"agb1")?;
Ok(debug_data.len() as u64 + 4)
}

View file

@ -1,4 +1,4 @@
use anyhow::{anyhow, bail, Result};
use anyhow::{anyhow, bail, ensure, Result};
use clap::{arg, value_parser};
use std::{
@ -7,8 +7,6 @@ use std::{
path::PathBuf,
};
use agb_gbafix::{write_gba_file, GbaHeader, PaddingBehaviour};
fn main() -> Result<()> {
let matches = clap::Command::new("agb-gbafix")
.about("Convert elf files directly to a valid GBA ROM")
@ -18,8 +16,7 @@ fn main() -> Result<()> {
.arg(arg!(-c --gamecode <GAME_CODE> "Sets the game code, 4 bytes"))
.arg(arg!(-m --makercode <MAKER_CODE> "Set the maker code, 2 bytes"))
.arg(arg!(-r --gameversion <VERSION> "Set the version of the game, 0-255").value_parser(value_parser!(u8)))
.arg(arg!(-p --padding "Pad the ROM to the next power of 2 in size"))
.arg(arg!(-g --debug "Include debug information directly in the ROM"))
.arg(arg!(-p --padding "Ignored for compatibility with gbafix"))
.get_matches();
let input = matches.get_one::<PathBuf>("INPUT").unwrap();
@ -28,7 +25,7 @@ fn main() -> Result<()> {
None => input.with_extension("gba"),
};
let mut header = GbaHeader::default();
let mut header = gbafix::GBAHeader::default();
{
let title = if let Some(title) = matches.get_one::<String>("title") {
@ -42,7 +39,7 @@ fn main() -> Result<()> {
};
for (i, &c) in title.as_bytes().iter().enumerate().take(12) {
header.game_title[i] = c;
header.title[i] = c;
}
}
@ -62,7 +59,7 @@ fn main() -> Result<()> {
}
if let Some(game_version) = matches.get_one::<u8>("gameversion") {
header.software_version = *game_version;
header.version = *game_version;
}
if let Some(game_code) = matches.get_one::<String>("gamecode") {
@ -71,27 +68,81 @@ fn main() -> Result<()> {
}
}
let include_debug = matches.get_flag("debug");
let pad = matches.get_flag("padding");
let pad = if pad {
PaddingBehaviour::Pad
} else {
PaddingBehaviour::DoNotPad
};
let mut output = BufWriter::new(fs::File::create(output)?);
let file_data = fs::read(input)?;
write_gba_file(
file_data.as_slice(),
header,
pad,
include_debug,
&mut output,
)?;
write_gba_file(file_data.as_slice(), header, &mut output)?;
output.flush()?;
Ok(())
}
fn write_gba_file<W: Write>(
input: &[u8],
mut header: gbafix::GBAHeader,
output: &mut W,
) -> Result<()> {
let elf_file = elf::ElfBytes::<elf::endian::AnyEndian>::minimal_parse(input)?;
let section_headers = elf_file
.section_headers()
.ok_or_else(|| anyhow!("Failed to parse as elf file"))?;
let mut bytes_written = 0;
for section_header in section_headers.iter() {
const SHT_NOBITS: u32 = 8;
const SHT_NULL: u32 = 0;
const SHF_ALLOC: u64 = 2;
if (section_header.sh_type == SHT_NOBITS || section_header.sh_type == SHT_NULL)
|| section_header.sh_flags & SHF_ALLOC == 0
{
continue;
}
let align = bytes_written % section_header.sh_addralign;
if align != 0 {
for _ in 0..(section_header.sh_addralign - align) {
output.write_all(&[0])?;
bytes_written += 1;
}
}
let (mut data, compression) = elf_file.section_data(&section_header)?;
if let Some(compression) = compression {
bail!("Cannot decompress elf content, but got compression header {compression:?}");
}
if bytes_written == 0 {
const GBA_HEADER_SIZE: usize = 192;
ensure!(
data.len() > GBA_HEADER_SIZE,
"first section must be at least as big as the gba header"
);
header.start_code = data[0..4].try_into().unwrap();
header.update_checksum();
let header_bytes = bytemuck::bytes_of(&header);
output.write_all(header_bytes)?;
data = &data[GBA_HEADER_SIZE..];
bytes_written += GBA_HEADER_SIZE as u64;
}
output.write_all(data)?;
bytes_written += data.len() as u64;
}
if !bytes_written.is_power_of_two() {
let required_padding = bytes_written.next_power_of_two() - bytes_written;
for _ in 0..required_padding {
output.write_all(&[0])?;
}
}
Ok(())
}

View file

@ -1,23 +1,24 @@
[package]
name = "agb_hashmap"
version = "0.21.1"
version = "0.15.0"
edition = "2021"
license = "MPL-2.0"
description = "A simple no_std hashmap implementation intended for use in the `agb` library"
repository = "https://github.com/agbrs/agb"
exclude = ["/benches"]
keywords = ["no-std", "data-structures"]
[features]
allocator_api = []
serde = ["dep:serde"]
[dependencies]
rustc-hash = { version = "1", default-features = false }
serde = { version = "1", default-features = false, optional = true }
[dev-dependencies]
rand = { version = "0.8", default-features = false, features = ["small_rng"] }
lazy_static = "1.4"
quickcheck = "1"
serde_json = { version = "1", default-features = false, features = ["alloc"] }
[profile.dev]
opt-level = 3
debug = true
[profile.release]
opt-level = 3
lto = "fat"
debug = true
codegen-units = 1

View file

@ -1,441 +0,0 @@
use crate::{Allocator, ClonableAllocator, Global};
use core::{borrow::Borrow, fmt::Debug, hash::Hash};
use super::HashMap;
/// A `HashSet` is implemented as a [`HashMap`] where the value is `()`.
///
/// As with the [`HashMap`] type, a `HashSet` requires that the elements implement the
/// [`Eq`] and [`Hash`] traits, although this is frequently achieved by using
/// `#[derive(PartialEq, Eq, Hash)]`. If you implement these yourself, it is important
/// that the following property holds:
///
/// It is a logic error for the key to be modified in such a way that the key's hash, as
/// determined by the [`Hash`] trait, or its equality as determined by the [`Eq`] trait,
/// changes while it is in the map. The behaviour for such a logic error is not specified,
/// but will not result in undefined behaviour. This could include panics, incorrect results,
/// aborts, memory leaks and non-termination.
///
/// The API surface provided is incredibly similar to the
/// [`std::collections::HashSet`](https://doc.rust-lang.org/std/collections/struct.HashMap.html)
/// implementation with fewer guarantees, and better optimised for the `GameBoy Advance`.
///
/// [`Eq`]: https://doc.rust-lang.org/core/cmp/trait.Eq.html
/// [`Hash`]: https://doc.rust-lang.org/core/hash/trait.Hash.html
///
/// # Example
///
/// ```
/// use agb_hashmap::HashSet;
///
/// // Type inference lets you omit the type signature (which would be HashSet<String> in this example)
/// let mut games = HashSet::new();
///
/// // Add some games
/// games.insert("Pokemon Emerald".to_string());
/// games.insert("Golden Sun".to_string());
/// games.insert("Super Dodge Ball Advance".to_string());
///
/// // Check for a specific game
/// if !games.contains("Legend of Zelda: The Minish Cap") {
/// println!("We've got {} games, but The Minish Cap ain't one", games.len());
/// }
///
/// // Remove a game
/// games.remove("Golden Sun");
///
/// // Iterate over everything
/// for game in &games {
/// println!("{game}");
/// }
/// ```
#[derive(Clone)]
pub struct HashSet<K, ALLOCATOR: Allocator = Global> {
map: HashMap<K, (), ALLOCATOR>,
}
impl<K> HashSet<K> {
/// Creates a `HashSet`
#[must_use]
pub fn new() -> Self {
Self::new_in(Global)
}
/// Creates an empty `HashSet` with specified internal size. The size must be a power of 2
#[must_use]
pub fn with_size(size: usize) -> Self {
Self::with_size_in(size, Global)
}
/// Creates an empty `HashSet` which can hold at least `capacity` elements before resizing. The actual
/// internal size may be larger as it must be a power of 2
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self::with_capacity_in(capacity, Global)
}
}
impl<K, ALLOCATOR: ClonableAllocator> HashSet<K, ALLOCATOR> {
/// Creates an empty `HashSet` with specified internal size using the specified allocator.
/// The size must be a power of 2
#[must_use]
pub fn with_size_in(size: usize, alloc: ALLOCATOR) -> Self {
Self {
map: HashMap::with_size_in(size, alloc),
}
}
/// Creates a `HashSet` with a specified allocator
#[must_use]
pub fn new_in(alloc: ALLOCATOR) -> Self {
Self::with_size_in(16, alloc)
}
/// Creates an empty `HashSet` which can hold at least `capacity` elements before resizing. The actual
/// internal size may be larger as it must be a power of 2
///
/// # Panics
///
/// Panics if capacity is larger than 2^32 * .85
#[must_use]
pub fn with_capacity_in(capacity: usize, alloc: ALLOCATOR) -> Self {
Self {
map: HashMap::with_capacity_in(capacity, alloc),
}
}
/// Returns a reference to the underlying allocator
pub fn allocator(&self) -> &ALLOCATOR {
self.map.allocator()
}
/// Returns the number of elements in the set
#[must_use]
pub fn len(&self) -> usize {
self.map.len()
}
/// Returns whether or not the set is empty
#[must_use]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
/// Returns the number of elements the set can hold without resizing
#[must_use]
pub fn capacity(&self) -> usize {
self.map.capacity()
}
/// Removes all elements from the set
pub fn clear(&mut self) {
self.map.clear();
}
/// An iterator visiting all the values in the set
pub fn iter(&self) -> impl Iterator<Item = &'_ K> {
self.map.keys()
}
/// Retains only the elements specified by the predicate `f`
pub fn retain<F>(&mut self, mut f: F)
where
F: FnMut(&K) -> bool,
{
self.map.retain(|k, _| f(k));
}
}
impl<K> Default for HashSet<K> {
fn default() -> Self {
Self::new()
}
}
impl<K, ALLOCATOR: ClonableAllocator> HashSet<K, ALLOCATOR>
where
K: Eq + Hash,
{
/// Inserts a value into the set. This does not replace the value if it already existed.
///
/// Returns whether the value was newly inserted, that is:
///
/// * If the set did not previously contain this value, `true` is returned
/// * If the set already contained this value, `false` is returned.
///
/// # Examples
///
/// ```
/// use agb_hashmap::HashSet;
///
/// let mut set = HashSet::new();
/// assert_eq!(set.insert(2), true);
/// assert_eq!(set.insert(2), false);
/// assert_eq!(set.len(), 1);
/// ```
pub fn insert(&mut self, value: K) -> bool {
self.map.insert(value, ()).is_none()
}
/// Removes a value from the set. Returns whether the value was present in the set.
///
/// # Examples
/// ```
/// use agb_hashmap::HashSet;
///
/// let mut set = HashSet::new();
/// set.insert(2);
///
/// assert_eq!(set.remove(&2), true);
/// assert_eq!(set.remove(&2), false);
/// assert!(set.is_empty());
/// ```
pub fn remove<Q>(&mut self, value: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.map.remove(value).is_some()
}
/// Returns `true` if the set contains the value `value`.
///
/// # Examples
///
/// ```
/// use agb_hashmap::HashSet;
///
/// let set = HashSet::from([1, 2, 3]);
/// assert_eq!(set.contains(&1), true);
/// assert_eq!(set.contains(&4), false);
/// ```
pub fn contains<Q>(&self, value: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.map.contains_key(value)
}
/// Visits the values reperesting the difference i.e. the values that are in `self` but not in `other`.
///
/// # Examples
///
/// ```
/// use agb_hashmap::HashSet;
///
/// let a = HashSet::from([1, 2, 3]);
/// let b = HashSet::from([4, 2, 3, 4]);
///
/// // Can be seen as `a - b`
/// let diff: HashSet<_> = a.difference(&b).collect();
/// assert_eq!(diff, HashSet::from([&1]));
///
/// // Difference is not symmetric. `b - a` means something different
/// let diff: HashSet<_> = b.difference(&a).collect();
/// assert_eq!(diff, HashSet::from([&4]));
/// ``````
pub fn difference<'a>(
&'a self,
other: &'a HashSet<K, ALLOCATOR>,
) -> impl Iterator<Item = &'a K> {
self.iter().filter(|k| !other.contains(k))
}
/// Visits the values which are in `self` or `other` but not both.
///
/// # Examples
///
/// ```
/// use agb_hashmap::HashSet;
///
/// let a = HashSet::from([1, 2, 3]);
/// let b = HashSet::from([4, 2, 3, 4]);
///
/// let diff1: HashSet<_> = a.symmetric_difference(&b).collect();
/// let diff2: HashSet<_> = b.symmetric_difference(&a).collect();
///
/// assert_eq!(diff1, diff2);
/// assert_eq!(diff1, HashSet::from([&1, &4]));
/// ```
pub fn symmetric_difference<'a>(
&'a self,
other: &'a HashSet<K, ALLOCATOR>,
) -> impl Iterator<Item = &'a K> {
self.iter()
.filter(|k| !other.contains(k))
.chain(other.iter().filter(|k| !self.contains(k)))
}
/// Visits the values in the intersection of `self` and `other`.
///
/// When an equal element is present in `self` and `other`, then the resulting intersection may
/// yield references to one or the other. This can be relevant if `K` contains fields which are not
/// covered by the `Eq` implementation.
///
/// # Examples
///
/// ```
/// use agb_hashmap::HashSet;
///
/// let a = HashSet::from([1, 2, 3]);
/// let b = HashSet::from([4, 2, 3, 4]);
///
/// let intersection: HashSet<_> = a.intersection(&b).collect();
/// assert_eq!(intersection, HashSet::from([&2, &3]));
/// ```
pub fn intersection<'a>(
&'a self,
other: &'a HashSet<K, ALLOCATOR>,
) -> impl Iterator<Item = &'a K> {
let (smaller, larger) = if self.len() < other.len() {
(self, other)
} else {
(other, self)
};
smaller.iter().filter(|k| larger.contains(k))
}
/// Visits the values in self and other without duplicates.
///
/// When an equal element is present in `self` and `other`, then the resulting union may
/// yield references to one or the other. This can be relevant if `K` contains fields which are not
/// covered by the `Eq` implementation.
///
/// # Examples
///
/// ```
/// use agb_hashmap::HashSet;
///
/// let a = HashSet::from([1, 2, 3]);
/// let b = HashSet::from([4, 2, 3, 4]);
///
/// let union: Vec<_> = a.union(&b).collect();
/// assert_eq!(union.len(), 4);
/// assert_eq!(HashSet::from_iter(union), HashSet::from([&1, &2, &3, &4]));
/// ```
pub fn union<'a>(&'a self, other: &'a HashSet<K, ALLOCATOR>) -> impl Iterator<Item = &'a K> {
let (smaller, larger) = if self.len() < other.len() {
(self, other)
} else {
(other, self)
};
larger.iter().chain(smaller.difference(self))
}
}
impl<K, ALLOCATOR: ClonableAllocator> IntoIterator for HashSet<K, ALLOCATOR> {
type Item = K;
type IntoIter = IterOwned<K, ALLOCATOR>;
fn into_iter(self) -> Self::IntoIter {
IterOwned {
map_iter: self.map.into_iter(),
}
}
}
/// An iterator over the entries of a [`HashSet`].
///
/// This struct is created using the `into_iter()` method on [`HashSet`] as part of its implementation
/// of the `IntoIterator` trait.
pub struct IterOwned<K, ALLOCATOR: ClonableAllocator> {
map_iter: super::IterOwned<K, (), ALLOCATOR>,
}
impl<K, ALLOCATOR: ClonableAllocator> Iterator for IterOwned<K, ALLOCATOR> {
type Item = K;
fn next(&mut self) -> Option<Self::Item> {
self.map_iter.next().map(|(k, _)| k)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.map_iter.size_hint()
}
}
impl<K, ALLOCATOR: ClonableAllocator> ExactSizeIterator for IterOwned<K, ALLOCATOR> {}
impl<'a, K, ALLOCATOR: ClonableAllocator> IntoIterator for &'a HashSet<K, ALLOCATOR> {
type Item = &'a K;
type IntoIter = Iter<'a, K, ALLOCATOR>;
fn into_iter(self) -> Self::IntoIter {
Iter {
map_iter: (&self.map).into_iter(),
}
}
}
pub struct Iter<'a, K, ALLOCATOR: ClonableAllocator> {
map_iter: super::Iter<'a, K, (), ALLOCATOR>,
}
impl<'a, K, ALLOCATOR: ClonableAllocator> Iterator for Iter<'a, K, ALLOCATOR> {
type Item = &'a K;
fn next(&mut self) -> Option<Self::Item> {
self.map_iter.next().map(|(k, _)| k)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.map_iter.size_hint()
}
}
impl<K, ALLOCATOR: ClonableAllocator> ExactSizeIterator for Iter<'_, K, ALLOCATOR> {}
impl<K> FromIterator<K> for HashSet<K>
where
K: Eq + Hash,
{
fn from_iter<T: IntoIterator<Item = K>>(iter: T) -> Self {
let mut set = HashSet::new();
set.extend(iter);
set
}
}
impl<K> Extend<K> for HashSet<K>
where
K: Eq + Hash,
{
fn extend<T: IntoIterator<Item = K>>(&mut self, iter: T) {
for k in iter {
self.insert(k);
}
}
}
impl<K, ALLOCATOR: ClonableAllocator> PartialEq for HashSet<K, ALLOCATOR>
where
K: Eq + Hash,
{
fn eq(&self, other: &Self) -> bool {
self.map == other.map
}
}
impl<K, ALLOCATOR: ClonableAllocator> Eq for HashSet<K, ALLOCATOR> where K: Eq + Hash {}
impl<K, ALLOCATOR: ClonableAllocator> Debug for HashSet<K, ALLOCATOR>
where
K: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_set().entries(self.iter()).finish()
}
}
impl<K, const N: usize> From<[K; N]> for HashSet<K>
where
K: Eq + Hash,
{
fn from(value: [K; N]) -> Self {
HashSet::from_iter(value)
}
}

View file

@ -1,7 +1,7 @@
//! A lot of the documentation for this module was copied straight out of the rust
//! standard library. The implementation however is not.
#![no_std]
#![cfg_attr(feature = "allocator_api", feature(allocator_api))]
#![feature(allocator_api)]
#![deny(clippy::all)]
#![deny(clippy::must_use_candidate)]
#![deny(missing_docs)]
@ -26,46 +26,25 @@
extern crate alloc;
pub(crate) use allocate::{Allocator, Global};
#[cfg(not(feature = "allocator_api"))]
mod allocate {
pub trait Allocator {}
#[derive(Copy, Clone)]
pub struct Global;
impl Allocator for Global {}
}
#[cfg(feature = "allocator_api")]
mod allocate {
pub(crate) use alloc::alloc::Global;
pub(crate) use core::alloc::Allocator;
}
#[cfg(feature = "serde")]
mod serde;
use alloc::{alloc::Global, vec::Vec};
use core::{
alloc::Allocator,
borrow::Borrow,
fmt::Debug,
hash::{BuildHasher, BuildHasherDefault, Hash},
hash::{BuildHasher, BuildHasherDefault, Hash, Hasher},
iter::FromIterator,
num::Wrapping,
ops::Index,
};
use rustc_hash::FxHasher;
mod hash_set;
mod node;
mod node_storage;
use node::Node;
use node_storage::NodeStorage;
pub use hash_set::HashSet;
// # Robin Hood Hash Tables
//
// The problem with regular hash tables where failing to find a slot for a specific
@ -196,6 +175,12 @@ impl<K, V> HashMap<K, V> {
pub fn with_capacity(capacity: usize) -> Self {
Self::with_capacity_in(capacity, Global)
}
#[doc(hidden)]
#[must_use]
pub fn distance_histogram(&self) -> (Vec<usize>, usize) {
self.nodes.distance_histogram()
}
}
impl<K, V, ALLOCATOR: ClonableAllocator> HashMap<K, V, ALLOCATOR> {
@ -353,12 +338,23 @@ where
}
}
unsafe fn insert_new_and_get(&mut self, key: K, value: V, hash: HashType) -> &'_ mut V {
if self.nodes.capacity() <= self.len() {
self.resize(self.nodes.backing_vec_size() * 2);
}
fn insert_and_get(&mut self, key: K, value: V) -> &'_ mut V {
let hash = self.hash(&key);
let location = self.nodes.insert_new(key, value, hash);
let location = if let Some(location) = self.nodes.location(&key, hash) {
// SAFETY: location is valid due to the above
unsafe {
self.nodes
.replace_at_location_unchecked(location, key, value);
}
location
} else {
if self.nodes.capacity() <= self.len() {
self.resize(self.nodes.backing_vec_size() * 2);
}
self.nodes.insert_new(key, value, hash)
};
// SAFETY: location is always valid
unsafe {
@ -485,7 +481,9 @@ where
K: Borrow<Q>,
Q: Hash + ?Sized,
{
let result = self.hasher.hash_one(key);
let mut hasher = self.hasher.build_hasher();
key.hash(&mut hasher);
let result = hasher.finish();
// we want to allow truncation here since we're reducing 64 bits to 32
#[allow(clippy::cast_possible_truncation)]
@ -531,8 +529,6 @@ impl<'a, K, V, ALLOCATOR: ClonableAllocator> Iterator for Iter<'a, K, V, ALLOCAT
}
}
impl<K, V, ALLOCATOR: ClonableAllocator> ExactSizeIterator for Iter<'_, K, V, ALLOCATOR> {}
impl<'a, K, V, ALLOCATOR: ClonableAllocator> IntoIterator for &'a HashMap<K, V, ALLOCATOR> {
type Item = (&'a K, &'a V);
type IntoIter = Iter<'a, K, V, ALLOCATOR>;
@ -583,8 +579,6 @@ impl<K, V, ALLOCATOR: ClonableAllocator> Iterator for IterOwned<K, V, ALLOCATOR>
}
}
impl<K, V, ALLOCATOR: ClonableAllocator> ExactSizeIterator for IterOwned<K, V, ALLOCATOR> {}
/// An iterator over entries of a [`HashMap`]
///
/// This struct is created using the `into_iter()` method on [`HashMap`] as part of its implementation
@ -603,10 +597,9 @@ impl<K, V, ALLOCATOR: ClonableAllocator> IntoIterator for HashMap<K, V, ALLOCATO
}
mod entries {
use crate::allocate::Allocator;
use core::hash::Hash;
use core::{alloc::Allocator, hash::Hash};
use super::{ClonableAllocator, HashMap, HashType};
use super::{ClonableAllocator, HashMap};
/// A view into an occupied entry in a `HashMap`. This is part of the [`crate::Entry`] enum.
pub struct OccupiedEntry<'a, K: 'a, V: 'a, ALLOCATOR: Allocator> {
@ -702,16 +695,11 @@ mod entries {
pub struct VacantEntry<'a, K: 'a, V: 'a, ALLOCATOR: Allocator> {
key: K,
map: &'a mut HashMap<K, V, ALLOCATOR>,
hash: HashType,
}
impl<'a, K: 'a, V: 'a, ALLOCATOR: ClonableAllocator> VacantEntry<'a, K, V, ALLOCATOR> {
pub(crate) unsafe fn new(
key: K,
hash: HashType,
map: &'a mut HashMap<K, V, ALLOCATOR>,
) -> Self {
Self { key, map, hash }
pub(crate) fn new(key: K, map: &'a mut HashMap<K, V, ALLOCATOR>) -> Self {
Self { key, map }
}
/// Gets a reference to the key that would be used when inserting a value through `VacantEntry`
@ -729,8 +717,7 @@ mod entries {
where
K: Hash + Eq,
{
// SAFETY: by construction, this doesn't already exist in the hashmap and we were given the hash and key
unsafe { self.map.insert_new_and_get(self.key, value, self.hash) }
self.map.insert_and_get(self.key, value)
}
}
}
@ -845,10 +832,7 @@ where
unsafe { OccupiedEntry::new(key, self, location) },
)
} else {
Entry::Vacant(
// SAFETY: item doesn't exist yet and the hash is correct here
unsafe { VacantEntry::new(key, hash, self) },
)
Entry::Vacant(VacantEntry::new(key, self))
}
}
}
@ -967,7 +951,7 @@ impl core::ops::Add<i32> for HashType {
#[cfg(test)]
mod test {
use core::{cell::RefCell, hash::Hasher};
use core::cell::RefCell;
use alloc::vec::Vec;
@ -1463,19 +1447,4 @@ mod test {
assert_eq!(format!("{empty:?}"), "{}");
}
}
#[cfg(not(miri))]
quickcheck::quickcheck! {
fn test_against_btree_map(entries: Vec<(u8, u32)>) -> bool {
let std_hashmap = alloc::collections::BTreeMap::from_iter(entries.clone());
let agb_hashmap = HashMap::from_iter(entries);
if std_hashmap.len() != agb_hashmap.len() {
return false;
}
std_hashmap.iter().all(|(key, value)| agb_hashmap.get(key) == Some(value)) &&
agb_hashmap.iter().all(|(key, value)| std_hashmap.get(key) == Some(value))
}
}
}

View file

@ -1,14 +1,12 @@
use core::{borrow::Borrow, mem};
use core::{alloc::Allocator, borrow::Borrow, mem};
use alloc::{alloc::Global, vec::Vec};
use crate::allocate::{Allocator, Global};
use crate::{node::Node, number_before_resize, ClonableAllocator, HashType};
mod vec;
use vec::MyVec;
#[derive(Clone)]
pub(crate) struct NodeStorage<K, V, ALLOCATOR: Allocator = Global> {
nodes: MyVec<Node<K, V>, ALLOCATOR>,
nodes: Vec<Node<K, V>, ALLOCATOR>,
max_distance_to_initial_bucket: i32,
number_of_items: usize,
@ -19,7 +17,7 @@ impl<K, V, ALLOCATOR: ClonableAllocator> NodeStorage<K, V, ALLOCATOR> {
pub(crate) fn with_size_in(capacity: usize, alloc: ALLOCATOR) -> Self {
assert!(capacity.is_power_of_two(), "Capacity must be a power of 2");
let mut nodes = MyVec::with_capacity_in(capacity, alloc);
let mut nodes = Vec::with_capacity_in(capacity, alloc);
for _ in 0..capacity {
nodes.push(Node::new());
}
@ -204,6 +202,22 @@ impl<K, V, ALLOCATOR: ClonableAllocator> NodeStorage<K, V, ALLOCATOR> {
self.nodes.get_unchecked_mut(at)
}
pub(crate) fn distance_histogram(&self) -> (Vec<usize>, usize) {
let mut ret = Vec::new();
for node in self.nodes.iter() {
let distance = node.distance();
if distance >= 0 {
let distance = distance as usize;
ret.resize(ret.len().max(distance + 1), 0);
ret[distance] += 1;
}
}
(ret, self.max_distance_to_initial_bucket as usize)
}
pub(crate) fn clear(&mut self) {
self.max_distance_to_initial_bucket = 0;
self.number_of_items = 0;

View file

@ -1,66 +0,0 @@
use core::ops::{Deref, DerefMut};
use alloc::vec::Vec;
use crate::{Allocator, Global};
pub(crate) use inner::MyVec;
#[cfg(not(feature = "allocator_api"))]
mod inner {
use super::*;
#[derive(Clone)]
pub(crate) struct MyVec<T, A: Allocator = Global>(Vec<T>, A);
impl<T, A: Allocator> MyVec<T, A> {
pub(crate) fn with_capacity_in(capacity: usize, allocator: A) -> Self {
Self(Vec::with_capacity(capacity), allocator)
}
pub(crate) fn allocator(&self) -> &A {
&self.1
}
}
impl<T, A: Allocator> Deref for MyVec<T, A> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T, A: Allocator> DerefMut for MyVec<T, A> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
}
#[cfg(feature = "allocator_api")]
mod inner {
use super::*;
#[derive(Clone)]
pub(crate) struct MyVec<T, A: Allocator = Global>(Vec<T, A>);
impl<T, A: Allocator> MyVec<T, A> {
pub(crate) fn with_capacity_in(capacity: usize, allocator: A) -> Self {
Self(Vec::with_capacity_in(capacity, allocator))
}
}
impl<T, A: Allocator> Deref for MyVec<T, A> {
type Target = Vec<T, A>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T, A: Allocator> DerefMut for MyVec<T, A> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
}

View file

@ -1,198 +0,0 @@
use core::{hash::Hash, marker::PhantomData};
use serde::{
de::{MapAccess, SeqAccess, Visitor},
ser::SerializeMap,
Deserialize, Serialize,
};
use crate::{ClonableAllocator, HashMap, HashSet};
mod hashmap {
use super::*;
impl<K: Serialize, V: Serialize, ALLOCATOR: ClonableAllocator> Serialize
for HashMap<K, V, ALLOCATOR>
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(self.len()))?;
for (key, value) in self {
map.serialize_entry(key, value)?;
}
map.end()
}
}
impl<'de, K, V> Deserialize<'de> for HashMap<K, V>
where
K: Deserialize<'de> + Hash + Eq,
V: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_map(HashMapVisitor::new())
}
}
struct HashMapVisitor<K, V> {
_marker: PhantomData<fn() -> HashMap<K, V>>,
}
impl<K, V> HashMapVisitor<K, V> {
fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<'de, K, V> Visitor<'de> for HashMapVisitor<K, V>
where
K: Deserialize<'de> + Hash + Eq,
V: Deserialize<'de>,
{
type Value = HashMap<K, V>;
fn expecting(&self, formatter: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
formatter.write_str("an agb::HashMap")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(8));
while let Some((key, value)) = access.next_entry()? {
map.insert(key, value);
}
Ok(map)
}
}
}
mod hashset {
use super::*;
impl<K: Serialize, ALLOCATOR: ClonableAllocator> Serialize for HashSet<K, ALLOCATOR> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_seq(self)
}
}
struct HashSetVisitor<K> {
_marker: PhantomData<fn() -> HashSet<K>>,
}
impl<K> HashSetVisitor<K> {
fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<'de, K> Visitor<'de> for HashSetVisitor<K>
where
K: Deserialize<'de> + Hash + Eq,
{
type Value = HashSet<K>;
fn expecting(&self, formatter: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
formatter.write_str("an agb::HashSet")
}
fn visit_seq<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut set = HashSet::with_capacity(access.size_hint().unwrap_or(8));
while let Some(value) = access.next_element()? {
set.insert(value);
}
Ok(set)
}
}
impl<'de, K> Deserialize<'de> for HashSet<K>
where
K: Deserialize<'de> + Hash + Eq,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_seq(HashSetVisitor::new())
}
}
}
#[cfg(test)]
mod test {
use alloc::{
string::{String, ToString},
vec::Vec,
};
use crate::{HashMap, HashSet};
#[test]
fn deserialize_map() {
let json = r#"
{
"three": 3,
"seven": 7
}
"#;
let map = serde_json::from_str::<HashMap<String, i32>>(json).unwrap();
assert_eq!(
map,
HashMap::from_iter([("three".to_string(), 3), ("seven".to_string(), 7)])
);
}
#[test]
fn serialize_map() {
let map = HashMap::from_iter([("three".to_string(), 3), ("seven".to_string(), 7)]);
let json = serde_json::to_string(&map).unwrap();
let possibilities = &[r#"{"three":3,"seven":7}"#, r#"{"seven":7,"three":3}"#];
assert!(possibilities.contains(&json.as_str()));
}
#[test]
fn deserialize_hashset() {
let json = "[1, 2, 5, 8, 9, 3, 4]";
let set = serde_json::from_str::<HashSet<i32>>(json).unwrap();
assert_eq!(set, HashSet::from_iter([1, 2, 3, 4, 5, 8, 9]));
}
#[test]
fn serialize_hashset() {
let set = HashSet::from_iter([1, 2, 3, 5, 8, 9, 10]);
let serialized = serde_json::to_string(&set).unwrap();
let mut deserialized = serde_json::from_str::<Vec<i32>>(&serialized).unwrap();
deserialized.sort();
assert_eq!(deserialized, &[1, 2, 3, 5, 8, 9, 10]);
}
}

View file

@ -1,7 +1,7 @@
[package]
name = "agb_image_converter"
version = "0.21.1"
authors = ["Gwilym Inzani <gw@ilym.me>"]
version = "0.15.0"
authors = ["Gwilym Kuiper <gw@ilym.me>"]
edition = "2021"
license = "MPL-2.0"
description = "Library for converting graphics for use on the Game Boy Advance"
@ -11,16 +11,19 @@ repository = "https://github.com/agbrs/agb"
proc-macro = true
[dependencies]
image = { version = "0.24", default-features = false, features = [
"png",
"bmp",
] }
image = { version = "0.23", default-features = false, features = [ "png", "bmp" ] }
syn = { version = "2", features = ["proc-macro", "parsing"] }
proc-macro2 = "1"
quote = "1"
asefile = "0.3.8"
fontdue = "0.9"
pagination-packing = "2.1.0"
asefile = "0.3.5"
fontdue = "0.7"
[dev-dependencies]
quickcheck = "1"
[profile.dev]
opt-level = 3
debug = true
[profile.release]
opt-level = 3
lto = "fat"
debug = true
codegen-units = 1

View file

@ -1,6 +1,6 @@
use std::{fmt, str::FromStr};
use std::str::FromStr;
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Colour {
pub r: u8,
pub g: u8,
@ -8,18 +8,6 @@ pub struct Colour {
pub a: u8,
}
impl fmt::Debug for Colour {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?;
if self.a != 0xff {
write!(f, "{:02x}", self.a)?;
}
Ok(())
}
}
impl Colour {
pub fn from_rgb(r: u8, g: u8, b: u8, a: u8) -> Self {
Colour { r, g, b, a }
@ -50,26 +38,3 @@ impl FromStr for Colour {
Ok(Colour::from_rgb(r, g, b, 255))
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Colour {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
Self::from_rgb(
quickcheck::Arbitrary::arbitrary(g),
quickcheck::Arbitrary::arbitrary(g),
quickcheck::Arbitrary::arbitrary(g),
quickcheck::Arbitrary::arbitrary(g),
)
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
vec![
Colour::from_rgb(0, 0, 0, 0),
Colour::from_rgb(self.r, self.g, self.b, 0),
*self,
]
.into_iter(),
)
}
}

View file

@ -11,5 +11,4 @@ pub(crate) trait Config {
pub(crate) trait Image {
fn filename(&self) -> String;
fn colours(&self) -> Colours;
fn deduplicate(&self) -> bool;
}

View file

@ -1,141 +0,0 @@
use std::collections::BTreeMap;
use crate::{colour::Colour, image_loader::Image};
pub struct Transformation {
pub vflip: bool,
pub hflip: bool,
}
impl Transformation {
pub fn none() -> Self {
Self {
vflip: false,
hflip: false,
}
}
pub fn vflipped() -> Self {
Self {
vflip: true,
hflip: false,
}
}
pub fn hflipped() -> Self {
Self {
vflip: false,
hflip: true,
}
}
pub fn vhflipped() -> Self {
Self {
vflip: true,
hflip: true,
}
}
}
pub struct DeduplicatedData {
pub new_index: usize,
pub transformation: Transformation,
}
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
struct Tile {
data: [Colour; 64],
}
impl Tile {
fn split_image(input: &Image) -> Vec<Self> {
let mut ret = vec![];
for y in 0..(input.height / 8) {
for x in 0..(input.width / 8) {
let mut tile_data = Vec::with_capacity(64);
for j in 0..8 {
for i in 0..8 {
tile_data.push(input.colour(x * 8 + i, y * 8 + j));
}
}
ret.push(Tile {
data: tile_data.try_into().unwrap(),
});
}
}
ret
}
fn vflipped(&self) -> Self {
let mut new_data = self.data;
for y in 0..4 {
for x in 0..8 {
new_data.swap(y * 8 + x, (7 - y) * 8 + x);
}
}
Self { data: new_data }
}
fn hflipped(&self) -> Self {
let mut new_data = self.data;
for y in 0..8 {
for x in 0..4 {
new_data.swap(y * 8 + x, y * 8 + (7 - x));
}
}
Self { data: new_data }
}
}
pub(crate) fn deduplicate_image(input: &Image, can_flip: bool) -> (Image, Vec<DeduplicatedData>) {
let mut resulting_tiles = vec![];
let mut deduplication_data = vec![];
let all_tiles = Tile::split_image(input);
let mut existing_tiles = BTreeMap::new();
for tile in all_tiles {
let (tile, transformation) = if can_flip {
let vflipped = tile.vflipped();
let hflipped = tile.hflipped();
let vhflipped = vflipped.hflipped();
let minimum = (&tile).min(&vflipped).min(&hflipped).min(&vhflipped);
if minimum == &tile {
(tile, Transformation::none())
} else if minimum == &vflipped {
(vflipped, Transformation::vflipped())
} else if minimum == &hflipped {
(hflipped, Transformation::hflipped())
} else {
(vhflipped, Transformation::vhflipped())
}
} else {
(tile, Transformation::none())
};
let index = *existing_tiles.entry(tile.clone()).or_insert_with(|| {
resulting_tiles.push(tile);
resulting_tiles.len() - 1
});
deduplication_data.push(DeduplicatedData {
new_index: index,
transformation,
});
}
let image_data = resulting_tiles
.iter()
.flat_map(|tile| tile.data)
.collect::<Vec<_>>();
(Image::from_colour_data(image_data), deduplication_data)
}

View file

@ -3,28 +3,21 @@ use quote::quote;
use proc_macro2::TokenStream;
struct KerningData {
previous_character: char,
amount: f32,
}
struct LetterData {
character: char,
width: usize,
height: usize,
xmin: i32,
ymin: i32,
advance_width: f32,
rendered: Vec<u8>,
kerning_data: Vec<KerningData>,
}
pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
let font = fontdue::Font::from_bytes(
font_data,
fontdue::FontSettings {
collection_index: 0,
scale: pixels_per_em,
..Default::default()
},
)
.expect("Invalid font data");
@ -32,13 +25,11 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
let line_metrics = font.horizontal_line_metrics(pixels_per_em).unwrap();
let line_height = line_metrics.new_line_size as i32;
let mut ascent = line_metrics.ascent as i32;
let ascent = line_metrics.ascent as i32;
let mut letters: Vec<_> = font
.chars()
.iter()
.map(|(&c, &index)| (c, index, font.rasterize(c, pixels_per_em)))
.map(|(c, index, (metrics, bitmap))| {
let font = (0..128)
.map(|i| font.rasterize(char::from_u32(i).unwrap(), pixels_per_em))
.map(|(metrics, bitmap)| {
let width = metrics.width;
let height = metrics.height;
@ -56,81 +47,34 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
})
.collect();
let mut kerning_data: Vec<_> = font
.chars()
.iter()
.filter_map(|(&left_char, &left_index)| {
let kerning = font.horizontal_kern_indexed(
left_index.into(),
index.into(),
pixels_per_em,
)?;
Some(KerningData {
previous_character: left_char,
amount: kerning,
})
})
.collect();
kerning_data.sort_unstable_by_key(|kd| kd.previous_character);
LetterData {
character: c,
width,
height,
rendered,
xmin: metrics.xmin,
ymin: metrics.ymin,
advance_width: metrics.advance_width,
kerning_data,
}
})
.collect();
.map(|letter_data| {
let data_raw = ByteString(&letter_data.rendered);
let height = letter_data.height as u8;
let width = letter_data.width as u8;
let xmin = letter_data.xmin as i8;
let ymin = letter_data.ymin as i8;
let advance_width = letter_data.advance_width.ceil() as u8;
letters.sort_unstable_by_key(|letter| letter.character);
let maximum_above_line = letters
.iter()
.map(|x| (x.height as i32 + x.ymin))
.max()
.unwrap();
if (ascent - maximum_above_line) < 0 {
ascent = maximum_above_line;
}
let font = letters.iter().map(|letter_data| {
let character = letter_data.character;
let data_raw = ByteString(&letter_data.rendered);
let height = letter_data.height as u8;
let width = letter_data.width as u8;
let xmin = letter_data.xmin as i8;
let ymin = letter_data.ymin as i8;
let advance_width = letter_data.advance_width.ceil() as u8;
let kerning_amounts = letter_data.kerning_data.iter().map(|kerning_data| {
let amount = kerning_data.amount as i8;
let c = kerning_data.previous_character;
quote! {
(#c, #amount)
}
});
quote!(
display::FontLetter::new(
#character,
#width,
#height,
#data_raw,
#xmin,
#ymin,
#advance_width,
&[
#(#kerning_amounts),*
]
quote!(
display::FontLetter::new(
#width,
#height,
#data_raw,
#xmin,
#ymin,
#advance_width,
)
)
)
});
});
quote![
display::Font::new(&[#(#font),*], #line_height, #ascent)

View file

@ -4,7 +4,6 @@ use image::{DynamicImage, GenericImageView};
use crate::colour::Colour;
#[derive(Clone)]
pub(crate) struct Image {
pub width: usize,
pub height: usize,
@ -43,14 +42,6 @@ impl Image {
}
}
pub fn from_colour_data(colour_data: Vec<Colour>) -> Self {
Self {
height: colour_data.len() / 8,
colour_data,
width: 8,
}
}
pub fn colour(&self, x: usize, y: usize) -> Colour {
self.colour_data[x + y * self.width]
}

View file

@ -15,13 +15,13 @@ use quote::{format_ident, quote, ToTokens};
mod aseprite;
mod colour;
mod config;
mod deduplicator;
mod font_loader;
mod image_loader;
mod palette16;
mod palette256;
mod rust_generator;
use image::GenericImageView;
use image_loader::Image;
use colour::Colour;
@ -36,23 +36,16 @@ struct BackgroundGfxOption {
module_name: String,
file_name: String,
colours: Colours,
deduplicate: bool,
}
impl config::Image for BackgroundGfxOption {
fn filename(&self) -> String {
self.file_name
.clone()
.replace(OUT_DIR_TOKEN, &get_out_dir(&self.file_name))
self.file_name.clone()
}
fn colours(&self) -> Colours {
self.colours
}
fn deduplicate(&self) -> bool {
self.deduplicate
}
}
impl Parse for BackgroundGfxOption {
@ -79,37 +72,18 @@ impl Parse for BackgroundGfxOption {
Colours::Colours16
};
let lookahead = input.lookahead1();
let deduplicate = if lookahead.peek(syn::Ident) {
let deduplicate: syn::Ident = input.parse()?;
if deduplicate == "deduplicate" {
true
} else {
return Err(syn::Error::new_spanned(
deduplicate,
"Must either be the literal deduplicate or missing",
));
}
} else {
false
};
let file_name: syn::LitStr = input.parse()?;
Ok(Self {
module_name: module_name.to_string(),
file_name: file_name.value(),
colours,
deduplicate,
})
}
}
struct IncludeBackgroundGfxInput {
module_name: syn::Ident,
as_pub: bool,
crate_prefix: String,
transparent_colour: Colour,
background_gfx_options: Vec<BackgroundGfxOption>,
@ -127,15 +101,6 @@ impl Parse for IncludeBackgroundGfxInput {
format_ident!("agb")
};
let lookahead = input.lookahead1();
let as_pub = if lookahead.peek(Token![pub]) {
let _: Token![pub] = input.parse()?;
true
} else {
false
};
let module_name: syn::Ident = input.parse()?;
let _: Token![,] = input.parse()?;
@ -156,7 +121,6 @@ impl Parse for IncludeBackgroundGfxInput {
Ok(Self {
module_name,
as_pub,
crate_prefix: crate_prefix.to_string(),
transparent_colour,
background_gfx_options: background_gfx_options.into_iter().collect(),
@ -181,15 +145,6 @@ impl config::Config for IncludeBackgroundGfxInput {
}
}
/// Including from the out directory is supported through the `$OUT_DIR` token.
///
/// ```rust,ignore
/// # #![no_std]
/// # #![no_main]
/// # use agb::include_background_gfx;
/// include_background_gfx!(generated_background, "000000", DATA => "$OUT_DIR/generated_background.aseprite");
/// ```
///
#[proc_macro]
pub fn include_background_gfx(input: TokenStream) -> TokenStream {
let config = Box::new(parse_macro_input!(input as IncludeBackgroundGfxInput));
@ -197,13 +152,11 @@ pub fn include_background_gfx(input: TokenStream) -> TokenStream {
let root = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
let module_name = config.module_name.clone();
let as_pub = config.as_pub;
include_gfx_from_config(config, as_pub, module_name, Path::new(&root))
include_background_gfx_from_config(config, module_name, Path::new(&root))
}
fn include_gfx_from_config(
fn include_background_gfx_from_config(
config: Box<dyn config::Config>,
as_pub: bool,
module_name: syn::Ident,
parent: &Path,
) -> TokenStream {
@ -230,7 +183,6 @@ fn include_gfx_from_config(
&mut optimiser,
&image,
tile_size,
tile_size,
config.transparent_colour(),
);
@ -244,9 +196,7 @@ fn include_gfx_from_config(
}
}
let optimisation_results = optimiser
.optimise_palettes()
.expect("Failed to optimised palettes");
let optimisation_results = optimiser.optimise_palettes();
let optimisation_results = palette256.extend_results(&optimisation_results);
let mut image_code = vec![];
@ -270,21 +220,11 @@ fn include_gfx_from_config(
let palette_code =
rust_generator::generate_palette_code(&optimisation_results, &config.crate_prefix());
let module = if as_pub {
quote! {
pub mod #module_name {
#palette_code
let module = quote! {
mod #module_name {
#palette_code
#(#image_code)*
}
}
} else {
quote! {
mod #module_name {
#palette_code
#(#image_code)*
}
#(#image_code)*
}
};
@ -328,8 +268,6 @@ pub fn include_colours_inner(input: TokenStream) -> TokenStream {
#[proc_macro]
pub fn include_aseprite_inner(input: TokenStream) -> TokenStream {
let out_dir_path = get_out_dir(&input.to_string());
let parser = Punctuated::<LitStr, syn::Token![,]>::parse_terminated;
let parsed = match parser.parse(input) {
Ok(e) => e,
@ -347,7 +285,6 @@ pub fn include_aseprite_inner(input: TokenStream) -> TokenStream {
let filenames: Vec<PathBuf> = parsed
.iter()
.map(|s| s.value())
.map(|s| s.replace(OUT_DIR_TOKEN, &out_dir_path))
.map(|s| Path::new(&root).join(&*s))
.collect();
@ -368,20 +305,12 @@ pub fn include_aseprite_inner(input: TokenStream) -> TokenStream {
);
let image = Image::load_from_dyn_image(frame);
add_to_optimiser(
&mut optimiser,
&image,
width as usize,
height as usize,
Some(transparent_colour),
);
add_to_optimiser(&mut optimiser, &image, 8, Some(transparent_colour));
images.push(image);
}
}
let optimised_results = optimiser
.optimise_palettes()
.expect("Failed to optimise palettes");
let optimised_results = optimiser.optimise_palettes();
let (palette_data, tile_data, assignments) = palette_tile_data(&optimised_results, &images);
@ -441,15 +370,15 @@ pub fn include_aseprite_inner(input: TokenStream) -> TokenStream {
#(#include_paths)*
static PALETTES: &[Palette16] = &[
const PALETTES: &[Palette16] = &[
#(#palette_data),*
];
static SPRITES: &[Sprite] = &[
pub const SPRITES: &[Sprite] = &[
#(#sprites),*
];
static TAGS: TagMap = TagMap::new(
const TAGS: &TagMap = &TagMap::new(
&[
#(#tags),*
]
@ -470,7 +399,6 @@ fn convert_image(
) -> proc_macro2::TokenStream {
let image_filename = &parent.join(settings.filename());
let image = Image::load_from_file(image_filename);
let deduplicate = settings.deduplicate();
rust_generator::generate_code(
variable_name,
@ -479,27 +407,25 @@ fn convert_image(
&image_filename.to_string_lossy(),
crate_prefix.to_owned(),
assignment_offset,
deduplicate,
)
}
fn add_to_optimiser(
palette_optimiser: &mut palette16::Palette16Optimiser,
image: &Image,
tile_width: usize,
tile_height: usize,
tile_size: usize,
transparent_colour: Option<Colour>,
) {
let tiles_x = image.width / tile_width;
let tiles_y = image.height / tile_height;
let tiles_x = image.width / tile_size;
let tiles_y = image.height / tile_size;
for y in 0..tiles_y {
for x in 0..tiles_x {
let mut palette = palette16::Palette16::new();
for j in 0..tile_height {
for i in 0..tile_width {
let colour = image.colour(x * tile_width + i, y * tile_height + j);
for j in 0..tile_size {
for i in 0..tile_size {
let colour = image.colour(x * tile_size + i, y * tile_size + j);
palette.add_colour(match (colour.is_transparent(), transparent_colour) {
(true, Some(transparent_colour)) => transparent_colour,
@ -534,14 +460,7 @@ fn palette_tile_data(
let mut tile_data = Vec::new();
for (image_idx, image) in images.iter().enumerate() {
add_image_to_tile_data(
&mut tile_data,
image,
optimiser,
image_idx,
true,
&(0..images.len()).collect::<Vec<_>>(),
);
add_image_to_tile_data(&mut tile_data, image, optimiser, image_idx)
}
let tile_data = collapse_to_4bpp(&tile_data);
@ -563,8 +482,6 @@ fn add_image_to_tile_data(
image: &Image,
optimiser: &Palette16OptimisationResults,
assignment_offset: usize,
is_sprite: bool,
remap_index: &[usize],
) {
let tile_size = 8;
let tiles_x = image.width / tile_size;
@ -572,13 +489,7 @@ fn add_image_to_tile_data(
for y in 0..tiles_y {
for x in 0..tiles_x {
let assignment = if is_sprite {
assignment_offset
} else {
remap_index[y * tiles_x + x] + assignment_offset
};
let palette_index = optimiser.assignments[assignment];
let palette_index = optimiser.assignments[y * tiles_x + x + assignment_offset];
let palette = &optimiser.optimised_palettes[palette_index];
for inner_y in 0..tile_size / 8 {
@ -703,16 +614,6 @@ fn valid_sprite_size(width: u32, height: u32) -> bool {
}
}
const OUT_DIR_TOKEN: &str = "$OUT_DIR";
fn get_out_dir(raw_input: &str) -> String {
if raw_input.contains(OUT_DIR_TOKEN) {
std::env::var("OUT_DIR").expect("Failed to get OUT_DIR")
} else {
String::new()
}
}
#[cfg(test)]
mod tests {
use asefile::AnimationDirection;

View file

@ -1,13 +1,10 @@
use crate::colour::Colour;
use std::{
collections::{BTreeSet, HashSet},
fmt,
};
use std::collections::HashSet;
const MAX_COLOURS: usize = 256;
const MAX_COLOURS_PER_PALETTE: usize = 16;
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub(crate) struct Palette16 {
colours: Vec<Colour>,
}
@ -65,15 +62,12 @@ impl Palette16 {
self.colours.iter()
}
fn with_transparent(&self, transparent_colour: Colour) -> Self {
let mut new_colours = self.colours.clone();
let transparent_colour_index = new_colours
fn union_length(&self, other: &Palette16) -> usize {
self.colours
.iter()
.position(|&c| c == transparent_colour)
.expect("Could not find tranparent colour in palette");
new_colours.swap(0, transparent_colour_index);
Self::from(&new_colours)
.chain(&other.colours)
.collect::<HashSet<_>>()
.len()
}
fn is_satisfied_by(&self, other: &Palette16) -> bool {
@ -93,20 +87,6 @@ impl IntoIterator for Palette16 {
}
}
impl<'a, T> From<T> for Palette16
where
T: IntoIterator<Item = &'a Colour>,
{
fn from(value: T) -> Self {
let mut palette = Palette16::new();
for colour in value.into_iter() {
palette.add_colour(*colour);
}
palette
}
}
pub(crate) struct Palette16Optimiser {
palettes: Vec<Palette16>,
colours: Vec<Colour>,
@ -145,42 +125,28 @@ impl Palette16Optimiser {
}
}
pub fn optimise_palettes(&self) -> Result<Palette16OptimisationResults, DoesNotFitError> {
let transparent_colour = self
.transparent_colour
.unwrap_or_else(|| Colour::from_rgb(255, 0, 255, 0));
pub fn optimise_palettes(&self) -> Palette16OptimisationResults {
let mut assignments = vec![0; self.palettes.len()];
let mut optimised_palettes = vec![];
let palettes_to_optimise = self
let mut unsatisfied_palettes = self
.palettes
.iter()
.cloned()
.map(|mut palette| {
// ensure each palette we're creating the covering for has the transparent colour in it
palette.add_colour(transparent_colour);
palette
})
.collect::<BTreeSet<Palette16>>()
.into_iter()
.map(|palette| palette.colours)
.collect::<Vec<_>>();
.collect::<HashSet<Palette16>>();
let packed_palettes =
pagination_packing::overload_and_remove::<_, _, Vec<_>>(&palettes_to_optimise, 16);
while !unsatisfied_palettes.is_empty() {
let palette = self.find_maximal_palette_for(&unsatisfied_palettes);
let optimised_palettes = packed_palettes
.iter()
.map(|packed_palette| {
let colours = packed_palette.unique_symbols(&palettes_to_optimise);
Palette16::from(colours).with_transparent(transparent_colour)
})
.collect::<Vec<_>>();
unsatisfied_palettes.retain(|test_palette| !test_palette.is_satisfied_by(&palette));
if optimised_palettes.len() > 16 {
return Err(DoesNotFitError(packed_palettes.len()));
optimised_palettes.push(palette);
if optimised_palettes.len() == MAX_COLOURS / MAX_COLOURS_PER_PALETTE {
panic!("Failed to find covering palettes");
}
}
let mut assignments = vec![0; self.palettes.len()];
for (i, overall_palette) in self.palettes.iter().enumerate() {
assignments[i] = optimised_palettes
.iter()
@ -188,86 +154,59 @@ impl Palette16Optimiser {
.unwrap();
}
Ok(Palette16OptimisationResults {
Palette16OptimisationResults {
optimised_palettes,
assignments,
transparent_colour: self.transparent_colour,
})
}
}
pub struct DoesNotFitError(pub usize);
impl fmt::Display for DoesNotFitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Could not fit colours into palette, needed {} bins but can have at most 16",
self.0
)
}
}
impl fmt::Debug for DoesNotFitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
#[cfg(test)]
mod test {
use quickcheck::{quickcheck, Arbitrary};
use super::*;
quickcheck! {
fn less_than_256_colours_always_fits(palettes: Vec<Palette16>, transparent_colour: Colour) -> bool {
let mut optimiser = Palette16Optimiser::new(Some(transparent_colour));
for palette in palettes.clone().into_iter().take(16) {
optimiser.add_palette(palette);
}
let Ok(optimisation_results) = optimiser.optimise_palettes() else {
return false
};
check_palette_invariants(palettes.iter().take(16), optimisation_results, transparent_colour)
}
}
fn check_palette_invariants<'a>(
palettes: impl Iterator<Item = &'a Palette16>,
optimisation_results: Palette16OptimisationResults,
transparent_colour: Colour,
) -> bool {
for (i, palette) in palettes.enumerate() {
let optimised_palette =
&optimisation_results.optimised_palettes[optimisation_results.assignments[i]];
if !palette.is_satisfied_by(optimised_palette) {
return false;
fn find_maximal_palette_for(&self, unsatisfied_palettes: &HashSet<Palette16>) -> Palette16 {
let mut palette = Palette16::new();
palette.add_colour(
self.transparent_colour
.unwrap_or_else(|| Colour::from_rgb(255, 0, 255, 0)),
);
loop {
let mut colour_usage = vec![0; MAX_COLOURS];
let mut a_colour_is_used = false;
for current_palette in unsatisfied_palettes {
if palette.union_length(current_palette) > MAX_COLOURS_PER_PALETTE {
continue;
}
for colour in &current_palette.colours {
if palette.colours.contains(colour) {
continue;
}
if let Some(colour_index) = self.colours.iter().position(|c| c == colour) {
colour_usage[colour_index] += 1;
a_colour_is_used = true;
}
}
}
if optimised_palette.colour_index(transparent_colour) != 0 {
return false;
}
}
true
}
impl Arbitrary for Palette16 {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let mut palette = Palette16::new();
let size: usize = Arbitrary::arbitrary(g);
// never entirely fill the palette, will give at most 15 colours
let size = size.rem_euclid(16);
for _ in 0..size {
palette.add_colour(Arbitrary::arbitrary(g));
if !a_colour_is_used {
return palette;
}
palette
let best_index = colour_usage
.iter()
.enumerate()
.max_by(|(_, usage1), (_, usage2)| usage1.cmp(usage2))
.unwrap()
.0;
let best_colour = self.colours[best_index];
palette.add_colour(best_colour);
if palette.colours.len() == MAX_COLOURS_PER_PALETTE {
return palette;
}
}
}
}

View file

@ -1,4 +1,4 @@
use std::collections::BTreeSet;
use std::{collections::HashSet, iter::FromIterator};
use crate::{
colour::Colour,
@ -7,13 +7,13 @@ use crate::{
};
pub struct Palette256 {
colours: BTreeSet<Colour>,
colours: HashSet<Colour>,
}
impl Palette256 {
pub fn new() -> Self {
Self {
colours: BTreeSet::new(),
colours: HashSet::new(),
}
}
@ -41,8 +41,8 @@ impl Palette256 {
.cloned()
.collect();
let current_colours_set = BTreeSet::from_iter(optimised_palette_colours.iter().cloned());
let new_colours: BTreeSet<_> = self
let current_colours_set = HashSet::from_iter(optimised_palette_colours.iter().cloned());
let new_colours: HashSet<_> = self
.colours
.symmetric_difference(&current_colours_set)
.collect();

View file

@ -1,4 +1,3 @@
use crate::deduplicator::{DeduplicatedData, Transformation};
use crate::palette16::Palette16OptimisationResults;
use crate::{add_image_256_to_tile_data, add_image_to_tile_data, collapse_to_4bpp};
use crate::{image_loader::Image, ByteString};
@ -6,7 +5,6 @@ use crate::{image_loader::Image, ByteString};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::collections::BTreeMap;
use std::iter;
pub(crate) fn generate_palette_code(
@ -31,7 +29,7 @@ pub(crate) fn generate_palette_code(
});
quote! {
pub static PALETTES: &[#crate_prefix::display::palette16::Palette16] = &[#(#palettes),*];
pub const PALETTES: &[#crate_prefix::display::palette16::Palette16] = &[#(#palettes),*];
}
}
@ -42,87 +40,41 @@ pub(crate) fn generate_code(
image_filename: &str,
crate_prefix: String,
assignment_offset: Option<usize>,
deduplicate: bool,
) -> TokenStream {
let crate_prefix = format_ident!("{}", crate_prefix);
let output_variable_name = format_ident!("{}", output_variable_name);
let (image, dedup_data) = if deduplicate {
let (new_image, dedup_data) =
crate::deduplicator::deduplicate_image(image, assignment_offset.is_some());
(new_image, dedup_data)
} else {
(
image.clone(),
(0..(image.width * image.height / 8 / 8))
.map(|i| DeduplicatedData {
new_index: i,
transformation: Transformation::none(),
})
.collect(),
)
};
let remap_index = dedup_data
.iter()
.enumerate()
.map(|(i, data)| (data.new_index, i))
.collect::<BTreeMap<_, _>>(); // BTreeMap so that values below is in order
let remap_index = remap_index.values().cloned().collect::<Vec<_>>();
let (tile_data, assignments) = if let Some(assignment_offset) = assignment_offset {
let mut tile_data = Vec::new();
add_image_to_tile_data(
&mut tile_data,
&image,
results,
assignment_offset,
false,
&remap_index,
);
add_image_to_tile_data(&mut tile_data, image, results, assignment_offset);
let tile_data = collapse_to_4bpp(&tile_data);
let num_tiles = image.width * image.height / 8usize.pow(2);
let all_assignments = &results.assignments[assignment_offset..];
let assignments = (0..num_tiles)
.map(|tile_id| all_assignments[remap_index[tile_id]] as u8)
let assignments = results
.assignments
.iter()
.skip(assignment_offset)
.take(num_tiles)
.map(|&x| x as u8)
.collect();
(tile_data, assignments)
} else {
let mut tile_data = Vec::new();
add_image_256_to_tile_data(&mut tile_data, &image, results);
add_image_256_to_tile_data(&mut tile_data, image, results);
(tile_data, vec![])
};
let tile_settings = dedup_data.iter().map(|data| {
let palette_assignment = assignments.get(data.new_index).unwrap_or(&0);
let vflipped = data.transformation.vflip;
let hflipped = data.transformation.hflip;
let index = data.new_index as u16;
quote! {
#crate_prefix::display::tiled::TileSetting::new(#index, #hflipped, #vflipped, #palette_assignment)
}
});
let data = ByteString(&tile_data);
let tile_format = if assignment_offset.is_some() {
quote! { #crate_prefix::display::tiled::TileFormat::FourBpp }
} else {
quote! { #crate_prefix::display::tiled::TileFormat::EightBpp }
};
quote! {
#[allow(non_upper_case_globals)]
pub static #output_variable_name: #crate_prefix::display::tile_data::TileData = {
pub const #output_variable_name: #crate_prefix::display::tile_data::TileData = {
const _: &[u8] = include_bytes!(#image_filename);
const TILE_DATA: &[u8] = {
@ -131,7 +83,7 @@ pub(crate) fn generate_code(
pub bytes: Bytes,
}
const ALIGNED: &AlignedAs<u32, [u8]> = &AlignedAs {
const ALIGNED: &AlignedAs<u16, [u8]> = &AlignedAs {
_align: [],
bytes: *#data,
};
@ -139,13 +91,11 @@ pub(crate) fn generate_code(
&ALIGNED.bytes
};
const TILE_SET: #crate_prefix::display::tiled::TileSet = #crate_prefix::display::tiled::TileSet::new(TILE_DATA, #tile_format);
const TILE_SETTINGS: &[#crate_prefix::display::tiled::TileSetting] = &[
#(#tile_settings),*
const PALETTE_ASSIGNMENT: &[u8] = &[
#(#assignments),*
];
#crate_prefix::display::tile_data::TileData::new(TILE_SET, TILE_SETTINGS)
#crate_prefix::display::tile_data::TileData::new(TILE_DATA, PALETTE_ASSIGNMENT)
};
}
}

View file

@ -1,7 +1,7 @@
[package]
name = "agb_macros"
version = "0.21.1"
authors = ["Gwilym Inzani <gw@ilym.me>"]
version = "0.15.0"
authors = ["Gwilym Kuiper <gw@ilym.me>"]
edition = "2021"
license = "MPL-2.0"
description = "Macro for declaring the entry point for a game using the agb library"
@ -14,3 +14,13 @@ proc-macro = true
syn = { version = "2", features = ["full", "extra-traits"] }
proc-macro2 = "1"
quote = "1"
[profile.dev]
opt-level = 3
debug = true
[profile.release]
opt-level = 3
lto = "fat"
debug = true
codegen-units = 1

View file

@ -103,7 +103,6 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
quote!(
#[cfg(not(test))]
#[export_name = "main"]
#[doc(hidden)]
#(#attrs)*
pub extern "C" fn #fn_name() -> ! {
let #mutable #argument_name = unsafe { #argument_type ::new_in_entry() };
@ -113,7 +112,6 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
#[cfg(test)]
#[export_name = "main"]
#[doc(hidden)]
#(#attrs)*
pub extern "C" fn #fn_name() -> ! {
let mut #argument_name = unsafe { #argument_type ::new_in_entry() };

View file

@ -1,7 +1,7 @@
[package]
name = "agb_sound_converter"
version = "0.21.1"
authors = ["Gwilym Inzani <gw@ilym.me>"]
version = "0.15.0"
authors = ["Gwilym Kuiper <gw@ilym.me>"]
edition = "2021"
license = "MPL-2.0"
description = "Library for converting wavs for use on the Game Boy Advance"
@ -15,3 +15,13 @@ hound = "3.5"
syn = "2"
proc-macro2 = "1"
quote = "1"
[profile.dev]
opt-level = 3
debug = true
[profile.release]
opt-level = 3
lto = "fat"
debug = true
codegen-units = 1

View file

@ -6,17 +6,9 @@ build-std-features = ["compiler-builtins-mem"]
target = "thumbv4t-none-eabi"
[target.thumbv4t-none-eabi]
rustflags = [
"-Clink-arg=-Tgba.ld",
"-Ctarget-cpu=arm7tdmi",
"-Cforce-frame-pointers=yes",
]
rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"]
runner = "mgba-test-runner"
[target.armv4t-none-eabi]
rustflags = [
"-Clink-arg=-Tgba.ld",
"-Ctarget-cpu=arm7tdmi",
"-Cforce-frame-pointers=yes",
]
rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"]
runner = "mgba-test-runner"

View file

@ -1,46 +1,32 @@
[package]
name = "agb"
version = "0.21.1"
authors = ["Corwin Kuiper <corwin@kuiper.dev>", "Gwilym Inzani <gw@ilym.me>"]
version = "0.15.0"
authors = ["Corwin Kuiper <corwin@kuiper.dev>", "Gwilym Kuiper <gw@ilym.me>"]
edition = "2021"
description = "Library for Game Boy Advance Development"
license = "MPL-2.0"
repository = "https://github.com/agbrs/agb"
homepage = "https://agbrs.dev"
exclude = ["/tests", "/examples"]
keywords = ["game-engines", "embedded"]
[features]
default = ["backtrace", "testing"]
backtrace = ["testing", "dep:qrcodegen-no-heap"]
default = ["testing"]
testing = []
multiboot = []
[dependencies]
bitflags = "2"
agb_image_converter = { version = "0.21.1", path = "../agb-image-converter" }
agb_sound_converter = { version = "0.21.1", path = "../agb-sound-converter" }
agb_macros = { version = "0.21.1", path = "../agb-macros" }
agb_fixnum = { version = "0.21.1", path = "../agb-fixnum" }
agb_hashmap = { version = "0.21.1", path = "../agb-hashmap", features = [
"allocator_api",
] }
bilge = "0.2"
qrcodegen-no-heap = { version = "1.8", optional = true }
portable-atomic = { version = "1.6.0", default-features = false, features = [
"unsafe-assume-single-core",
"fallback",
] }
once_cell = { version = "1.20.1", default-features = false, features = [
"critical-section",
] }
critical-section = { version = "1.1.2", features = ["restore-state-u16"] }
agb_image_converter = { version = "0.15.0", path = "../agb-image-converter" }
agb_sound_converter = { version = "0.15.0", path = "../agb-sound-converter" }
agb_macros = { version = "0.15.0", path = "../agb-macros" }
agb_fixnum = { version = "0.15.0", path = "../agb-fixnum" }
agb_hashmap = { version = "0.15.0", path = "../agb-hashmap" }
bare-metal = "1"
modular-bitfield = "0.11"
rustc-hash = { version = "1", default-features = false }
embedded-hal = "0.2.7"
nb = "1.1"
[package.metadata.docs.rs]
default-target = "thumbv4t-none-eabi"
cargo-args = ["-Zbuild-std=core,alloc"]
default-target = "thumbv6m-none-eabi"
targets = []
[profile.dev]
opt-level = 3
@ -50,3 +36,4 @@ debug = true
opt-level = 3
lto = "fat"
debug = true
codegen-units = 1

View file

@ -1 +0,0 @@
../README.md

View file

@ -1,20 +1,76 @@
use std::{env, error::Error, fs, path::PathBuf};
use std::path;
fn main() -> Result<(), Box<dyn Error>> {
let out = &PathBuf::from(env::var("OUT_DIR").unwrap());
let linker_script = if env::var("CARGO_FEATURE_MULTIBOOT").is_ok() {
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/gba_mb.ld")).as_slice()
} else {
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/gba.ld")).as_slice()
};
fn main() {
let asm = &[
"src/crt0.s",
"src/interrupt_handler.s",
"src/sound/mixer/mixer.s",
"src/agbabi/memset.s",
"src/agbabi/memcpy.s",
"src/save/asm_routines.s",
];
fs::write(out.join("gba.ld"), linker_script)?;
println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=gba.ld");
println!("cargo:rerun-if-changed=gba_mb.ld");
println!("cargo:rerun-if-changed=src/asm_include.s");
println!("cargo:rerun-if-changed=src/agbabi/macros.inc");
println!("cargo:rerun-if-changed=gfx/test_logo.png");
println!("cargo:rerun-if-changed=src/gba.ld");
println!("cargo:rerun-if-changed=src/gba_mb.ld");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_MULTIBOOT");
Ok(())
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR environment variable must be specified");
let mut o_files = vec![];
for &a in asm.iter() {
println!("cargo:rerun-if-changed={a}");
let filename = path::Path::new(a);
let filename = filename.with_extension("o");
let filename = filename
.file_name()
.expect("should have filename")
.to_str()
.expect("Please make it valid utf-8");
let out_file_path = format!("{out_dir}/{filename}");
let out = std::process::Command::new("arm-none-eabi-as")
.arg("-mthumb-interwork")
.arg("-mcpu=arm7tdmi")
.arg("-g")
.args(["-o", out_file_path.as_str()])
.arg(a)
.output()
.unwrap_or_else(|_| panic!("failed to compile {a}"));
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
for warning_line in String::from_utf8_lossy(&out.stderr).split('\n') {
if !warning_line.is_empty() {
println!("cargo:warning={warning_line}");
}
}
o_files.push(out_file_path);
}
let archive = format!("{out_dir}/agb.a");
let _ = std::fs::remove_file(&archive);
let ar_out = std::process::Command::new("arm-none-eabi-ar")
.arg("-crs")
.arg(&archive)
.args(&o_files)
.output()
.expect("Failed to create static library");
assert!(
ar_out.status.success(),
"{}",
String::from_utf8_lossy(&ar_out.stderr)
);
println!("cargo:rustc-link-search={out_dir}");
}

View file

@ -4,34 +4,34 @@
use agb::{
display::{
affine::AffineMatrixBackground,
tiled::{AffineBackgroundSize, TiledMap},
tiled::{AffineBackgroundSize, TileFormat, TileSet, TiledMap},
Priority,
},
fixnum::{num, Num},
include_background_gfx,
};
include_background_gfx!(affine_tiles, "3f3f74", water_tiles => 256 "examples/water_tiles.png");
include_background_gfx!(affine_tiles, water_tiles => 256 "examples/water_tiles.png");
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
let (gfx, mut vram) = gba.display.video.tiled1();
let (gfx, mut vram) = gba.display.video.tiled2();
let vblank = agb::interrupt::VBlank::get();
let tileset = &affine_tiles::water_tiles.tiles;
let tileset = TileSet::new(affine_tiles::water_tiles.tiles, TileFormat::EightBpp);
vram.set_background_palettes(affine_tiles::PALETTES);
let mut bg = gfx.affine(Priority::P0, AffineBackgroundSize::Background32x32);
let mut bg = gfx.background(Priority::P0, AffineBackgroundSize::Background32x32);
for y in 0..32u16 {
for x in 0..32u16 {
bg.set_tile(&mut vram, (x, y), tileset, 1);
bg.set_tile(&mut vram, (x, y).into(), &tileset, 1);
}
}
bg.commit(&mut vram);
bg.set_visible(true);
bg.show();
let mut rotation = num!(0.);
let rotation_increase: Num<i32, 16> = num!(0.01);
@ -43,17 +43,17 @@ fn main(mut gba: agb::Gba) -> ! {
loop {
input.update();
scroll_x += input.x_tri() as i16;
scroll_y += input.y_tri() as i16;
scroll_x += input.x_tri() as i32;
scroll_y += input.y_tri() as i32;
let scroll_pos = (scroll_x, scroll_y);
let scroll_pos = (scroll_x, scroll_y).into();
rotation += rotation_increase;
rotation = rotation.rem_euclid(1.into());
let transformation = AffineMatrixBackground::from_scale_rotation_position(
(0, 0),
(1, 1),
(0, 0).into(),
(1, 1).into(),
rotation,
scroll_pos,
);

View file

@ -3,7 +3,7 @@
use agb::{
display::{
tiled::{RegularBackgroundSize, TiledMap},
tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, TiledMap},
Priority,
},
include_background_gfx,
@ -16,35 +16,35 @@ fn main(mut gba: agb::Gba) -> ! {
let (gfx, mut vram) = gba.display.video.tiled0();
let vblank = agb::interrupt::VBlank::get();
let tileset = &water_tiles::water_tiles.tiles;
let tileset = TileSet::new(water_tiles::water_tiles.tiles, TileFormat::FourBpp);
vram.set_background_palettes(water_tiles::PALETTES);
let mut bg = gfx.background(
Priority::P0,
RegularBackgroundSize::Background32x32,
tileset.format(),
TileFormat::FourBpp,
);
for y in 0..20u16 {
for x in 0..30u16 {
bg.set_tile(
&mut vram,
(x, y),
tileset,
water_tiles::water_tiles.tile_settings[0],
(x, y).into(),
&tileset,
TileSetting::new(0, false, false, 0),
);
}
}
bg.commit(&mut vram);
bg.set_visible(true);
bg.show();
let mut i = 0;
loop {
i = (i + 1) % 8;
vram.replace_tile(tileset, 0, tileset, i);
vram.replace_tile(&tileset, 0, &tileset, i);
vblank.wait_for_vblank();
}

View file

@ -1,7 +1,7 @@
#![no_std]
#![no_main]
use agb::display::{self, HEIGHT, WIDTH};
use agb::display;
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
@ -11,22 +11,25 @@ fn main(mut gba: agb::Gba) -> ! {
bitmap.set_palette_entry(1, 0x001F);
bitmap.set_palette_entry(2, 0x03E0);
for y in 0..HEIGHT {
for x in 0..WIDTH {
bitmap.draw_point_page(x, y, 0xF, display::bitmap4::Page::Front);
bitmap.draw_point_page(x, y, 1, display::bitmap4::Page::Front);
bitmap.draw_point_page(x, y, 0xFF, display::bitmap4::Page::Back);
bitmap.draw_point_page(x, y, 2, display::bitmap4::Page::Back);
}
}
bitmap.draw_point_page(
display::WIDTH / 2,
display::HEIGHT / 2,
1,
display::bitmap4::Page::Front,
);
bitmap.draw_point_page(
display::WIDTH / 2 + 5,
display::HEIGHT / 2,
2,
display::bitmap4::Page::Back,
);
let mut input = agb::input::ButtonController::new();
let mut count = 0;
loop {
vblank.wait_for_vblank();
input.update();
if input.is_just_pressed(agb::input::Button::A) {
count += 1;
if count % 6 == 0 {
bitmap.flip_page();
}
}

View file

@ -11,6 +11,7 @@ use agb::{
},
input::Button,
};
use core::convert::TryInto;
#[derive(PartialEq, Eq)]
enum State {
@ -64,13 +65,13 @@ fn main(mut gba: agb::Gba) -> ! {
let i = i as u16;
background.set_tile(
&mut vram,
(i % 32, i / 32),
(i % 32, i / 32).into(),
&tileset,
TileSetting::from_raw(tile),
);
}
background.set_visible(true);
background.show();
background.commit(&mut vram);
let object = gba.display.object.get_managed();

View file

@ -1,49 +0,0 @@
#![no_std]
#![no_main]
extern crate alloc;
use alloc::boxed::Box;
use agb::{
display::{
example_logo,
tiled::{RegularBackgroundSize, TileFormat},
},
interrupt::VBlank,
};
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
let (gfx, mut vram) = gba.display.video.tiled0();
let mut map = gfx.background(
agb::display::Priority::P0,
RegularBackgroundSize::Background32x32,
TileFormat::FourBpp,
);
let dma = gba.dma.dma().dma0;
example_logo::display_logo_basic(&mut map, &mut vram);
let vblank = VBlank::get();
let colours: Box<[_]> = (0..160).map(|i| ((i * 0xffff) / 160) as u16).collect();
let background_colour = 0x732b; // generated using `https://agbrs.dev/colour`
let background_colour_index = vram
.find_colour_index_16(0, background_colour)
.expect("Should contain colour 0x732b");
loop {
let _background_color_transfer = unsafe {
dma.hblank_transfer(
&vram.background_palette_colour_dma(0, background_colour_index),
&colours,
)
};
vblank.wait_for_vblank();
}
}

View file

@ -1,46 +0,0 @@
#![no_std]
#![no_main]
extern crate alloc;
use alloc::boxed::Box;
use agb::{
display::{
example_logo,
tiled::{RegularBackgroundSize, TileFormat},
},
interrupt::VBlank,
};
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
let (gfx, mut vram) = gba.display.video.tiled0();
let mut map = gfx.background(
agb::display::Priority::P0,
RegularBackgroundSize::Background32x32,
TileFormat::FourBpp,
);
let dma = gba.dma.dma().dma0;
example_logo::display_logo(&mut map, &mut vram);
let vblank = VBlank::get();
let offsets: Box<[_]> = (0..160 * 2).collect();
let mut frame = 0;
loop {
let _x_scroll_transfer =
unsafe { dma.hblank_transfer(&map.x_scroll_dma(), &offsets[frame..]) };
vblank.wait_for_vblank();
frame += 1;
if frame > 160 {
frame = 0;
}
}
}

View file

@ -1,96 +0,0 @@
#![no_std]
#![no_main]
extern crate alloc;
use agb::{
display::{
example_logo,
tiled::{RegularBackgroundSize, TileFormat},
window::WinIn,
HEIGHT, WIDTH,
},
fixnum::{Num, Rect, Vector2D},
interrupt::VBlank,
};
use alloc::{boxed::Box, vec};
type FNum = Num<i32, 8>;
#[agb::entry]
fn entry(mut gba: agb::Gba) -> ! {
main(gba)
}
fn main(mut gba: agb::Gba) -> ! {
let (gfx, mut vram) = gba.display.video.tiled0();
let mut map = gfx.background(
agb::display::Priority::P0,
RegularBackgroundSize::Background32x32,
TileFormat::FourBpp,
);
let mut window = gba.display.window.get();
window
.win_in(WinIn::Win0)
.set_background_enable(map.background(), true)
.set_position(&Rect::new((10, 10).into(), (64, 64).into()))
.enable();
let dmas = gba.dma.dma();
example_logo::display_logo(&mut map, &mut vram);
let mut pos: Vector2D<FNum> = (10, 10).into();
let mut velocity: Vector2D<FNum> = Vector2D::new(1.into(), 1.into());
let vblank = VBlank::get();
let circle: Box<[_]> = (1..64i32)
.map(|i| {
let y = 32 - i;
let x = (32 * 32 - y * y).isqrt();
let x1 = 32 - x;
let x2 = 32 + x;
((x1 as u16) << 8) | (x2 as u16)
})
.collect();
let mut circle_poses = vec![0; 160];
loop {
pos += velocity;
if pos.x.floor() > WIDTH - 64 || pos.x.floor() < 0 {
velocity.x *= -1;
}
if pos.y.floor() > HEIGHT - 64 || pos.y.floor() < 0 {
velocity.y *= -1;
}
let x_pos = pos.x.floor().max(0) as u16;
let y_pos = pos.y.floor().max(0);
let x_adjustment = x_pos << 8 | x_pos;
for (i, value) in circle_poses.iter_mut().enumerate() {
let i = i as i32;
if i <= y_pos || i >= y_pos + 64 {
*value = 0;
continue;
}
*value = circle[(i - y_pos) as usize - 1] + x_adjustment;
}
window
.win_in(WinIn::Win0)
.set_position(&Rect::new(pos.floor(), (64, 65).into()));
window.commit();
let dma_controllable = window.win_in(WinIn::Win0).horizontal_position_dma();
let _transfer = unsafe { dmas.dma0.hblank_transfer(&dma_controllable, &circle_poses) };
vblank.wait_for_vblank();
}
}

View file

@ -3,7 +3,7 @@
use agb::display::{
palette16::Palette16,
tiled::{RegularBackgroundSize, TileFormat, TiledMap},
tiled::{RegularBackgroundSize, TileFormat, TileSetting, TiledMap},
Priority,
};
@ -40,9 +40,9 @@ fn main(mut gba: agb::Gba) -> ! {
bg.set_tile(
&mut vram,
(x as u16, y as u16),
(x as u16, y as u16).into(),
&dynamic_tile.tile_set(),
dynamic_tile.tile_setting(),
TileSetting::from_raw(dynamic_tile.tile_index()),
);
vram.remove_dynamic_tile(dynamic_tile);
@ -50,7 +50,7 @@ fn main(mut gba: agb::Gba) -> ! {
}
bg.commit(&mut vram);
bg.set_visible(true);
bg.show();
loop {
vblank.wait_for_vblank();

View file

@ -1,94 +0,0 @@
Copyright (c) 2021, TakWolf (https://takwolf.com),
with Reserved Font Name 'Ark Pixel'.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

View file

@ -0,0 +1,25 @@
#![no_std]
#![no_main]
#[panic_handler]
fn panic_handler(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[no_mangle]
pub extern "C" fn __RUST_INTERRUPT_HANDLER(_: u16) {}
// implementation of tonc's "My first GBA demo"
// https://coranac.com/tonc/text/first.htm
#[no_mangle]
pub fn main() -> ! {
unsafe {
*(0x0400_0000 as *mut u32) = 0x0403;
let video = 0x0600_0000 as *mut u16;
*video.offset(120 + 80 * 240) = 0x001F;
*video.offset(136 + 80 * 240) = 0x03E0;
*video.offset(120 + 96 * 240) = 0x7C00;
}
loop {}
}

View file

@ -3,7 +3,9 @@
use agb::{
display::{
tiled::{RegularBackgroundSize, RegularMap, TileFormat, TiledMap, VRamManager},
tiled::{
RegularBackgroundSize, RegularMap, TileFormat, TileSetting, TiledMap, VRamManager,
},
Font, Priority,
},
include_font, include_wav,
@ -14,9 +16,9 @@ use agb::{
use core::fmt::Write;
// Music - "Crazy glue" by Josh Woodward, free download at http://joshwoodward.com
static CRAZY_GLUE: &[u8] = include_wav!("examples/JoshWoodward-CrazyGlue.wav");
const CRAZY_GLUE: &[u8] = include_wav!("examples/JoshWoodward-CrazyGlue.wav");
static FONT: Font = include_font!("examples/font/yoster.ttf", 12);
const FONT: Font = include_font!("examples/font/yoster.ttf", 12);
#[agb::entry]
fn main(mut gba: Gba) -> ! {
@ -31,7 +33,7 @@ fn main(mut gba: Gba) -> ! {
init_background(&mut bg, &mut vram);
let mut title_renderer = FONT.render_text((0u16, 3u16));
let mut title_renderer = FONT.render_text((0u16, 3u16).into());
let mut writer = title_renderer.writer(1, 0, &mut bg, &mut vram);
writeln!(&mut writer, "Crazy Glue by Josh Woodward").unwrap();
@ -39,7 +41,7 @@ fn main(mut gba: Gba) -> ! {
writer.commit();
bg.commit(&mut vram);
bg.set_visible(true);
bg.show();
let timer_controller = gba.timers.timers();
let mut timer = timer_controller.timer2;
@ -57,7 +59,7 @@ fn main(mut gba: Gba) -> ! {
let mut frame_counter = 0i32;
let mut has_written_frame_time = false;
let mut stats_renderer = FONT.render_text((0u16, 6u16));
let mut stats_renderer = FONT.render_text((0u16, 6u16).into());
loop {
vblank_provider.wait_for_vblank();
bg.commit(&mut vram);
@ -106,9 +108,9 @@ fn init_background(bg: &mut RegularMap, vram: &mut VRamManager) {
for x in 0..30u16 {
bg.set_tile(
vram,
(x, y),
(x, y).into(),
&background_tile.tile_set(),
background_tile.tile_setting(),
TileSetting::from_raw(background_tile.tile_index()),
);
}
}

View file

@ -7,7 +7,7 @@ use agb::sound::mixer::{Frequency, SoundChannel};
use agb::{fixnum::num, include_wav, Gba};
// Music - "Dead Code" by Josh Woodward, free download at http://joshwoodward.com
static DEAD_CODE: &[u8] = include_wav!("examples/JoshWoodward-DeadCode.wav");
const DEAD_CODE: &[u8] = include_wav!("examples/JoshWoodward-DeadCode.wav");
#[agb::entry]
fn main(mut gba: Gba) -> ! {
@ -25,8 +25,8 @@ fn main(mut gba: Gba) -> ! {
{
if let Some(channel) = mixer.channel(&channel_id) {
let half: Num<i16, 8> = num!(0.5);
let half_usize: Num<u32, 8> = num!(0.5);
let half: Num<i16, 4> = num!(0.5);
let half_usize: Num<usize, 8> = num!(0.5);
match input.x_tri() {
Tri::Negative => channel.panning(-half),
Tri::Zero => channel.panning(0),
@ -46,14 +46,6 @@ fn main(mut gba: Gba) -> ! {
} else {
channel.volume(1);
}
if input.is_pressed(Button::A) {
channel.resume();
}
if input.is_pressed(Button::B) {
channel.pause();
}
}
}

View file

@ -1,98 +0,0 @@
#![no_std]
#![no_main]
use agb::{
display::{
object::{ChangeColour, ObjectTextRender, PaletteVram, Size, TextAlignment},
palette16::Palette16,
Font, HEIGHT, WIDTH,
},
include_font,
input::Button,
};
extern crate alloc;
use core::fmt::Write;
static FONT: Font = include_font!("examples/font/ark-pixel-10px-proportional-ja.ttf", 10);
#[agb::entry]
fn entry(gba: agb::Gba) -> ! {
main(gba);
}
fn main(mut gba: agb::Gba) -> ! {
let (mut unmanaged, _sprites) = gba.display.object.get_unmanaged();
let mut palette = [0x0; 16];
palette[1] = 0xFF_FF;
palette[2] = 0x00_FF;
let palette = Palette16::new(palette);
let palette = PaletteVram::new(&palette).unwrap();
let timer = gba.timers.timers();
let mut timer: agb::timer::Timer = timer.timer2;
timer.set_enabled(true);
timer.set_divider(agb::timer::Divider::Divider256);
let mut wr = ObjectTextRender::new(&FONT, Size::S16x16, palette);
let start = timer.value();
let player_name = "You";
let _ = writeln!(
wr,
"Woah!{change2} {player_name}! {change1}こんにちは! I have a bunch of text I want to show you. However, you will find that the amount of text I can display is limited. Who'd have thought! Good thing that my text system supports scrolling! It only took around 20 jank versions to get here!",
change2 = ChangeColour::new(2),
change1 = ChangeColour::new(1),
);
let end = timer.value();
agb::println!(
"Write took {} cycles",
256 * (end.wrapping_sub(start) as u32)
);
let vblank = agb::interrupt::VBlank::get();
let mut input = agb::input::ButtonController::new();
let start = timer.value();
wr.layout((WIDTH, 40), TextAlignment::Justify, 2);
let end = timer.value();
agb::println!(
"Layout took {} cycles",
256 * (end.wrapping_sub(start) as u32)
);
let mut line_done = false;
let mut frame = 0;
loop {
vblank.wait_for_vblank();
input.update();
let oam = &mut unmanaged.iter();
wr.commit(oam);
let start = timer.value();
if frame % 4 == 0 {
line_done = !wr.next_letter_group();
}
if line_done && input.is_just_pressed(Button::A) {
line_done = false;
wr.pop_line();
}
wr.update((0, HEIGHT - 40));
let end = timer.value();
frame += 1;
agb::println!(
"Took {} cycles, line done {}",
256 * (end.wrapping_sub(start) as u32),
line_done
);
}
}

View file

@ -1,17 +1,17 @@
#![no_std]
#![no_main]
use portable_atomic::{AtomicU32, Ordering};
use agb::sync::Static;
static COUNT: AtomicU32 = AtomicU32::new(0);
static COUNT: Static<u32> = Static::new(0);
#[agb::entry]
fn main(_gba: agb::Gba) -> ! {
let _a = unsafe {
agb::interrupt::add_interrupt_handler(agb::interrupt::Interrupt::VBlank, |_| {
let cur_count = COUNT.load(Ordering::SeqCst);
let cur_count = COUNT.read();
agb::println!("Hello, world, frame = {}", cur_count);
COUNT.store(cur_count + 1, Ordering::SeqCst);
COUNT.write(cur_count + 1);
})
};
loop {}

View file

@ -16,7 +16,7 @@ fn main(mut gba: agb::Gba) -> ! {
}
if input.is_just_pressed(agb::input::Button::B) {
#[allow(arithmetic_overflow)]
let _p = i32::MAX + 1;
let _p = core::i32::MAX + 1;
}
}
}

View file

@ -1,29 +0,0 @@
#![no_std]
#![no_main]
use agb::save::Error;
extern crate alloc;
use alloc::vec::Vec;
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
test_save(gba).unwrap();
panic!("example finished");
}
fn test_save(mut gba: agb::Gba) -> Result<(), Error> {
gba.save.init_sram();
let mut access = gba.save.access()?;
let mut is_save = 0;
access.read(0, core::slice::from_mut(&mut is_save))?;
if is_save != 0 {
access
.prepare_write(0..128)?
.write(0, &(0..128).collect::<Vec<_>>())?;
}
Ok(())
}

View file

@ -11,14 +11,14 @@ use agb::fixnum::num;
use agb_fixnum::Num;
use alloc::vec::Vec;
static GRAPHICS: &Graphics = agb::include_aseprite!(
const GRAPHICS: &Graphics = agb::include_aseprite!(
"examples/gfx/objects.aseprite",
"examples/gfx/boss.aseprite",
"examples/gfx/wide.aseprite",
"examples/gfx/tall.aseprite"
);
static SPRITES: &[Sprite] = GRAPHICS.sprites();
static TAG_MAP: &TagMap = GRAPHICS.tags();
const SPRITES: &[Sprite] = GRAPHICS.sprites();
const TAG_MAP: &TagMap = GRAPHICS.tags();
fn all_sprites(gfx: &OamManaged, rotation_speed: Num<i32, 16>) {
let mut input = agb::input::ButtonController::new();
@ -34,7 +34,7 @@ fn all_sprites(gfx: &OamManaged, rotation_speed: Num<i32, 16>) {
let mut obj = gfx.object_sprite(&SPRITES[0]);
obj.set_affine_matrix(matrix.clone());
obj.show_affine(object::AffineMode::Affine);
obj.set_position((x * 16 + 8, y * 16 + 8));
obj.set_position((x * 16 + 8, y * 16 + 8).into());
objs.push(obj);
}
}
@ -87,7 +87,7 @@ fn all_tags(gfx: &OamManaged) {
let (size_x, size_y) = (size_x as i32, size_y as i32);
let mut obj = gfx.object_sprite(sprite);
obj.show();
obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2));
obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2).into());
objs.push((obj, v));
}

View file

@ -3,7 +3,9 @@
use agb::{
display::{
tiled::{RegularBackgroundSize, RegularMap, TileFormat, TiledMap, VRamManager},
tiled::{
RegularBackgroundSize, RegularMap, TileFormat, TileSetting, TiledMap, VRamManager,
},
Font, Priority,
},
include_font, include_wav,
@ -14,9 +16,9 @@ use agb::{
use core::fmt::Write;
// Music - "Let it in" by Josh Woodward, free download at http://joshwoodward.com
static LET_IT_IN: &[u8] = include_wav!("examples/JoshWoodward-LetItIn.wav");
const LET_IT_IN: &[u8] = include_wav!("examples/JoshWoodward-LetItIn.wav");
static FONT: Font = include_font!("examples/font/yoster.ttf", 12);
const FONT: Font = include_font!("examples/font/yoster.ttf", 12);
#[agb::entry]
fn main(mut gba: Gba) -> ! {
@ -31,7 +33,7 @@ fn main(mut gba: Gba) -> ! {
init_background(&mut bg, &mut vram);
let mut title_renderer = FONT.render_text((0u16, 3u16));
let mut title_renderer = FONT.render_text((0u16, 3u16).into());
let mut writer = title_renderer.writer(1, 0, &mut bg, &mut vram);
writeln!(&mut writer, "Let it in by Josh Woodward").unwrap();
@ -39,7 +41,7 @@ fn main(mut gba: Gba) -> ! {
writer.commit();
bg.commit(&mut vram);
bg.set_visible(true);
bg.show();
let timer_controller = gba.timers.timers();
let mut timer = timer_controller.timer2;
@ -55,7 +57,7 @@ fn main(mut gba: Gba) -> ! {
let mut frame_counter = 0i32;
let mut has_written_frame_time = false;
let mut stats_renderer = FONT.render_text((0u16, 6u16));
let mut stats_renderer = FONT.render_text((0u16, 6u16).into());
loop {
vblank_provider.wait_for_vblank();
bg.commit(&mut vram);
@ -94,9 +96,9 @@ fn init_background(bg: &mut RegularMap, vram: &mut VRamManager) {
for x in 0..30u16 {
bg.set_tile(
vram,
(x, y),
(x, y).into(),
&background_tile.tile_set(),
background_tile.tile_setting(),
TileSetting::from_raw(background_tile.tile_index()),
);
}
}

View file

@ -3,7 +3,7 @@
use agb::{
display::{
tiled::{RegularBackgroundSize, TileFormat, TiledMap},
tiled::{RegularBackgroundSize, TileFormat, TileSetting, TiledMap},
Font, Priority,
},
include_font,
@ -11,7 +11,7 @@ use agb::{
use core::fmt::Write;
static FONT: Font = include_font!("examples/font/ark-pixel-10px-proportional-ja.ttf", 10);
const FONT: Font = include_font!("examples/font/yoster.ttf", 12);
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
@ -35,30 +35,30 @@ fn main(mut gba: agb::Gba) -> ! {
for x in 0..30u16 {
bg.set_tile(
&mut vram,
(x, y),
(x, y).into(),
&background_tile.tile_set(),
background_tile.tile_setting(),
TileSetting::from_raw(background_tile.tile_index()),
);
}
}
vram.remove_dynamic_tile(background_tile);
let mut renderer = FONT.render_text((0u16, 3u16));
let mut renderer = FONT.render_text((0u16, 3u16).into());
let mut writer = renderer.writer(1, 2, &mut bg, &mut vram);
writeln!(&mut writer, "Hello, World! こんにちは 世界").unwrap();
writeln!(&mut writer, "Hello, World!").unwrap();
writeln!(&mut writer, "This is a font rendering example").unwrap();
writer.commit();
bg.commit(&mut vram);
bg.set_visible(true);
bg.show();
let mut frame = 0;
loop {
let mut renderer = FONT.render_text((4u16, 0u16));
let mut renderer = FONT.render_text((4u16, 0u16).into());
let mut writer = renderer.writer(1, 2, &mut bg, &mut vram);
writeln!(&mut writer, "Frame {frame}").unwrap();

View file

@ -0,0 +1,5 @@
version = "1.0"
[image.water_tiles]
filename = "water_tiles.png"
tile_size = "8x8"

View file

@ -9,9 +9,9 @@ use agb::{
tiled::{RegularBackgroundSize, TileFormat},
},
fixnum::FixedNum,
interrupt::Interrupt,
interrupt::{free, Interrupt},
};
use critical_section::{CriticalSection, Mutex};
use bare_metal::{CriticalSection, Mutex};
struct BackCosines {
cosines: [u16; 32],
@ -37,7 +37,7 @@ fn main(mut gba: agb::Gba) -> ! {
let _a = unsafe {
agb::interrupt::add_interrupt_handler(Interrupt::HBlank, |key: CriticalSection| {
let mut back = BACK.borrow_ref_mut(key);
let mut back = BACK.borrow(key).borrow_mut();
let deflection = back.cosines[back.row % 32];
((0x0400_0010) as *mut u16).write_volatile(deflection);
back.row += 1;
@ -49,8 +49,8 @@ fn main(mut gba: agb::Gba) -> ! {
loop {
vblank.wait_for_vblank();
critical_section::with(|key| {
let mut back = BACK.borrow_ref_mut(key);
free(|key| {
let mut back = BACK.borrow(key).borrow_mut();
back.row = 0;
time += 1;
for (r, a) in back.cosines.iter_mut().enumerate() {

View file

@ -1,94 +0,0 @@
Copyright (c) 2021, TakWolf (https://takwolf.com),
with Reserved Font Name 'Ark Pixel'.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -7,66 +7,72 @@ EXTERN(__RUST_INTERRUPT_HANDLER)
EXTERN(__agbabi_memset)
EXTERN(__agbabi_memcpy)
/* The bios reserves the final 256 bytes of iwram for its exclusive use, so we
* need to avoid writing there */
__bios_reserved_iwram = 256;
MEMORY {
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K - __bios_reserved_iwram
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K
rom (rx) : ORIGIN = 0x08000000, LENGTH = 32M
}
SECTIONS {
. = ORIGIN(rom);
__text_start = ORIGIN(rom);
INPUT (agb.a)
SECTIONS {
. = __text_start;
.entrypoint : {
*(.entrypoint.regular .entrypoint.common);
. = ALIGN(4);
} > rom
.text : {
KEEP(*(.crt0));
*(.crt0 .crt0*);
*(.text .text*);
. = ALIGN(4);
} > rom
__text_end = .;
.rodata : {
*(.rodata .rodata.*);
. = ALIGN(4);
} > rom
__iwram_rom_start = .;
.iwram : {
__iwram_data_start = ABSOLUTE(.);
*(.iwram .iwram.*);
. = ALIGN(4);
*(.text_iwram .text_iwram.*);
. = ALIGN(4);
} > iwram AT>rom
__iwram_data_start = ADDR(.iwram);
__iwram_rom_start = LOADADDR(.iwram);
__iwram_rom_length_halfwords = (SIZEOF(.iwram) + 1) / 2;
__iwram_end = __iwram_data_start + SIZEOF(.iwram);
__iwram_data_end = ABSOLUTE(.);
} > iwram AT>rom
. = __iwram_rom_start + (__iwram_data_end - __iwram_data_start);
__ewram_rom_start = .;
.ewram : {
__ewram_data_start = ABSOLUTE(.);
*(.ewram .ewram.*);
. = ALIGN(4);
*(.data .data.*);
. = ALIGN(4);
__ewram_data_end = ABSOLUTE(.);
} > ewram AT>rom
__ewram_data_start = ADDR(.ewram);
__ewram_rom_start = LOADADDR(.ewram);
__ewram_rom_length_halfwords = (SIZEOF(.ewram) + 1) / 2;
.bss : {
*(.bss .bss.*);
. = ALIGN(4);
= ABSOLUTE(.);
} > ewram
__iwram_end = ABSOLUTE(.);
} > iwram
__ewram_data_end = __ewram_data_start + SIZEOF(.ewram) + SIZEOF(.bss);
__iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start;
__iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2;
__ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start;
__ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2;
.shstrtab : {
*(.shstrtab)

View file

@ -1,76 +1,82 @@
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(__mb_entry)
ENTRY(__start)
EXTERN(__RUST_INTERRUPT_HANDLER)
EXTERN(__agbabi_memset)
EXTERN(__agbabi_memcpy)
/* The bios reserves the final 256 bytes of iwram for its exclusive use, so we
* need to avoid writing there */
__bios_reserved_iwram = 256;
MEMORY {
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K - __bios_reserved_iwram
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K
}
SECTIONS {
. = ORIGIN(ewram);
__text_start = ORIGIN(ewram);
.entrypoint : {
*(.entrypoint.multiboot .entrypoint.common);
. = ALIGN(4);
} > ewram
INPUT (agb.a)
SECTIONS {
. = __text_start;
.text : {
KEEP(*(.crt0));
*(.crt0 .crt0*);
*(.text .text*);
. = ALIGN(4);
} > ewram
__text_end = .;
.rodata : {
*(.rodata .rodata.*);
. = ALIGN(4);
} > ewram
__iwram_rom_start = .;
.iwram : {
__iwram_data_start = ABSOLUTE(.);
*(.iwram .iwram.*);
. = ALIGN(4);
*(.text_iwram .text_iwram.*);
. = ALIGN(4);
} > iwram AT>ewram
__iwram_data_start = ADDR(.iwram);
__iwram_rom_start = LOADADDR(.iwram);
__iwram_rom_length_halfwords = (SIZEOF(.iwram) + 1) / 2;
__iwram_end = __iwram_data_start + SIZEOF(.iwram);
__iwram_data_end = ABSOLUTE(.);
} > iwram AT>ewram
. = __iwram_rom_start + (__iwram_data_end - __iwram_data_start);
__ewram_rom_start = .;
.ewram : {
__ewram_data_start = ABSOLUTE(.);
*(.ewram .ewram.*);
. = ALIGN(4);
*(.data .data.*);
. = ALIGN(4);
} > ewram
__ewram_data_start = ADDR(.ewram);
__ewram_rom_start = LOADADDR(.ewram);
__ewram_rom_length_halfwords = (SIZEOF(.ewram) + 1) / 2;
__ewram_data_end = ABSOLUTE(.);
} > ewram AT>ewram
.bss : {
*(.bss .bss.*);
. = ALIGN(4);
= ABSOLUTE(.);
} > ewram
__iwram_end = ABSOLUTE(.);
} > iwram
__ewram_data_end = __ewram_data_start + SIZEOF(.ewram) + SIZEOF(.bss);
__iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start;
__iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2;
__ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start;
__ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2;
.shstrtab : {
*(.shstrtab)
}
/* debugging sections */
/* debugging sections */
/* Stabs */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -6,6 +6,7 @@
use core::alloc::{Allocator, GlobalAlloc, Layout};
use core::cell::UnsafeCell;
use core::convert::TryInto;
use core::ptr::NonNull;
use super::bump_allocator::{BumpAllocatorInner, StartEnd};
@ -71,6 +72,12 @@ impl BlockAllocator {
f(inner)
}
#[doc(hidden)]
#[cfg(any(test, feature = "testing"))]
pub unsafe fn number_of_blocks(&self) -> u32 {
self.with_inner(|inner| inner.number_of_blocks())
}
pub unsafe fn alloc(&self, layout: Layout) -> Option<NonNull<u8>> {
self.with_inner(|inner| inner.alloc(layout))
}
@ -79,6 +86,12 @@ impl BlockAllocator {
self.with_inner(|inner| inner.dealloc(ptr, layout));
}
pub unsafe fn dealloc_no_normalise(&self, ptr: *mut u8, layout: Layout) {
self.with_inner(|inner| {
inner.dealloc_no_normalise(ptr, layout);
});
}
pub unsafe fn grow(
&self,
ptr: *mut u8,
@ -99,6 +112,20 @@ impl BlockAllocatorInner {
}
}
#[doc(hidden)]
#[cfg(any(test, feature = "testing"))]
pub unsafe fn number_of_blocks(&mut self) -> u32 {
let mut count = 0;
let mut list_ptr = &mut self.state.first_free_block;
while let Some(mut current) = list_ptr {
count += 1;
list_ptr = &mut current.as_mut().next;
}
count
}
/// Requests a brand new block from the inner bump allocator
fn new_block(&mut self, layout: Layout) -> Option<NonNull<u8>> {
let overall_layout = Block::either_layout(layout);
@ -106,25 +133,29 @@ impl BlockAllocatorInner {
}
/// Merges blocks together to create a normalised list
unsafe fn normalise(&mut self, point_to_normalise: *mut Block) {
unsafe fn normalise_block(block_to_normalise: &mut Block) {
if let Some(next_block) = block_to_normalise.next {
let difference = next_block
unsafe fn normalise(&mut self) {
let mut list_ptr = &mut self.state.first_free_block;
while let Some(mut current) = list_ptr {
if let Some(next_elem) = current.as_mut().next {
let difference = next_elem
.as_ptr()
.cast::<u8>()
.offset_from((block_to_normalise as *mut Block).cast::<u8>());
if difference == block_to_normalise.size as isize {
let next = next_block.as_ref();
block_to_normalise.next = next.next;
block_to_normalise.size += next.size;
normalise_block(block_to_normalise);
.offset_from(current.as_ptr().cast::<u8>());
let usize_difference: usize = difference
.try_into()
.expect("distances in alloc'd blocks must be positive");
if usize_difference == current.as_mut().size {
let current = current.as_mut();
let next = next_elem.as_ref();
current.size += next.size;
current.next = next.next;
continue;
}
}
}
normalise_block(&mut *point_to_normalise);
if let Some(mut next_block) = (*point_to_normalise).next {
normalise_block(next_block.as_mut());
list_ptr = &mut current.as_mut().next;
}
}
@ -248,10 +279,8 @@ impl BlockAllocatorInner {
}
pub unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout) {
let point_to_normalise = self.dealloc_no_normalise(ptr, layout);
if let Some(block_to_normalise) = *point_to_normalise {
self.normalise(block_to_normalise.as_ptr());
}
self.dealloc_no_normalise(ptr, layout);
self.normalise();
}
/// Returns a reference to the pointer to the next block
@ -276,16 +305,11 @@ impl BlockAllocatorInner {
}
}
pub unsafe fn dealloc_no_normalise(
&mut self,
ptr: *mut u8,
layout: Layout,
) -> *mut Option<SendNonNull<Block>> {
pub unsafe fn dealloc_no_normalise(&mut self, ptr: *mut u8, layout: Layout) {
let new_layout = Block::either_layout(layout).pad_to_align();
// note that this is a reference to a pointer
let mut list_ptr = &mut self.state.first_free_block;
let mut list_ptr_prev: *mut Option<SendNonNull<Block>> = list_ptr;
// This searches the free list until it finds a block further along
// than the block that is being freed. The newly freed block is then
@ -303,7 +327,6 @@ impl BlockAllocatorInner {
*list_ptr = NonNull::new(ptr.cast()).map(SendNonNull);
break;
}
list_ptr_prev = list_ptr;
list_ptr = &mut current_block.as_mut().next;
}
None => {
@ -318,8 +341,6 @@ impl BlockAllocatorInner {
}
}
}
list_ptr_prev
}
}

View file

@ -1,7 +1,10 @@
use core::alloc::Layout;
use core::alloc::{GlobalAlloc, Layout};
use core::cell::RefCell;
use core::ptr::NonNull;
use super::SendNonNull;
use crate::interrupt::free;
use bare_metal::Mutex;
pub(crate) struct StartEnd {
pub start: fn() -> usize,
@ -13,6 +16,10 @@ pub(crate) struct BumpAllocatorInner {
start_end: StartEnd,
}
pub(crate) struct BumpAllocator {
inner: Mutex<RefCell<BumpAllocatorInner>>,
}
impl BumpAllocatorInner {
pub const fn new(start_end: StartEnd) -> Self {
Self {
@ -51,3 +58,20 @@ impl BumpAllocatorInner {
NonNull::new(resulting_ptr as *mut _)
}
}
impl BumpAllocator {
fn alloc_safe(&self, layout: Layout) -> Option<NonNull<u8>> {
free(|key| self.inner.borrow(key).borrow_mut().alloc(layout))
}
}
unsafe impl GlobalAlloc for BumpAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
match self.alloc_safe(layout) {
None => core::ptr::null_mut(),
Some(p) => p.as_ptr(),
}
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
}

View file

@ -1,3 +1,4 @@
use core::alloc::{Allocator, Layout};
use core::ops::{Deref, DerefMut};
use core::ptr::NonNull;
@ -13,7 +14,7 @@ unsafe impl<T> Send for SendNonNull<T> {}
impl<T> Clone for SendNonNull<T> {
fn clone(&self) -> Self {
*self
SendNonNull(self.0)
}
}
impl<T> Copy for SendNonNull<T> {}
@ -44,27 +45,21 @@ static GLOBAL_ALLOC: BlockAllocator = unsafe {
macro_rules! impl_zst_allocator {
($name_of_struct: ty, $name_of_static: ident) => {
unsafe impl core::alloc::Allocator for $name_of_struct {
fn allocate(
&self,
layout: core::alloc::Layout,
) -> Result<core::ptr::NonNull<[u8]>, core::alloc::AllocError> {
unsafe impl Allocator for $name_of_struct {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, core::alloc::AllocError> {
$name_of_static.allocate(layout)
}
unsafe fn deallocate(&self, ptr: core::ptr::NonNull<u8>, layout: core::alloc::Layout) {
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
$name_of_static.deallocate(ptr, layout)
}
}
};
}
pub(crate) use impl_zst_allocator;
/// This is the allocator for the External Working Ram.
///
/// This is currently equivalent to the Global Allocator (where things are allocated if no allocator is provided).
/// This implements the allocator trait, so is meant to be used in specifying where certain structures should be
/// This is the allocator for the External Working Ram. This is currently
/// equivalent to the Global Allocator (where things are allocated if no allocator is provided). This implements the allocator trait, so
/// is meant to be used in specifying where certain structures should be
/// allocated.
///
/// ```rust,no_run
@ -120,24 +115,29 @@ static __IWRAM_ALLOC: BlockAllocator = unsafe {
})
};
#[cfg(any(test, feature = "testing"))]
pub(crate) unsafe fn number_of_blocks() -> u32 {
GLOBAL_ALLOC.number_of_blocks()
}
fn iwram_data_end() -> usize {
extern "C" {
static __iwram_end: u8;
static __iwram_end: usize;
}
// Symbols defined in the linker have an address *but no data or value*.
// As strange as this looks, they are only useful to take the address of.
core::ptr::addr_of!(__iwram_end) as usize
// TODO: This seems completely wrong, but without the &, rust generates
// a double dereference :/. Maybe a bug in nightly?
(unsafe { &__iwram_end }) as *const _ as usize
}
fn data_end() -> usize {
extern "C" {
static __ewram_data_end: u8;
static __ewram_data_end: usize;
}
// Symbols defined in the linker have an address *but no data or value*.
// As strange as this looks, they are only useful to take the address of.
core::ptr::addr_of!(__ewram_data_end) as usize
// TODO: This seems completely wrong, but without the &, rust generates
// a double dereference :/. Maybe a bug in nightly?
(unsafe { &__ewram_data_end }) as *const _ as usize
}
#[cfg(test)]

View file

@ -11,6 +11,8 @@
===============================================================================
*/
.include "src/agbabi/macros.inc"
.arm
.section .iwram.__aeabi_memcpy, "ax", %progbits
@ -30,14 +32,14 @@ __aeabi_memcpy:
joaobapt_test r3
// Copy byte head to align
ldrbmi r3, [r1], #1
strbmi r3, [r0], #1
ldrmib r3, [r1], #1
strmib r3, [r0], #1
submi r2, r2, #1
// r0, r1 are now half aligned
// Copy half head to align
ldrhcs r3, [r1], #2
strhcs r3, [r0], #2
ldrcsh r3, [r1], #2
strcsh r3, [r0], #2
subcs r2, r2, #2
// r0, r1 are now word aligned
@ -49,13 +51,13 @@ __aeabi_memcpy4:
blt .Lcopy_words
// Word aligned, 32-byte copy
push {{r4-r10}}
push {r4-r10}
.Lloop_32:
subs r2, r2, #32
ldmiage r1!, {{r3-r10}}
stmiage r0!, {{r3-r10}}
ldmgeia r1!, {r3-r10}
stmgeia r0!, {r3-r10}
bgt .Lloop_32
pop {{r4-r10}}
pop {r4-r10}
bxeq lr
// < 32 bytes remaining to be copied
@ -75,40 +77,40 @@ __aeabi_memcpy4:
// This test still works when r2 is negative
joaobapt_test r2
// Copy half
ldrhcs r3, [r1], #2
strhcs r3, [r0], #2
ldrcsh r3, [r1], #2
strcsh r3, [r0], #2
// Copy byte
ldrbmi r3, [r1]
strbmi r3, [r0]
ldrmib r3, [r1]
strmib r3, [r0]
bx lr
.Lcopy_halves:
// Copy byte head to align
tst r0, #1
ldrbne r3, [r1], #1
strbne r3, [r0], #1
ldrneb r3, [r1], #1
strneb r3, [r0], #1
subne r2, r2, #1
// r0, r1 are now half aligned
.global __agbabi_memcpy2
__agbabi_memcpy2:
subs r2, r2, #2
ldrhge r3, [r1], #2
strhge r3, [r0], #2
ldrgeh r3, [r1], #2
strgeh r3, [r0], #2
bgt __agbabi_memcpy2
bxeq lr
// Copy byte tail
adds r2, r2, #1
ldrbeq r3, [r1]
strbeq r3, [r0]
ldreqb r3, [r1]
streqb r3, [r0]
bx lr
.global __agbabi_memcpy1
__agbabi_memcpy1:
subs r2, r2, #1
ldrbge r3, [r1], #1
strbge r3, [r0], #1
ldrgeb r3, [r1], #1
strgeb r3, [r0], #1
bgt __agbabi_memcpy1
bx lr
@ -116,7 +118,7 @@ __agbabi_memcpy1:
.align 2
.global memcpy
memcpy:
push {{r0, lr}}
push {r0, lr}
bl __aeabi_memcpy
pop {{r0, lr}}
pop {r0, lr}
bx lr

View file

@ -40,9 +40,9 @@ __aeabi_memset:
// JoaoBapt carry & sign bit test
movs r1, r1, lsl #31
// Set byte and half
strbmi r2, [r0], #1
strbcs r2, [r0], #1
strbcs r2, [r0]
strmib r2, [r0], #1
strcsb r2, [r0], #1
strcsb r2, [r0]
bx lr
.LskipShortHead:
@ -50,9 +50,9 @@ __aeabi_memset:
// JoaoBapt carry & sign bit test
movs r3, r3, lsl #31
// Set half and byte head
strbmi r2, [r0], #1
strmib r2, [r0], #1
submi r1, r1, #1
strhcs r2, [r0], #2
strcsh r2, [r0], #2
subcs r1, r1, #2
b __agbabi_wordset4
@ -79,7 +79,7 @@ __agbabi_wordset4:
beq .Lskip32
lsl r3, r12, #5
sub r1, r1, r3
push {{r4-r9}}
push {r4-r9}
mov r3, r2
mov r4, r2
mov r5, r2
@ -88,10 +88,10 @@ __agbabi_wordset4:
mov r8, r2
mov r9, r2
.LsetWords8:
stmia r0!, {{r2-r9}}
stmia r0!, {r2-r9}
subs r12, r12, #1
bne .LsetWords8
pop {{r4-r9}}
pop {r4-r9}
.Lskip32:
// Set words
@ -104,8 +104,8 @@ __agbabi_wordset4:
// Set half and byte tail
// JoaoBapt carry & sign bit test
movs r3, r1, lsl #31
strhcs r2, [r0], #2
strbmi r2, [r0]
strcsh r2, [r0], #2
strmib r2, [r0]
bx lr
.section .iwram.memset, "ax", %progbits
@ -115,7 +115,7 @@ memset:
mov r3, r1
mov r1, r2
mov r2, r3
push {{r0, lr}}
push {r0, lr}
bl __aeabi_memset
pop {{r0, lr}}
pop {r0, lr}
bx lr

View file

@ -1,9 +1,3 @@
use core::arch::global_asm;
global_asm!(include_str!("macros.inc"));
global_asm!(include_str!("memcpy.s"));
global_asm!(include_str!("memset.s"));
#[cfg(test)]
mod test {
mod memset {
@ -154,7 +148,7 @@ mod test {
#[test_case]
fn test_all_of_memcpy(_gba: &mut Gba) {
let mut input = [0u8; 100];
let mut input = vec![0u8; 100];
let mut output = vec![0u8; 100];
for size in 0..80 {

View file

@ -4,14 +4,14 @@
.align 2
.global \functionName
.type \functionName, %function
@ .func \functionName
.func \functionName
\functionName:
.endm
.macro agb_arm_end functionName:req
.pool
.size \functionName,.-\functionName
@ .endfunc
.endfunc
.endm
.macro agb_thumb_func functionName:req
@ -20,12 +20,12 @@
.align 1
.global \functionName
.type \functionName, %function
@ .func \functionName
.func \functionName
\functionName:
.endm
.macro agb_thumb_end functionName:req
.pool
.size \functionName,.-\functionName
@ .endfunc
.endfunc
.endm

View file

@ -1,137 +0,0 @@
use core::{arch::asm, ops::Index};
use alloc::vec::Vec;
// only works for code compiled as THUMB
#[repr(C)]
#[derive(Clone, Default, Debug)]
struct Context {
registers: [u32; 11],
}
pub struct Frames {
frames: Vec<u32>,
}
#[allow(unused)]
enum Register {
R0,
R1,
R2,
R3,
R4,
R5,
R6,
FP,
SP,
LR,
PC,
}
impl Index<Register> for Context {
type Output = u32;
fn index(&self, index: Register) -> &Self::Output {
&self.registers[index as usize]
}
}
#[inline(never)]
pub(crate) fn unwind_exception() -> Frames {
let mut context = Context::default();
unsafe {
let context_ptr = (&mut context) as *mut _;
asm!(
"
str r0, [r0, #0x00]
str r1, [r0, #0x04]
str r2, [r0, #0x08]
str r3, [r0, #0x0C]
str r4, [r0, #0x10]
str r5, [r0, #0x14]
str r6, [r0, #0x18]
str r7, [r0, #0x1C]
mov r7, sp
str r7, [r0, #0x20]
mov r7, lr
str r7, [r0, #0x24]
mov r7, pc
str r7, [r0, #0x28]
ldr r7, [r0, #0x1C]
",
in("r0") context_ptr
);
}
let mut frame_pointer = context[Register::FP];
let mut frames = Vec::new();
loop {
let sp = unsafe { *(frame_pointer as *const u32) };
let lr = unsafe { *((frame_pointer as *const u32).add(1)) };
if sp == 0 {
break;
}
// need to subtract 2 here since the link register points to the _next_ instruction
// to execute, not the one that is being branched from which is the one we care about
// in the stack trace.
frames.push(lr - 2);
frame_pointer = sp;
}
Frames { frames }
}
impl core::fmt::Display for Frames {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for frame in &self.frames {
if frame & 0xFFFF_0000 == 0x0800_0000 {
let frame = *frame as u16; // intentionally truncate
let frame_encoded = gwilym_encoding::encode_16(frame);
let frame_str = unsafe { core::str::from_utf8_unchecked(&frame_encoded) };
write!(f, "{frame_str}")?;
} else {
let frame_encoded = gwilym_encoding::encode_32(*frame);
let frame_str = unsafe { core::str::from_utf8_unchecked(&frame_encoded) };
write!(f, "{frame_str}")?;
}
}
write!(f, "v1")
}
}
mod gwilym_encoding {
static ALPHABET: &[u8] = b"0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
pub fn encode_16(input: u16) -> [u8; 3] {
let input = input as usize;
[
ALPHABET[input >> (16 - 5)],
ALPHABET[(input >> (16 - 10)) & 0b11111],
ALPHABET[input & 0b111111],
]
}
pub fn encode_32(input: u32) -> [u8; 6] {
let input = input as usize;
let output_lower_16 = encode_16(input as u16);
let input_upper_16 = input >> 16;
[
ALPHABET[(input_upper_16 >> (16 - 5)) | (1 << 5)],
ALPHABET[(input_upper_16 >> (16 - 10)) & 0b11111],
ALPHABET[input_upper_16 & 0b111111],
output_lower_16[0],
output_lower_16[1],
output_lower_16[2],
]
}
}

View file

@ -1,13 +1,31 @@
.arm
.section .entrypoint.regular, "ax", %progbits
.global __start
.section .crt0
.align
.global __start
__start:
b .Initialise
@ Filled in by gbafix
.space 188
.fill 188, 1, 0
@ multiboot launch point
b .Initialise_mb
.byte 0 @ boot mode, BIOS overwrites this value
.byte 0 @ slave ID number
.fill 26, 1, 0 @ unused?
.word 0 @ joybus entrypoint
.Initialise_mb:
swi 0x00250000
@ Set interrupt handler
ldr r0, =InterruptHandler
ldr r1, =0x03007FFC
str r0, [r1]
b .CommonInit
.Initialise:
@ Set interrupt handler
@ -25,40 +43,8 @@ __start:
@ r2: length + size information
@
@ see: https://mgba-emu.github.io/gbatek/#swi-0bh-gbands7nds9dsi7dsi9---cpuset
ldr r0, =CommonInit
bx r0
.arm
.section .entrypoint.multiboot, "ax", %progbits
.align
b __mb_entry
@ Filled in by gbafix
.space 188
@ multiboot launch point
.global __mb_entry
__mb_entry:
b .Initialise_mb
.byte 0 @ boot mode, BIOS overwrites this value
.byte 0 @ slave ID number
.space 26 @ unused?
.Initialise_mb:
swi 0x00250000
@ Set interrupt handler
ldr r0, =InterruptHandler
ldr r1, =0x03007FFC
str r0, [r1]
ldr r0, =CommonInit
bx r0
.arm
.section .entrypoint.common, "ax", %progbits
.align
.global CommonInit
CommonInit:
.CommonInit:
@ set the waitstate control register to the normal value used in manufactured cartridges
ldr r0, =0x04000204 @ address for waitstate control register
ldr r1, =0x4317 @ WS0/ROM=3,1 clks; SRAM=8 clks; WS2/EEPROM: 8,8 clks; prefetch enabled
@ -80,12 +66,8 @@ CommonInit:
ldr r0, =0
mov r1, r0
@ ensure the frame pointer is zero so that stack traces are guaranteed to terminate
mov r7, r0
@ load main and branch
ldr r2, =main
mov lr, pc
bx r2
@ loop if we end up here

View file

@ -81,7 +81,10 @@
//! # }
//! ```
use core::ops::{Mul, MulAssign};
use core::{
convert::TryFrom,
ops::{Mul, MulAssign},
};
use agb_fixnum::{Num, Vector2D};
@ -90,20 +93,13 @@ type AffineMatrixElement = Num<i32, 8>;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
/// An affine matrix stored in a way that is efficient for the GBA to perform
/// operations on. This implements multiplication.
///
/// ```txt
/// a b x
/// c d y
/// 0 0 0
/// ```
#[allow(missing_docs)]
pub struct AffineMatrix {
pub a: AffineMatrixElement,
pub b: AffineMatrixElement,
pub c: AffineMatrixElement,
pub d: AffineMatrixElement,
pub x: AffineMatrixElement,
pub y: AffineMatrixElement,
a: AffineMatrixElement,
b: AffineMatrixElement,
c: AffineMatrixElement,
d: AffineMatrixElement,
x: AffineMatrixElement,
y: AffineMatrixElement,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -302,15 +298,15 @@ impl AffineMatrixBackground {
/// # }
/// ```
pub fn from_scale_rotation_position(
transform_origin: impl Into<Vector2D<Num<i32, 8>>>,
scale: impl Into<Vector2D<Num<i32, 8>>>,
transform_origin: Vector2D<Num<i32, 8>>,
scale: Vector2D<Num<i32, 8>>,
rotation: Num<i32, 16>,
position: impl Into<Vector2D<i16>>,
position: Vector2D<Num<i32, 8>>,
) -> Self {
crate::syscall::bg_affine_matrix(
transform_origin.into(),
position.into(),
scale.into().try_change_base().unwrap(),
transform_origin,
position.try_change_base::<i16, 8>().unwrap().floor(),
scale.try_change_base().unwrap(),
rotation.rem_euclid(1.into()).try_change_base().unwrap(),
)
}
@ -325,17 +321,11 @@ impl From<AffineMatrixBackground> for AffineMatrix {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(C, packed(4))]
/// An affine matrix that can be used in affine objects
///
/// ```txt
/// a b
/// c d
/// ```
#[allow(missing_docs)]
pub struct AffineMatrixObject {
pub a: Num<i16, 8>,
pub b: Num<i16, 8>,
pub c: Num<i16, 8>,
pub d: Num<i16, 8>,
a: Num<i16, 8>,
b: Num<i16, 8>,
c: Num<i16, 8>,
d: Num<i16, 8>,
}
impl Default for AffineMatrixObject {

View file

@ -4,7 +4,7 @@ use super::{
set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, HEIGHT, WIDTH,
};
use core::marker::PhantomData;
use core::{convert::TryInto, marker::PhantomData};
const BITMAP_MODE_3: MemoryMapped2DArray<u16, { WIDTH as usize }, { HEIGHT as usize }> =
unsafe { MemoryMapped2DArray::new(0x600_0000) };
@ -30,19 +30,4 @@ impl Bitmap3<'_> {
let y = y.try_into().unwrap();
BITMAP_MODE_3.set(x, y, colour);
}
#[must_use]
pub fn read_point(&self, x: i32, y: i32) -> u16 {
let x = x.try_into().unwrap();
let y = y.try_into().unwrap();
BITMAP_MODE_3.get(x, y)
}
pub fn clear(&mut self, colour: u16) {
for y in 0..(HEIGHT as usize) {
for x in 0..(WIDTH as usize) {
BITMAP_MODE_3.set(x, y, colour);
}
}
}
}

View file

@ -54,9 +54,9 @@ impl Bitmap4<'_> {
let c = addr.get(x_in_screen, y_in_screen);
if x & 0b1 != 0 {
addr.set(x_in_screen, y_in_screen, c & 0xff | u16::from(colour) << 8);
addr.set(x_in_screen, y_in_screen, c | u16::from(colour) << 8);
} else {
addr.set(x_in_screen, y_in_screen, c & 0xff00 | u16::from(colour));
addr.set(x_in_screen, y_in_screen, c | u16::from(colour));
}
}
@ -88,64 +88,4 @@ impl Bitmap4<'_> {
let swapped = display ^ GraphicsSettings::PAGE_SELECT.bits();
DISPLAY_CONTROL.set(swapped);
}
/// Draws 2-pixel wide point on the non-current page at (x, y) coordinates with colour
/// index whose colour is specified in the background palette. Panics if (x,
/// y) is out of the bounds of the screen.
pub fn draw_wide_point(&mut self, x: i32, y: i32, colour: u8) {
let display = DISPLAY_CONTROL.get();
// get other page
let page = if display & GraphicsSettings::PAGE_SELECT.bits() != 0 {
Page::Front
} else {
Page::Back
};
self.draw_wide_point_page(x, y, colour, page);
}
/// Draws 2-pixel wide point on specified page at (x, y) coordinates with colour index
/// whose colour is specified in the background palette. Panics if (x, y) is
/// out of the bounds of the screen.
pub fn draw_wide_point_page(&mut self, x: i32, y: i32, colour: u8, page: Page) {
let addr = match page {
Page::Front => BITMAP_PAGE_FRONT_MODE_4,
Page::Back => BITMAP_PAGE_BACK_MODE_4,
};
let x_in_screen = (x / 2) as usize;
let y_in_screen = y as usize;
let c = u16::from(colour);
addr.set(x_in_screen, y_in_screen, c << 8 | c);
}
/// Fills specified page with color.
pub fn clear_page(&mut self, colour: u8, page: Page) {
let addr = match page {
Page::Front => BITMAP_PAGE_FRONT_MODE_4,
Page::Back => BITMAP_PAGE_BACK_MODE_4,
};
let c = u16::from(colour);
for x in 0..(WIDTH / 2) as usize {
for y in 0..(HEIGHT as usize) {
addr.set(x, y, c << 8 | c);
}
}
}
/// Fills non-current page with color.
pub fn clear(&mut self, colour: u8) {
let display = DISPLAY_CONTROL.get();
// get other page
let page = if display & GraphicsSettings::PAGE_SELECT.bits() != 0 {
Page::Front
} else {
Page::Back
};
self.clear_page(colour, page);
}
}

View file

@ -60,7 +60,7 @@ pub struct BlendLayer<'blend, 'gba> {
layer: Layer,
}
impl BlendLayer<'_, '_> {
impl<'gba> BlendLayer<'_, 'gba> {
/// Set whether a background is enabled for blending on this layer.
pub fn set_background_enable(&mut self, background: BackgroundID, enable: bool) -> &mut Self {
self.blend

View file

@ -1,24 +1,25 @@
use super::tiled::{RegularMap, TiledMap, VRamManager};
use super::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager};
crate::include_background_gfx!(crate, agb_logo, test_logo => deduplicate "gfx/test_logo.png");
crate::include_background_gfx!(crate, agb_logo_basic, test_logo => deduplicate "gfx/test_logo_basic.png");
crate::include_background_gfx!(crate, agb_logo, test_logo => "gfx/test_logo.png");
pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) {
vram.set_background_palettes(agb_logo::PALETTES);
map.fill_with(vram, &agb_logo::test_logo);
let background_tilemap = TileSet::new(agb_logo::test_logo.tiles, TileFormat::FourBpp);
for y in 0..20 {
for x in 0..30 {
let tile_id = y * 30 + x;
let palette_entry = agb_logo::test_logo.palette_assignments[tile_id as usize];
let tile_setting = TileSetting::new(tile_id, false, false, palette_entry);
map.set_tile(vram, (x, y).into(), &background_tilemap, tile_setting);
}
}
map.commit(vram);
map.set_visible(true);
}
pub fn display_logo_basic(map: &mut RegularMap, vram: &mut VRamManager) {
vram.set_background_palettes(agb_logo_basic::PALETTES);
map.fill_with(vram, &agb_logo_basic::test_logo);
map.commit(vram);
map.set_visible(true);
map.show();
}
#[cfg(test)]
@ -34,7 +35,7 @@ mod tests {
let mut map = gfx.background(
Priority::P0,
RegularBackgroundSize::Background32x32,
agb_logo::test_logo.tiles.format(),
TileFormat::FourBpp,
);
display_logo(&mut map, &mut vram);

View file

@ -3,63 +3,38 @@ use core::fmt::{Error, Write};
use crate::fixnum::Vector2D;
use crate::hash_map::HashMap;
use super::tiled::{DynamicTile, RegularMap, VRamManager};
use super::tiled::{DynamicTile, RegularMap, TileSetting, VRamManager};
/// The text renderer renders a variable width fixed size
/// bitmap font using dynamic tiles as a rendering surface.
/// Does not support any unicode features.
/// For usage see the `text_render.rs` example
pub struct FontLetter {
pub(crate) character: char,
pub(crate) width: u8,
pub(crate) height: u8,
pub(crate) data: &'static [u8],
pub(crate) xmin: i8,
pub(crate) ymin: i8,
pub(crate) advance_width: u8,
kerning_amounts: &'static [(char, i8)],
width: u8,
height: u8,
data: &'static [u8],
xmin: i8,
ymin: i8,
advance_width: u8,
}
impl FontLetter {
#[must_use]
#[allow(clippy::too_many_arguments)] // only used in macro
pub const fn new(
character: char,
width: u8,
height: u8,
data: &'static [u8],
xmin: i8,
ymin: i8,
advance_width: u8,
kerning_amounts: &'static [(char, i8)],
) -> Self {
Self {
character,
width,
height,
data,
xmin,
ymin,
advance_width,
kerning_amounts,
}
}
pub(crate) const fn bit_absolute(&self, x: usize, y: usize) -> bool {
let position = x + y * self.width as usize;
let byte = self.data[position / 8];
let bit = position % 8;
((byte >> bit) & 1) != 0
}
pub(crate) fn kerning_amount(&self, previous_char: char) -> i32 {
if let Ok(index) = self
.kerning_amounts
.binary_search_by_key(&previous_char, |kerning_data| kerning_data.0)
{
self.kerning_amounts[index].1 as i32
} else {
0
}
}
}
@ -80,36 +55,20 @@ impl Font {
}
}
pub(crate) fn letter(&self, letter: char) -> &'static FontLetter {
let letter = self
.letters
.binary_search_by_key(&letter, |letter| letter.character);
match letter {
Ok(index) => &self.letters[index],
Err(_) => &self.letters[0],
}
}
pub(crate) fn ascent(&self) -> i32 {
self.ascent
}
pub(crate) fn line_height(&self) -> i32 {
self.line_height
fn letter(&self, letter: char) -> &'static FontLetter {
&self.letters[letter as usize]
}
}
impl Font {
#[must_use]
/// Create renderer starting at the given tile co-ordinates.
pub fn render_text(&self, tile_pos: impl Into<Vector2D<u16>>) -> TextRenderer<'_> {
pub fn render_text(&self, tile_pos: Vector2D<u16>) -> TextRenderer<'_> {
TextRenderer {
current_x_pos: 0,
current_y_pos: 0,
previous_character: None,
font: self,
tile_pos: tile_pos.into(),
tile_pos,
tiles: Default::default(),
}
}
@ -119,7 +78,6 @@ impl Font {
pub struct TextRenderer<'a> {
current_x_pos: i32,
current_y_pos: i32,
previous_character: Option<char>,
font: &'a Font,
tile_pos: Vector2D<u16>,
tiles: HashMap<(i32, i32), DynamicTile<'a>>,
@ -135,7 +93,7 @@ pub struct TextWriter<'a, 'b> {
bg: &'a mut RegularMap,
}
impl Write for TextWriter<'_, '_> {
impl<'a, 'b> Write for TextWriter<'a, 'b> {
fn write_str(&mut self, text: &str) -> Result<(), Error> {
for c in text.chars() {
self.text_renderer.write_char(
@ -150,7 +108,7 @@ impl Write for TextWriter<'_, '_> {
}
}
impl TextWriter<'_, '_> {
impl<'a, 'b> TextWriter<'a, 'b> {
pub fn commit(self) {
self.text_renderer.commit(self.bg, self.vram_manager);
}
@ -255,9 +213,9 @@ impl<'a, 'b> TextRenderer<'b> {
for ((x, y), tile) in self.tiles.iter() {
bg.set_tile(
vram_manager,
(self.tile_pos.x + *x as u16, self.tile_pos.y + *y as u16),
(self.tile_pos.x + *x as u16, self.tile_pos.y + *y as u16).into(),
&tile.tile_set(),
tile.tile_setting(),
TileSetting::from_raw(tile.tile_index()),
);
}
}
@ -275,12 +233,6 @@ impl<'a, 'b> TextRenderer<'b> {
self.current_x_pos = 0;
} else {
let letter = self.font.letter(c);
if let Some(previous_character) = self.previous_character {
self.current_x_pos += letter.kerning_amount(previous_character);
}
self.previous_character = Some(c);
self.render_letter(letter, vram_manager, foreground_colour, background_colour);
self.current_x_pos += i32::from(letter.advance_width);
}
@ -302,7 +254,7 @@ impl<'a, 'b> TextRenderer<'b> {
mod tests {
use super::*;
use crate::display::tiled::{TileFormat, TiledMap};
static FONT: Font = crate::include_font!("examples/font/yoster.ttf", 12);
const FONT: Font = crate::include_font!("examples/font/yoster.ttf", 12);
#[test_case]
fn font_display(gba: &mut crate::Gba) {
@ -325,16 +277,16 @@ mod tests {
for x in 0..30u16 {
bg.set_tile(
&mut vram,
(x, y),
(x, y).into(),
&background_tile.tile_set(),
background_tile.tile_setting(),
TileSetting::from_raw(background_tile.tile_index()),
);
}
}
vram.remove_dynamic_tile(background_tile);
let mut renderer = FONT.render_text((0u16, 3u16));
let mut renderer = FONT.render_text((0u16, 3u16).into());
// Test twice to ensure that clearing works
for _ in 0..2 {
@ -347,14 +299,14 @@ mod tests {
writeln!(&mut writer, "World!").unwrap();
writer.commit();
bg.commit(&mut vram);
bg.set_visible(true);
bg.show();
// Test writing with same renderer after showing background
let mut writer = renderer.writer(1, 2, &mut bg, &mut vram);
writeln!(&mut writer, "This is a font rendering example").unwrap();
writer.commit();
bg.commit(&mut vram);
bg.set_visible(true);
bg.show();
crate::test_runner::assert_image_output("examples/font/font-test-output.png");
renderer.clear(&mut vram);

View file

@ -1,8 +1,7 @@
use crate::memory_mapped::MemoryMapped;
use bilge::prelude::*;
use bitflags::bitflags;
use modular_bitfield::BitfieldSpecifier;
use video::Video;
use self::{
@ -151,6 +150,7 @@ unsafe fn set_graphics_settings(settings: GraphicsSettings) {
DISPLAY_CONTROL.set(s);
}
#[allow(non_snake_case)]
/// Waits until vblank using a busy wait loop, this should almost never be used.
/// I only say almost because whilst I don't believe there to be a reason to use
/// this I can't rule it out.
@ -159,14 +159,8 @@ pub fn busy_wait_for_vblank() {
while VCOUNT.get() < 160 {}
}
/// The priority of a background layer or object. A higher priority should be
/// thought of as rendering first, and so is behind that of a lower priority.
/// For an equal priority background layer and object, the background has a
/// higher priority and therefore is behind the object.
#[bitsize(2)]
#[derive(FromBits, PartialEq, Eq, Clone, Copy, Debug, Default)]
#[derive(BitfieldSpecifier, Clone, Copy, Debug)]
pub enum Priority {
#[default]
P0 = 0,
P1 = 1,
P2 = 2,

View file

@ -9,7 +9,6 @@
//! harder to integrate into your games depending on how they are architectured.
mod affine;
mod font;
mod managed;
mod sprites;
mod unmanaged;
@ -21,11 +20,7 @@ pub use sprites::{
pub use affine::AffineMatrixInstance;
pub use managed::{OamManaged, Object};
pub use unmanaged::{
AffineMode, GraphicsMode, OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged,
};
pub use font::{ChangeColour, ObjectTextRender, TextAlignment};
pub use unmanaged::{AffineMode, OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged};
use super::DISPLAY_CONTROL;

View file

@ -14,12 +14,10 @@ struct AffineMatrixData {
#[derive(Debug, Clone)]
pub(crate) struct AffineMatrixVram(Rc<AffineMatrixData>);
/// An affine matrix that can be used on objects.
///
/// It is just in time copied to vram, so you can have as many as you like
/// of these but you can only use up to 16 in one frame. They are reference
/// counted (Cloning is cheap) and immutable, if you want to change a matrix
/// you must make a new one and set it
/// An affine matrix that can be used on objects. It is just in time copied to
/// vram, so you can have as many as you like of these but you can only use up
/// to 16 in one frame. They are reference counted (Cloning is cheap) and
/// immutable, if you want to change a matrix you must make a new one and set it
/// on all your objects.
#[derive(Debug, Clone)]
pub struct AffineMatrixInstance {

View file

@ -1,511 +0,0 @@
use core::fmt::{Display, Write};
use agb_fixnum::{Num, Vector2D};
use alloc::{collections::VecDeque, vec::Vec};
use crate::display::Font;
use self::{
preprocess::{Line, Preprocessed, PreprocessedElement},
renderer::{Configuration, WordRender},
};
use super::{OamIterator, ObjectUnmanaged, PaletteVram, Size, SpriteVram};
mod preprocess;
mod renderer;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub(crate) enum WhiteSpace {
NewLine,
Space,
}
impl WhiteSpace {
pub(crate) fn from_char(c: char) -> Self {
match c {
' ' => WhiteSpace::Space,
'\n' => WhiteSpace::NewLine,
_ => panic!("char not supported whitespace"),
}
}
}
struct BufferedRender<'font> {
char_render: WordRender,
preprocessor: Preprocessed,
buffered_chars: VecDeque<char>,
letters: Letters,
font: &'font Font,
}
#[derive(Debug, Default)]
struct Letters {
letters: VecDeque<SpriteVram>,
number_of_groups: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
/// The text alignment of the layout
pub enum TextAlignment {
#[default]
/// Left aligned, the left edge of the text lines up
Left,
/// Right aligned, the right edge of the text lines up
Right,
/// Center aligned, the center of the text lines up
Center,
/// Justified, both the left and right edges line up with space width adapted to make it so.
Justify,
}
struct TextAlignmentSettings {
space_width: Num<i32, 10>,
start_x: i32,
}
impl TextAlignment {
fn settings(self, line: &Line, minimum_space_width: i32, width: i32) -> TextAlignmentSettings {
match self {
TextAlignment::Left => TextAlignmentSettings {
space_width: minimum_space_width.into(),
start_x: 0,
},
TextAlignment::Right => TextAlignmentSettings {
space_width: minimum_space_width.into(),
start_x: width - line.width(),
},
TextAlignment::Center => TextAlignmentSettings {
space_width: minimum_space_width.into(),
start_x: (width - line.width()) / 2,
},
TextAlignment::Justify => {
let space_width = if line.number_of_spaces() != 0 {
Num::new(
width - line.width() + line.number_of_spaces() as i32 * minimum_space_width,
) / line.number_of_spaces() as i32
} else {
minimum_space_width.into()
};
TextAlignmentSettings {
space_width,
start_x: 0,
}
}
}
}
}
impl<'font> BufferedRender<'font> {
#[must_use]
fn new(font: &'font Font, sprite_size: Size, palette: PaletteVram) -> Self {
let config = Configuration::new(sprite_size, palette);
BufferedRender {
char_render: WordRender::new(config),
preprocessor: Preprocessed::new(),
buffered_chars: VecDeque::new(),
letters: Default::default(),
font,
}
}
}
fn is_private_use(c: char) -> bool {
('\u{E000}'..'\u{F8FF}').contains(&c)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// Changes the palette to use to draw characters.
/// ```rust,no_run
/// # #![no_std]
/// # #![no_main]
/// use agb::display::object::{ObjectTextRender, PaletteVram, ChangeColour, Size};
/// use agb::display::palette16::Palette16;
/// use agb::display::Font;
///
/// use core::fmt::Write;
///
/// static EXAMPLE_FONT: Font = agb::include_font!("examples/font/yoster.ttf", 12);
///
/// # fn foo() {
/// let mut palette = [0x0; 16];
/// palette[1] = 0xFF_FF;
/// palette[2] = 0x00_FF;
/// let palette = Palette16::new(palette);
/// let palette = PaletteVram::new(&palette).unwrap();
/// let mut writer = ObjectTextRender::new(&EXAMPLE_FONT, Size::S16x16, palette);
///
/// let _ = writeln!(writer, "Hello, {}World{}!", ChangeColour::new(2), ChangeColour::new(1));
/// # }
/// ```
pub struct ChangeColour(u8);
impl ChangeColour {
#[must_use]
/// Creates the colour changer. Colour is a palette index and must be in the range 0..16.
pub fn new(colour: usize) -> Self {
assert!(colour < 16, "paletted colour must be valid (0..=15)");
Self(colour as u8)
}
fn try_from_char(c: char) -> Option<Self> {
let c = c as u32 as usize;
if (0xE000..0xE000 + 16).contains(&c) {
Some(ChangeColour::new(c - 0xE000))
} else {
None
}
}
fn to_char(self) -> char {
char::from_u32(self.0 as u32 + 0xE000).unwrap()
}
}
impl Display for ChangeColour {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_char(self.to_char())
}
}
impl BufferedRender<'_> {
fn input_character(&mut self, character: char) {
if !is_private_use(character) {
self.preprocessor
.add_character(self.font, character, self.char_render.sprite_width());
}
self.buffered_chars.push_back(character);
}
fn process(&mut self) {
let Some(c) = self.buffered_chars.pop_front() else {
return;
};
match c {
' ' | '\n' => {
if let Some(group) = self.char_render.finalise_letter() {
self.letters.letters.push_back(group);
self.letters.number_of_groups += 1;
}
self.letters.number_of_groups += 1;
}
letter => {
if let Some(group) = self.char_render.render_char(self.font, letter) {
self.letters.letters.push_back(group);
self.letters.number_of_groups += 1;
}
}
}
}
}
/// The object text renderer. Uses objects to render and layout text. It's use is non trivial.
/// Changes the palette to use to draw characters.
/// ```rust,no_run
/// #![no_std]
/// #![no_main]
/// use agb::display::object::{ObjectTextRender, PaletteVram, TextAlignment, Size};
/// use agb::display::palette16::Palette16;
/// use agb::display::{Font, WIDTH};
///
/// use core::fmt::Write;
///
/// static EXAMPLE_FONT: Font = agb::include_font!("examples/font/yoster.ttf", 12);
///
/// #[agb::entry]
/// fn main(gba: &mut agb::Gba) -> ! {
/// let (mut unmanaged, _) = gba.display.object.get_unmanaged();
/// let vblank = agb::interrupt::VBlank::get();
///
/// let mut palette = [0x0; 16];
/// palette[1] = 0xFF_FF;
/// let palette = Palette16::new(palette);
/// let palette = PaletteVram::new(&palette).unwrap();
///
/// let mut writer = ObjectTextRender::new(&EXAMPLE_FONT, Size::S16x16, palette);
///
/// let _ = writeln!(writer, "Hello, World!");
/// writer.layout((WIDTH, 40), TextAlignment::Left, 2);
///
/// loop {
/// writer.next_letter_group();
/// writer.update((0, 0));
/// vblank.wait_for_vblank();
/// let oam = &mut unmanaged.iter();
/// writer.commit(oam);
/// }
/// }
/// ```
pub struct ObjectTextRender<'font> {
buffer: BufferedRender<'font>,
layout: LayoutCache,
number_of_objects: usize,
}
impl<'font> ObjectTextRender<'font> {
#[must_use]
/// Creates a new text renderer with a given font, sprite size, and palette.
/// You must ensure that the sprite size can accomodate the letters from the
/// font otherwise it will panic at render time.
pub fn new(font: &'font Font, sprite_size: Size, palette: PaletteVram) -> Self {
Self {
buffer: BufferedRender::new(font, sprite_size, palette),
number_of_objects: 0,
layout: LayoutCache {
positions: VecDeque::new(),
line_capacity: VecDeque::new(),
objects: Vec::new(),
objects_are_at_origin: (0, 0).into(),
area: (0, 0).into(),
},
}
}
}
impl Write for ObjectTextRender<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for c in s.chars() {
self.buffer.input_character(c);
}
Ok(())
}
}
impl ObjectTextRender<'_> {
/// Commits work already done to screen. You can commit to multiple places in the same frame.
pub fn commit(&mut self, oam: &mut OamIterator) {
for (object, slot) in self.layout.objects.iter().zip(oam) {
slot.set(object);
}
}
/// Force a relayout, must be called after writing.
pub fn layout(
&mut self,
area: impl Into<Vector2D<i32>>,
alignment: TextAlignment,
paragraph_spacing: i32,
) {
self.layout.create_positions(
self.buffer.font,
&self.buffer.preprocessor,
&LayoutSettings {
area: area.into(),
alignment,
paragraph_spacing,
},
);
}
/// Removes one complete line. Returns whether a line could be removed. You must call [`update`][ObjectTextRender::update] after this
pub fn pop_line(&mut self) -> bool {
let width = self.layout.area.x;
let space = self.buffer.font.letter(' ').advance_width as i32;
let line_height = self.buffer.font.line_height();
if let Some(line) = self.buffer.preprocessor.lines(width, space).next() {
// there is a line
if self.layout.objects.len() >= line.number_of_letter_groups() {
// we have enough rendered letter groups to count
self.number_of_objects -= line.number_of_letter_groups();
for _ in 0..line.number_of_letter_groups() {
self.buffer.letters.letters.pop_front();
self.layout.positions.pop_front();
}
self.layout.line_capacity.pop_front();
self.layout.objects.clear();
self.buffer.preprocessor.pop(&line);
for position in self.layout.positions.iter_mut() {
position.y -= line_height as i16;
}
return true;
}
}
false
}
/// Updates the internal state of the number of letters to write and popped
/// line. Should be called in the same frame as and after
/// [`next_letter_group`][ObjectTextRender::next_letter_group], [`next_line`][ObjectTextRender::next_line], and [`pop_line`][ObjectTextRender::pop_line].
pub fn update(&mut self, position: impl Into<Vector2D<i32>>) {
if !self.buffer.buffered_chars.is_empty()
&& self.buffer.letters.letters.len() <= self.number_of_objects + 5
{
self.buffer.process();
}
self.layout.update_objects_to_display_at_position(
position.into(),
self.buffer.letters.letters.iter(),
self.number_of_objects,
);
}
/// Causes the next letter group to be shown on the next update. Returns
/// whether another letter could be added in the space given.
pub fn next_letter_group(&mut self) -> bool {
if !self.can_render_another_element() {
return false;
}
self.number_of_objects += 1;
self.at_least_n_letter_groups(self.number_of_objects);
true
}
fn can_render_another_element(&self) -> bool {
let max_number_of_lines = (self.layout.area.y / self.buffer.font.line_height()) as usize;
let max_number_of_objects = self
.layout
.line_capacity
.iter()
.take(max_number_of_lines)
.sum::<usize>();
max_number_of_objects > self.number_of_objects
}
/// Causes the next line to be shown on the next update. Returns
/// whether another line could be added in the space given.
pub fn next_line(&mut self) -> bool {
let max_number_of_lines = (self.layout.area.y / self.buffer.font.line_height()) as usize;
// find current line
for (start, end) in self
.layout
.line_capacity
.iter()
.scan(0, |count, line_size| {
let start = *count;
*count += line_size;
Some((start, *count))
})
.take(max_number_of_lines)
{
if self.number_of_objects >= start && self.number_of_objects < end {
self.number_of_objects = end;
self.at_least_n_letter_groups(end);
return true;
}
}
false
}
fn at_least_n_letter_groups(&mut self, n: usize) {
while !self.buffer.buffered_chars.is_empty() && self.buffer.letters.letters.len() <= n {
self.buffer.process();
}
}
}
struct LayoutCache {
positions: VecDeque<Vector2D<i16>>,
line_capacity: VecDeque<usize>,
objects: Vec<ObjectUnmanaged>,
objects_are_at_origin: Vector2D<i32>,
area: Vector2D<i32>,
}
impl LayoutCache {
fn update_objects_to_display_at_position<'a>(
&mut self,
position: Vector2D<i32>,
letters: impl Iterator<Item = &'a SpriteVram>,
number_of_objects: usize,
) {
let already_done = if position == self.objects_are_at_origin {
self.objects.len()
} else {
self.objects.clear();
0
};
self.objects.extend(
self.positions
.iter()
.zip(letters)
.take(number_of_objects)
.skip(already_done)
.map(|(offset, letter)| {
let position = offset.change_base() + position;
let mut object = ObjectUnmanaged::new(letter.clone());
object.show().set_position(position);
object
}),
);
self.objects.truncate(number_of_objects);
self.objects_are_at_origin = position;
}
fn create_positions(
&mut self,
font: &Font,
preprocessed: &Preprocessed,
settings: &LayoutSettings,
) {
self.area = settings.area;
self.line_capacity.clear();
self.positions.clear();
for (line, line_positions) in Self::create_layout(font, preprocessed, settings) {
self.line_capacity.push_back(line.number_of_letter_groups());
self.positions
.extend(line_positions.map(|x| Vector2D::new(x.x as i16, x.y as i16)));
}
}
fn create_layout<'a>(
font: &Font,
preprocessed: &'a Preprocessed,
settings: &'a LayoutSettings,
) -> impl Iterator<Item = (Line, impl Iterator<Item = Vector2D<i32>> + 'a)> + 'a {
let minimum_space_width = font.letter(' ').advance_width as i32;
let width = settings.area.x;
let line_height = font.line_height();
let mut head_position: Vector2D<Num<i32, 10>> = (0, -line_height).into();
preprocessed
.lines_element(width, minimum_space_width)
.map(move |(line, line_elements)| {
let line_settings = settings
.alignment
.settings(&line, minimum_space_width, width);
head_position.y += line_height;
head_position.x = line_settings.start_x.into();
(
line,
line_elements.filter_map(move |element| match element.decode() {
PreprocessedElement::LetterGroup { width } => {
let this_position = head_position;
head_position.x += width as i32;
Some(this_position.floor())
}
PreprocessedElement::WhiteSpace(space) => {
match space {
WhiteSpace::NewLine => {
head_position.y += settings.paragraph_spacing;
}
WhiteSpace::Space => head_position.x += line_settings.space_width,
}
None
}
}),
)
})
}
}
#[derive(PartialEq, Eq, Default)]
struct LayoutSettings {
area: Vector2D<i32>,
alignment: TextAlignment,
paragraph_spacing: i32,
}

Some files were not shown because too many files have changed in this diff Show more