mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
Compare commits
10 commits
21e0cb309a
...
450a033b96
Author | SHA1 | Date | |
---|---|---|---|
Alex Janka | 450a033b96 | ||
Alex Janka | 62e9c30337 | ||
Alex Janka | 911b60bcd1 | ||
Alex Janka | 8e04c415d2 | ||
2f57168fb8 | |||
Alex Janka | cd50bf9673 | ||
c7591a3e37 | |||
Alex Janka | 6be8f02fd3 | ||
Alex Janka | e1ac328ce6 | ||
Alex Janka | 94228978eb |
BIN
.github/logo.png
vendored
Normal file
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
3
.github/pull_request_template.md
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
|
||||
- [ ] Changelog updated / no changelog update needed
|
9
.github/renovate.json
vendored
Normal file
9
.github/renovate.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"ignorePresets": [
|
||||
":prHourlyLimit2"
|
||||
],
|
||||
"dependencyDashboardAutoclose": true
|
||||
}
|
20
.github/scripts/update-lockfiles.sh
vendored
Executable file
20
.github/scripts/update-lockfiles.sh
vendored
Executable 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
27
.github/scripts/update-template-repo.sh
vendored
Executable 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
47
.github/workflows/build-and-test.yml
vendored
Normal 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
54
.github/workflows/publish-agb.yml
vendored
Normal 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
22
.github/workflows/update-lockfiles.yml
vendored
Normal 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
5
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
|||
target
|
||||
/out
|
||||
*.tiled-session
|
||||
website/build
|
||||
Cargo.lock
|
||||
template/Cargo.lock
|
||||
agb*/Cargo.lock
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
|||
[submodule "emulator/mgba-sys/mgba"]
|
||||
path = emulator/mgba-sys/mgba
|
||||
url = https://github.com/mgba-emu/mgba.git
|
102
.vscode/agb.code-workspace
vendored
102
.vscode/agb.code-workspace
vendored
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
233
CHANGELOG.md
233
CHANGELOG.md
|
@ -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
|
||||
|
|
49
Cargo.toml
49
Cargo.toml
|
@ -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",
|
||||
]
|
81
README.md
81
README.md
|
@ -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/)
|
|
@ -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"
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
1
agb-gbafix/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
!Cargo.lock
|
290
agb-gbafix/Cargo.lock
generated
Normal file
290
agb-gbafix/Cargo.lock
generated
Normal 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"
|
|
@ -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
674
agb-gbafix/LICENSE
Normal 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>.
|
|
@ -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(§ion_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(§ion_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)
|
||||
}
|
|
@ -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(§ion_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(())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,5 +11,4 @@ pub(crate) trait Config {
|
|||
pub(crate) trait Image {
|
||||
fn filename(&self) -> String;
|
||||
fn colours(&self) -> Colours;
|
||||
fn deduplicate(&self) -> bool;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ¤t_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(¤t_colours_set)
|
||||
.collect();
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../README.md
|
84
agb/build.rs
84
agb/build.rs
|
@ -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}");
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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.
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
25
agb/examples/just_build.rs
Normal file
25
agb/examples/just_build.rs
Normal 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 {}
|
||||
}
|
|
@ -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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 {}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
5
agb/examples/water_tiles.toml
Normal file
5
agb/examples/water_tiles.toml
Normal file
|
@ -0,0 +1,5 @@
|
|||
version = "1.0"
|
||||
|
||||
[image.water_tiles]
|
||||
filename = "water_tiles.png"
|
||||
tile_size = "8x8"
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
@ -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)
|
|
@ -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 |
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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],
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue