mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 00:01:34 +11:00
Replace mgba-test-runner with better bindings (#471)
- [ ] Changelog updated / no changelog update needed
This commit is contained in:
commit
ec7d1447d8
8
.github/workflows/build-and-test.yml
vendored
8
.github/workflows/build-and-test.yml
vendored
|
@ -16,6 +16,8 @@ jobs:
|
|||
name: Just CI
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Set CARGO_TARGET_DIR
|
||||
run: echo "CARGO_TARGET_DIR=$HOME/target" >> $GITHUB_ENV
|
||||
- name: Install build tools
|
||||
run: sudo apt-get update && sudo apt-get install build-essential libelf-dev zip -y
|
||||
- name: Install Miri
|
||||
|
@ -31,13 +33,9 @@ jobs:
|
|||
~/.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
|
||||
run: cargo install --path emulator/test-runner --verbose
|
||||
- uses: extractions/setup-just@v1
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "emulator/mgba-sys/mgba"]
|
||||
path = emulator/mgba-sys/mgba
|
||||
url = https://github.com/mgba-emu/mgba.git
|
|
@ -54,7 +54,7 @@ to just write games for the Game Boy Advance using this library:
|
|||
- 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
|
||||
- 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)
|
||||
|
@ -95,7 +95,7 @@ for performant decimals.
|
|||
|
||||
`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
|
||||
`emulator` - Rust bindings for the [mgba](https://mgba.io) emulator used for our purposes. Currently this does not accept contributions.
|
||||
|
||||
`template` - the source for the [template repository](https://github.com/agbrs/template)
|
||||
|
||||
|
|
|
@ -289,9 +289,9 @@ pub mod test_runner {
|
|||
mgba::DebugLevel::Info,
|
||||
)
|
||||
.unwrap();
|
||||
mgba::number_of_cycles_tagged(785);
|
||||
mgba::test_runner_measure_cycles();
|
||||
self(gba);
|
||||
mgba::number_of_cycles_tagged(785);
|
||||
mgba::test_runner_measure_cycles();
|
||||
|
||||
assert!(
|
||||
unsafe { agb_alloc::number_of_blocks() } < 2,
|
||||
|
|
|
@ -28,8 +28,8 @@ fn is_running_in_mgba() -> bool {
|
|||
|
||||
const NUMBER_OF_CYCLES: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x04FF_F800) };
|
||||
|
||||
pub fn number_of_cycles_tagged(tag: u16) {
|
||||
NUMBER_OF_CYCLES.set(tag);
|
||||
pub(crate) fn test_runner_measure_cycles() {
|
||||
NUMBER_OF_CYCLES.set(0);
|
||||
}
|
||||
|
||||
pub struct Mgba {
|
||||
|
|
731
emulator/Cargo.lock
generated
Normal file
731
emulator/Cargo.lock
generated
Normal file
|
@ -0,0 +1,731 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "agb-gbafix"
|
||||
version = "0.16.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is-terminal",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
|
||||
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.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.66.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"peeking_take_while",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[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.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
|
||||
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 = "fdeflate"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "mgba"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mgba-sys",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mgba-sys"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cmake",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mgba-test-runner"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agb-gbafix",
|
||||
"anyhow",
|
||||
"clap",
|
||||
"image",
|
||||
"mgba",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
|
||||
dependencies = [
|
||||
"either",
|
||||
"libc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[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.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
|
||||
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"
|
6
emulator/Cargo.toml
Normal file
6
emulator/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"mgba",
|
||||
"mgba-sys",
|
||||
"test-runner"
|
||||
]
|
20
emulator/mgba-sys/Cargo.toml
Normal file
20
emulator/mgba-sys/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "mgba-sys"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
exclude = [
|
||||
"mgba/doc/*",
|
||||
"mgba/res/*",
|
||||
"mgba/cinema/*",
|
||||
"mgba/tools/*",
|
||||
]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.66"
|
||||
pkg-config = "0.3.27"
|
||||
cmake = "0.1"
|
77
emulator/mgba-sys/build.rs
Normal file
77
emulator/mgba-sys/build.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use std::{
|
||||
env,
|
||||
error::Error,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
type MyError = Result<(), Box<dyn Error>>;
|
||||
|
||||
fn main() -> MyError {
|
||||
have_submodule()?;
|
||||
|
||||
generate_bindings()?;
|
||||
|
||||
compile()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn have_submodule() -> MyError {
|
||||
if !Path::new("mgba/src").exists() {
|
||||
let _ = Command::new("git")
|
||||
.args(["submodule", "update", "--init", "mgba"])
|
||||
.status()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_bindings() -> MyError {
|
||||
println!("cargo:rerun-if-changed=wrapper.h");
|
||||
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header("wrapper.h")
|
||||
.opaque_type("mTiming")
|
||||
.allowlist_type("mCore")
|
||||
.allowlist_type("VFile")
|
||||
.allowlist_type("VDir")
|
||||
.allowlist_type("mLogger")
|
||||
.allowlist_type("mLogLevel")
|
||||
.allowlist_var("MAP_WRITE")
|
||||
.allowlist_var("BYTES_PER_PIXEL")
|
||||
.allowlist_function("GBACoreCreate")
|
||||
.allowlist_function("mCoreInitConfig")
|
||||
.allowlist_function("mLogSetDefaultLogger")
|
||||
.allowlist_function("blip_set_rates")
|
||||
.allowlist_function("blip_read_samples")
|
||||
.allowlist_function("blip_samples_avail")
|
||||
.allowlist_function("mCoreConfigLoadDefaults")
|
||||
.allowlist_function("mCoreLoadConfig")
|
||||
.allowlist_function("mTimingGlobalTime")
|
||||
.allowlist_function("mLogCategoryName")
|
||||
.generate_cstr(true)
|
||||
.derive_default(true)
|
||||
.clang_arg("-I./mgba/include")
|
||||
.generate()?;
|
||||
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR")?);
|
||||
bindings.write_to_file(out_path.join("bindings.rs"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile() -> MyError {
|
||||
let dst = cmake::Config::new("mgba")
|
||||
.define("LIBMGBA_ONLY", "1")
|
||||
.define("M_CORE_GBA", "1")
|
||||
.define("M_CORE_GB", "0")
|
||||
.define("USE_DEBUGGERS", "1")
|
||||
.build();
|
||||
|
||||
println!("cargo:rustc-link-search=native={}/lib", dst.display());
|
||||
println!("cargo:rustc-link-search=native={}", dst.display());
|
||||
println!("cargo:rustc-link-lib=static=mgba");
|
||||
|
||||
Ok(())
|
||||
}
|
1
emulator/mgba-sys/mgba
Submodule
1
emulator/mgba-sys/mgba
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 2fb55450610a4d10479a1eed1408f905d79318e3
|
11
emulator/mgba-sys/src/lib.rs
Normal file
11
emulator/mgba-sys/src/lib.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
#[allow(non_upper_case_globals)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_snake_case)]
|
||||
mod ffi {
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
}
|
||||
|
||||
pub use ffi::*;
|
||||
|
||||
unsafe impl Sync for mLogger {}
|
||||
unsafe impl Send for mLogger {}
|
6
emulator/mgba-sys/wrapper.h
Normal file
6
emulator/mgba-sys/wrapper.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#include "mgba/include/mgba-util/vfs.h"
|
||||
#include "mgba/include/mgba/core/blip_buf.h"
|
||||
#include "mgba/include/mgba/core/core.h"
|
||||
#include "mgba/include/mgba/core/log.h"
|
||||
#include "mgba/include/mgba/core/timing.h"
|
||||
#include "mgba/include/mgba/gba/core.h"
|
11
emulator/mgba/Cargo.toml
Normal file
11
emulator/mgba/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "mgba"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.147"
|
||||
mgba-sys = { version = "0.1.0", path = "../mgba-sys" }
|
||||
thiserror = "1"
|
BIN
emulator/mgba/save.gba
Normal file
BIN
emulator/mgba/save.gba
Normal file
Binary file not shown.
228
emulator/mgba/src/lib.rs
Normal file
228
emulator/mgba/src/lib.rs
Normal file
|
@ -0,0 +1,228 @@
|
|||
mod log;
|
||||
mod vfile;
|
||||
|
||||
use std::{
|
||||
cell::UnsafeCell,
|
||||
ptr::NonNull,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
pub use log::{LogLevel, Logger};
|
||||
pub use vfile::{file::FileBacked, memory::MemoryBacked, shared::Shared, MapFlag, VFile};
|
||||
|
||||
use vfile::VFileAlloc;
|
||||
|
||||
pub struct MCore {
|
||||
core: NonNull<mgba_sys::mCore>,
|
||||
video_buffer: UnsafeCell<Box<[u32]>>,
|
||||
}
|
||||
|
||||
impl Drop for MCore {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.core.as_ref().deinit.unwrap()(self.core.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
const SAMPLE_RATE: f64 = 44100.0;
|
||||
|
||||
macro_rules! call_on_core {
|
||||
($core:expr => $fn_name:ident($($arg:expr),* $(,)?)) => {
|
||||
$core.as_ref().$fn_name.unwrap()($core.as_ptr(), $($arg),*)
|
||||
};
|
||||
}
|
||||
|
||||
static GLOBAL_LOGGER_HAS_BEEN_INITIALISED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn set_global_default_logger(logger: &'static Logger) {
|
||||
GLOBAL_LOGGER_HAS_BEEN_INITIALISED.store(true, Ordering::SeqCst);
|
||||
unsafe { mgba_sys::mLogSetDefaultLogger(logger.to_mgba()) }
|
||||
}
|
||||
|
||||
impl MCore {
|
||||
pub fn new() -> Option<Self> {
|
||||
if !GLOBAL_LOGGER_HAS_BEEN_INITIALISED.load(Ordering::SeqCst) {
|
||||
set_global_default_logger(&log::NO_LOGGER);
|
||||
}
|
||||
|
||||
let core = unsafe { mgba_sys::GBACoreCreate() };
|
||||
let core = NonNull::new(core)?;
|
||||
|
||||
unsafe { mgba_sys::mCoreInitConfig(core.as_ptr(), std::ptr::null()) };
|
||||
|
||||
unsafe { call_on_core!(core=>init()) };
|
||||
|
||||
let (mut width, mut height) = (0, 0);
|
||||
|
||||
unsafe { call_on_core!(core=>desiredVideoDimensions(&mut width, &mut height)) };
|
||||
|
||||
let mut video_buffer = UnsafeCell::new(
|
||||
vec![
|
||||
0;
|
||||
(width * height * mgba_sys::BYTES_PER_PIXEL) as usize / std::mem::size_of::<u32>()
|
||||
]
|
||||
.into_boxed_slice(),
|
||||
);
|
||||
|
||||
unsafe {
|
||||
call_on_core!(
|
||||
core=>setVideoBuffer(
|
||||
video_buffer.get_mut().as_mut_ptr(),
|
||||
width as usize
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
unsafe { call_on_core!(core=>reset()) };
|
||||
|
||||
unsafe { call_on_core!(core=>setAudioBufferSize(0x4000)) };
|
||||
|
||||
unsafe {
|
||||
mgba_sys::blip_set_rates(
|
||||
call_on_core!(core=>getAudioChannel(0)),
|
||||
call_on_core!(core=>frequency()) as f64,
|
||||
SAMPLE_RATE,
|
||||
)
|
||||
}
|
||||
unsafe {
|
||||
mgba_sys::blip_set_rates(
|
||||
call_on_core!(core=>getAudioChannel(1)),
|
||||
call_on_core!(core=>frequency()) as f64,
|
||||
SAMPLE_RATE,
|
||||
)
|
||||
}
|
||||
|
||||
let core_options = mgba_sys::mCoreOptions {
|
||||
volume: 0x100,
|
||||
useBios: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe { mgba_sys::mCoreConfigLoadDefaults(&mut (*core.as_ptr()).config, &core_options) };
|
||||
unsafe { mgba_sys::mCoreLoadConfig(core.as_ptr()) };
|
||||
|
||||
Some(MCore { core, video_buffer })
|
||||
}
|
||||
|
||||
pub fn load_rom<V: VFile>(&mut self, vfile: V) {
|
||||
let vfile = VFileAlloc::new(vfile);
|
||||
unsafe { call_on_core!(self.core=>loadROM(vfile.into_mgba())) };
|
||||
}
|
||||
|
||||
pub fn frame(&mut self) {
|
||||
unsafe { call_on_core!(self.core=>runFrame()) };
|
||||
}
|
||||
|
||||
pub fn step(&mut self) {
|
||||
unsafe { call_on_core!(self.core=>step()) };
|
||||
}
|
||||
|
||||
pub fn set_keys(&mut self, buttons: u32) {
|
||||
unsafe { call_on_core!(self.core=>setKeys(buttons)) };
|
||||
}
|
||||
|
||||
pub fn load_save<V: VFile>(&mut self, save_file: V) {
|
||||
let save_file = VFileAlloc::new(save_file);
|
||||
unsafe {
|
||||
call_on_core!(self.core=>loadSave(save_file.into_mgba()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn video_buffer(&mut self) -> &[u32] {
|
||||
// Safety: For the duration of this borrow, mgba can't be called into,
|
||||
// so this reference can be taken.
|
||||
unsafe { &*self.video_buffer.get() }
|
||||
}
|
||||
|
||||
pub fn current_cycle(&mut self) -> u64 {
|
||||
unsafe { mgba_sys::mTimingGlobalTime(self.core.as_ref().timing) }
|
||||
}
|
||||
|
||||
pub fn read_audio(&mut self, target: &mut [i16]) -> usize {
|
||||
let audio_channel_left = unsafe { call_on_core!(self.core=>getAudioChannel(0)) };
|
||||
let audio_channel_right = unsafe { call_on_core!(self.core=>getAudioChannel(1)) };
|
||||
|
||||
let samples_available = unsafe { mgba_sys::blip_samples_avail(audio_channel_left) };
|
||||
|
||||
if samples_available > 0 {
|
||||
let samples_to_read = samples_available.min(target.len() as i32 / 2);
|
||||
let produced = unsafe {
|
||||
mgba_sys::blip_read_samples(
|
||||
audio_channel_left,
|
||||
target.as_mut_ptr().cast(),
|
||||
samples_to_read,
|
||||
1,
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
mgba_sys::blip_read_samples(
|
||||
audio_channel_right,
|
||||
target.as_mut_ptr().add(1).cast(),
|
||||
samples_to_read,
|
||||
1,
|
||||
)
|
||||
};
|
||||
|
||||
return produced.try_into().unwrap();
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
static TEST_ROM: &[u8] = include_bytes!("../test.gba");
|
||||
static SAVE_TEST_ROM: &[u8] = include_bytes!("../save.gba");
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_running_game_for_some_frames() {
|
||||
let file = MemoryBacked::new_from_slice(TEST_ROM);
|
||||
let mut core = MCore::new().unwrap();
|
||||
core.load_rom(file);
|
||||
|
||||
for _ in 0..100 {
|
||||
core.frame();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_save_file_is_initialised() {
|
||||
let shared_save_file = Shared::new(MemoryBacked::new(Vec::new()));
|
||||
|
||||
{
|
||||
let save_file = shared_save_file.clone();
|
||||
let rom_file = MemoryBacked::new_from_slice(SAVE_TEST_ROM);
|
||||
|
||||
let mut core = MCore::new().unwrap();
|
||||
core.load_rom(rom_file);
|
||||
core.load_save(save_file);
|
||||
for _ in 0..10 {
|
||||
core.frame();
|
||||
}
|
||||
}
|
||||
|
||||
let save_file = shared_save_file
|
||||
.try_into_inner()
|
||||
.unwrap_or_else(|_| panic!("the shared references were not released"))
|
||||
.into_inner()
|
||||
.into_owned();
|
||||
|
||||
assert_eq!(save_file.len(), 32 * 1024, "the save file should be 32 kb");
|
||||
|
||||
assert_eq!(
|
||||
save_file[0..128],
|
||||
(0..128).collect::<Vec<u8>>(),
|
||||
"First 128 bytes should be ascending numbers"
|
||||
);
|
||||
assert_eq!(
|
||||
save_file[128..],
|
||||
std::iter::repeat(0xFF)
|
||||
.take(save_file.len() - 128)
|
||||
.collect::<Vec<u8>>(),
|
||||
"Remanider of save should be 0xFF, all ones"
|
||||
);
|
||||
}
|
||||
}
|
143
emulator/mgba/src/log.rs
Normal file
143
emulator/mgba/src/log.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
use std::ffi::CStr;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub static NO_LOGGER: Logger = generate_no_logger();
|
||||
|
||||
pub enum LogLevel {
|
||||
Fatal,
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
Stub,
|
||||
GameError,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("A log level of {provided_log_level} does not match any known log level")]
|
||||
pub struct LogLevelIsNotValid {
|
||||
provided_log_level: mgba_sys::mLogLevel,
|
||||
}
|
||||
|
||||
impl TryFrom<mgba_sys::mLogLevel> for LogLevel {
|
||||
type Error = LogLevelIsNotValid;
|
||||
|
||||
fn try_from(value: mgba_sys::mLogLevel) -> Result<Self, LogLevelIsNotValid> {
|
||||
Ok(match value {
|
||||
mgba_sys::mLogLevel_mLOG_FATAL => LogLevel::Fatal,
|
||||
mgba_sys::mLogLevel_mLOG_ERROR => LogLevel::Error,
|
||||
mgba_sys::mLogLevel_mLOG_WARN => LogLevel::Warn,
|
||||
mgba_sys::mLogLevel_mLOG_INFO => LogLevel::Info,
|
||||
mgba_sys::mLogLevel_mLOG_DEBUG => LogLevel::Debug,
|
||||
mgba_sys::mLogLevel_mLOG_STUB => LogLevel::Stub,
|
||||
mgba_sys::mLogLevel_mLOG_GAME_ERROR => LogLevel::GameError,
|
||||
_ => return Err(LogLevelIsNotValid {
|
||||
provided_log_level: value
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const fn generate_no_logger() -> Logger {
|
||||
Logger {
|
||||
logger: mgba_sys::mLogger {
|
||||
log: Some(no_log),
|
||||
filter: std::ptr::null_mut(),
|
||||
},
|
||||
log: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Logger {
|
||||
logger: mgba_sys::mLogger,
|
||||
log: Option<fn(&str, LogLevel, String)>,
|
||||
}
|
||||
|
||||
impl Logger {
|
||||
pub(crate) fn to_mgba(&'static self) -> *mut mgba_sys::mLogger {
|
||||
(self as *const Logger)
|
||||
.cast::<mgba_sys::mLogger>()
|
||||
.cast_mut()
|
||||
}
|
||||
|
||||
pub const fn new(logger: fn(&str, LogLevel, String)) -> Self {
|
||||
Logger {
|
||||
logger: mgba_sys::mLogger {
|
||||
log: Some(log_string_wrapper),
|
||||
filter: std::ptr::null_mut(),
|
||||
},
|
||||
log: Some(logger),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn log_string_wrapper(
|
||||
logger: *mut mgba_sys::mLogger,
|
||||
category: i32,
|
||||
level: u32,
|
||||
format: *const i8,
|
||||
args: VaArgs,
|
||||
) {
|
||||
let logger = logger.cast::<Logger>();
|
||||
if let Some(logger) = unsafe { &(*logger).log } {
|
||||
let s = convert_to_string(format, args);
|
||||
|
||||
if let Some(s) = s {
|
||||
let category_c_name = unsafe { mgba_sys::mLogCategoryName(category) };
|
||||
const UNKNOWN: &str = "Unknown";
|
||||
let category_name = if category_c_name.is_null() {
|
||||
UNKNOWN
|
||||
} else {
|
||||
unsafe { CStr::from_ptr(category_c_name).to_str() }.unwrap_or(UNKNOWN)
|
||||
};
|
||||
|
||||
logger(category_name, LogLevel::try_from(level).unwrap_or(LogLevel::Unknown), s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
type VaArgs = *mut mgba_sys::__va_list_tag;
|
||||
|
||||
#[cfg(windows)]
|
||||
type VaArgs = mgba_sys::va_list;
|
||||
|
||||
extern "C" {
|
||||
fn vsnprintf(
|
||||
s: *mut libc::c_char,
|
||||
n: libc::size_t,
|
||||
format: *const libc::c_char,
|
||||
va_args: VaArgs,
|
||||
) -> std::ffi::c_int;
|
||||
}
|
||||
|
||||
fn convert_to_string(format: *const i8, var_args: VaArgs) -> Option<String> {
|
||||
const BUFFER_SIZE: usize = 1024;
|
||||
|
||||
let mut string = vec![0u8; BUFFER_SIZE];
|
||||
|
||||
let count = unsafe { vsnprintf(string.as_mut_ptr().cast(), BUFFER_SIZE, format, var_args) };
|
||||
if count < 0 {
|
||||
return None;
|
||||
}
|
||||
// The last byte is always null, so guarentee we can remove that. If we
|
||||
// wrote too much in this sprint then we can at least partially recover the
|
||||
// string.
|
||||
string.truncate(string.len() - 1);
|
||||
string.truncate(count as usize);
|
||||
|
||||
String::from_utf8(string).ok()
|
||||
}
|
||||
|
||||
extern "C" fn no_log(
|
||||
_l: *mut mgba_sys::mLogger,
|
||||
_category: i32,
|
||||
_level: u32,
|
||||
_format: *const i8,
|
||||
_var_args: VaArgs,
|
||||
) {
|
||||
}
|
271
emulator/mgba/src/vfile.rs
Normal file
271
emulator/mgba/src/vfile.rs
Normal file
|
@ -0,0 +1,271 @@
|
|||
use std::io::Read;
|
||||
use std::io::Result;
|
||||
use std::io::Seek;
|
||||
use std::io::SeekFrom;
|
||||
use std::io::Write;
|
||||
|
||||
pub mod file;
|
||||
pub mod memory;
|
||||
pub mod shared;
|
||||
|
||||
pub enum MapFlag {
|
||||
Read,
|
||||
Write,
|
||||
}
|
||||
|
||||
trait VFileExtensions: VFile {
|
||||
fn readline(&mut self, buffer: &mut [u8]) -> Result<usize> {
|
||||
let mut byte = 0;
|
||||
while byte < buffer.len() - 1 {
|
||||
if self.read(&mut buffer[byte..byte + 1])? == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
byte += 1;
|
||||
if buffer[byte - 1] == b'\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
buffer[byte] = b'\0';
|
||||
Ok(byte)
|
||||
}
|
||||
fn map(&mut self, size: usize, _flag: MapFlag) -> Result<Box<[u8]>> {
|
||||
let position = self.stream_position()?;
|
||||
let data = vec![0; size];
|
||||
let mut data = data.into_boxed_slice();
|
||||
|
||||
self.seek(SeekFrom::Start(0))?;
|
||||
match self.read_exact(&mut data) {
|
||||
Ok(_) => {}
|
||||
Err(err) => match err.kind() {
|
||||
std::io::ErrorKind::UnexpectedEof => {}
|
||||
_ => return Err(err),
|
||||
},
|
||||
}
|
||||
self.seek(SeekFrom::Start(position))?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
fn unmap(&mut self, data: Box<[u8]>) -> Result<()> {
|
||||
// assume map was created with write
|
||||
let position = self.stream_position()?;
|
||||
self.seek(SeekFrom::Start(0))?;
|
||||
self.write_all(&data)?;
|
||||
self.seek(SeekFrom::Start(position))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn truncate(&mut self, size: usize) -> Result<()> {
|
||||
let position = self.stream_position()?;
|
||||
let stream_length = self.seek(SeekFrom::End(0))?;
|
||||
|
||||
if (size as u64) > stream_length {
|
||||
self.seek(SeekFrom::Start(position))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.seek(SeekFrom::Start(size as u64))?;
|
||||
|
||||
let bytes_to_write = stream_length - size as u64;
|
||||
let bytes: Vec<u8> = std::iter::repeat(0).take(bytes_to_write as usize).collect();
|
||||
self.write_all(&bytes)?;
|
||||
|
||||
self.seek(SeekFrom::Start(position))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn size(&mut self) -> Result<usize> {
|
||||
let position = self.stream_position()?;
|
||||
let stream_length = self.seek(SeekFrom::End(0))?;
|
||||
self.seek(SeekFrom::Start(position))?;
|
||||
|
||||
Ok(stream_length as usize)
|
||||
}
|
||||
fn sync(&mut self, buffer: &[u8]) -> Result<()> {
|
||||
let position = self.stream_position()?;
|
||||
|
||||
self.seek(SeekFrom::Start(0))?;
|
||||
self.write_all(buffer)?;
|
||||
self.seek(SeekFrom::Start(position))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VFile: Seek + Read + Write {}
|
||||
|
||||
impl<T: VFile + ?Sized> VFileExtensions for T {}
|
||||
|
||||
#[repr(C)]
|
||||
struct VFileInner<V: VFile> {
|
||||
vfile: mgba_sys::VFile,
|
||||
file: V,
|
||||
}
|
||||
|
||||
pub struct VFileAlloc<V: VFile>(Box<VFileInner<V>>);
|
||||
|
||||
impl<V: VFile> VFileAlloc<V> {
|
||||
pub fn new(f: V) -> Self {
|
||||
Self(Box::new(VFileInner {
|
||||
vfile: unsafe { vfile_extern::create_vfile::<V>() },
|
||||
file: f,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn into_mgba(self) -> *mut mgba_sys::VFile {
|
||||
let f = Box::into_raw(self.0) as *mut VFileInner<V>;
|
||||
f.cast()
|
||||
}
|
||||
}
|
||||
|
||||
mod vfile_extern {
|
||||
use std::io::SeekFrom;
|
||||
use super::VFileExtensions;
|
||||
|
||||
/// Safety: Must be part of a VFileInner
|
||||
pub unsafe fn create_vfile<V: super::VFile>() -> mgba_sys::VFile {
|
||||
mgba_sys::VFile {
|
||||
close: Some(close::<V>),
|
||||
seek: Some(seek::<V>),
|
||||
read: Some(read::<V>),
|
||||
readline: Some(readline::<V>),
|
||||
write: Some(write::<V>),
|
||||
map: Some(map::<V>),
|
||||
unmap: Some(unmap::<V>),
|
||||
truncate: Some(truncate::<V>),
|
||||
size: Some(size::<V>),
|
||||
sync: Some(sync::<V>),
|
||||
}
|
||||
}
|
||||
|
||||
use mgba_sys::VFile;
|
||||
|
||||
extern "C" fn close<V: super::VFile>(vf: *mut VFile) -> bool {
|
||||
drop(unsafe { Box::from_raw(vf.cast::<super::VFileInner<V>>()) });
|
||||
true
|
||||
}
|
||||
|
||||
unsafe fn with_inner<V: super::VFile, F, T>(vf: *mut VFile, f: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut dyn super::VFile) -> T,
|
||||
{
|
||||
let vf = vf.cast::<super::VFileInner<V>>();
|
||||
let vf = &mut *vf;
|
||||
f(&mut vf.file)
|
||||
}
|
||||
|
||||
extern "C" fn seek<V: super::VFile>(
|
||||
vf: *mut VFile,
|
||||
offset: mgba_sys::off_t,
|
||||
whence: std::os::raw::c_int,
|
||||
) -> mgba_sys::off_t {
|
||||
unsafe {
|
||||
with_inner::<V, _, _>(vf, |vf| {
|
||||
// casts required for windows compatability
|
||||
#[allow(clippy::useless_conversion)]
|
||||
let seek = match whence {
|
||||
libc::SEEK_CUR => SeekFrom::Current(offset.into()),
|
||||
libc::SEEK_SET => SeekFrom::Start(offset as u64),
|
||||
libc::SEEK_END => SeekFrom::End(offset.into()),
|
||||
_ => return -1,
|
||||
};
|
||||
vf.seek(seek).map(|x| x as mgba_sys::off_t).unwrap_or(-1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn read<V: super::VFile>(
|
||||
vf: *mut VFile,
|
||||
buffer: *mut ::std::os::raw::c_void,
|
||||
size: usize,
|
||||
) -> isize {
|
||||
unsafe {
|
||||
with_inner::<V, _, _>(vf, |vf| {
|
||||
vf.read(std::slice::from_raw_parts_mut(buffer.cast(), size))
|
||||
.map(|x| x as isize)
|
||||
.unwrap_or(-1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn readline<V: super::VFile>(
|
||||
vf: *mut VFile,
|
||||
buffer: *mut ::std::os::raw::c_char,
|
||||
size: usize,
|
||||
) -> isize {
|
||||
unsafe {
|
||||
with_inner::<V, _, _>(vf, |vf| {
|
||||
vf.readline(std::slice::from_raw_parts_mut(buffer.cast(), size))
|
||||
.map(|x| x as isize)
|
||||
.unwrap_or(-1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn write<V: super::VFile>(
|
||||
vf: *mut VFile,
|
||||
buffer: *const ::std::os::raw::c_void,
|
||||
size: usize,
|
||||
) -> isize {
|
||||
unsafe {
|
||||
with_inner::<V, _, _>(vf, |vf| {
|
||||
vf.write(std::slice::from_raw_parts(buffer.cast(), size))
|
||||
.map(|x| x as isize)
|
||||
.unwrap_or(-1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn map<V: super::VFile>(
|
||||
vf: *mut VFile,
|
||||
size: usize,
|
||||
flags: ::std::os::raw::c_int,
|
||||
) -> *mut ::std::os::raw::c_void {
|
||||
unsafe {
|
||||
with_inner::<V, _, _>(vf, |vf| {
|
||||
let map_type = match flags as u32 {
|
||||
mgba_sys::MAP_WRITE => super::MapFlag::Write,
|
||||
_ => super::MapFlag::Read,
|
||||
};
|
||||
vf.map(size, map_type)
|
||||
.map(|x| Box::leak(x).as_mut_ptr().cast())
|
||||
.unwrap_or(std::ptr::null_mut())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn unmap<V: super::VFile>(
|
||||
vf: *mut VFile,
|
||||
memory: *mut ::std::os::raw::c_void,
|
||||
size: usize,
|
||||
) {
|
||||
unsafe {
|
||||
with_inner::<V, _, _>(vf, |vf| {
|
||||
let b = Box::from_raw(std::slice::from_raw_parts_mut(memory.cast::<u8>(), size));
|
||||
let _ = vf.unmap(b);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn truncate<V: super::VFile>(vf: *mut VFile, size: usize) {
|
||||
unsafe {
|
||||
let _ = with_inner::<V, _, _>(vf, |vf| vf.truncate(size));
|
||||
}
|
||||
}
|
||||
extern "C" fn size<V: super::VFile>(vf: *mut VFile) -> isize {
|
||||
unsafe { with_inner::<V, _, _>(vf, |vf| vf.size().map(|x| x as isize).unwrap_or(-1)) }
|
||||
}
|
||||
|
||||
extern "C" fn sync<V: super::VFile>(
|
||||
vf: *mut VFile,
|
||||
buffer: *mut ::std::os::raw::c_void,
|
||||
size: usize,
|
||||
) -> bool {
|
||||
unsafe {
|
||||
with_inner::<V, _, _>(vf, |vf| {
|
||||
vf.sync(std::slice::from_raw_parts(buffer.cast(), size))
|
||||
.is_ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
44
emulator/mgba/src/vfile/file.rs
Normal file
44
emulator/mgba/src/vfile/file.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Seek, Write},
|
||||
};
|
||||
|
||||
use super::VFile;
|
||||
|
||||
pub struct FileBacked {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl FileBacked {
|
||||
pub fn new(file: File) -> Self {
|
||||
Self { file }
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> File {
|
||||
self.file
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for FileBacked {
|
||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||
self.file.seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for FileBacked {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.file.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.file.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for FileBacked {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.file.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl VFile for FileBacked {}
|
61
emulator/mgba/src/vfile/memory.rs
Normal file
61
emulator/mgba/src/vfile/memory.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
io::{Cursor, Read, Seek, Write},
|
||||
};
|
||||
|
||||
use super::VFile;
|
||||
|
||||
pub struct MemoryBacked {
|
||||
buffer: Cursor<Cow<'static, [u8]>>,
|
||||
}
|
||||
|
||||
impl MemoryBacked {
|
||||
pub fn new_from_slice(data: &'static [u8]) -> Self {
|
||||
Self {
|
||||
buffer: Cursor::new(Cow::Borrowed(data)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
buffer: Cursor::new(Cow::Owned(data)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> Cow<'static, [u8]> {
|
||||
self.buffer.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for MemoryBacked {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let position = self.buffer.position();
|
||||
let underlying = self.buffer.get_mut().to_mut();
|
||||
let mut new_buffer = Cursor::new(underlying);
|
||||
new_buffer.set_position(position);
|
||||
let result = new_buffer.write(buf);
|
||||
let new_position = new_buffer.position();
|
||||
|
||||
self.buffer.set_position(new_position);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for MemoryBacked {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.buffer.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for MemoryBacked {
|
||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||
self.buffer.seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl VFile for MemoryBacked {}
|
64
emulator/mgba/src/vfile/shared.rs
Normal file
64
emulator/mgba/src/vfile/shared.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use std::{
|
||||
io::{Read, Seek, Write},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::VFile;
|
||||
|
||||
pub struct Shared<V> {
|
||||
inner: Arc<Mutex<V>>,
|
||||
}
|
||||
|
||||
impl<V> Clone for Shared<V> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Shared<V> {
|
||||
pub fn new(v: V) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(v)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_into_inner(self) -> Result<V, Self> {
|
||||
Arc::try_unwrap(self.inner)
|
||||
.map(|x| x.into_inner().unwrap())
|
||||
.map_err(|e| Self { inner: e })
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Clone> Shared<V> {
|
||||
pub fn into_inner(self) -> V {
|
||||
Arc::try_unwrap(self.inner)
|
||||
.map(|x| x.into_inner().unwrap())
|
||||
.unwrap_or_else(|x| x.lock().unwrap().clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Write> Write for Shared<V> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.inner.lock().unwrap().write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.inner.lock().unwrap().flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Read> Read for Shared<V> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.inner.lock().unwrap().read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Seek> Seek for Shared<V> {
|
||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||
self.inner.lock().unwrap().seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: VFile> VFile for Shared<V> {}
|
BIN
emulator/mgba/test.gba
Normal file
BIN
emulator/mgba/test.gba
Normal file
Binary file not shown.
13
emulator/test-runner/Cargo.toml
Normal file
13
emulator/test-runner/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "mgba-test-runner"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
mgba = { path = "../mgba" }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
anyhow = "1"
|
||||
image = { version = "0.24", default-features = false, features = [ "png", "bmp" ] }
|
||||
agb-gbafix = { path = "../../agb-gbafix" }
|
64
emulator/test-runner/src/image_compare.rs
Normal file
64
emulator/test-runner/src/image_compare.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use std::path::Path;
|
||||
|
||||
use image::{io::Reader, DynamicImage};
|
||||
|
||||
pub struct ComparisonResult {
|
||||
matches: bool,
|
||||
image: DynamicImage,
|
||||
}
|
||||
|
||||
impl ComparisonResult {
|
||||
pub fn success(&self) -> bool {
|
||||
self.matches
|
||||
}
|
||||
|
||||
pub fn image(&self) -> &DynamicImage {
|
||||
&self.image
|
||||
}
|
||||
}
|
||||
|
||||
const WIDTH: usize = 240;
|
||||
const HEIGHT: usize = 160;
|
||||
|
||||
fn convert_rgba_to_nearest_gba_colour(c: [u8; 4]) -> [u8; 4] {
|
||||
let mut n = c;
|
||||
n.iter_mut()
|
||||
.for_each(|a| *a = ((((*a as u32 >> 3) << 3) * 0x21) >> 5) as u8);
|
||||
n
|
||||
}
|
||||
|
||||
pub fn compare_image(
|
||||
image: impl AsRef<Path>,
|
||||
video_buffer: &[u32],
|
||||
) -> anyhow::Result<ComparisonResult> {
|
||||
let expected = Reader::open(image)?.decode()?;
|
||||
let expected_buffer = expected.to_rgba8();
|
||||
|
||||
let (exp_dim_x, exp_dim_y) = expected_buffer.dimensions();
|
||||
if exp_dim_x != WIDTH as u32 || exp_dim_y != HEIGHT as u32 {
|
||||
return Ok(ComparisonResult {
|
||||
matches: false,
|
||||
image: expected,
|
||||
});
|
||||
}
|
||||
|
||||
for y in 0..HEIGHT {
|
||||
for x in 0..WIDTH {
|
||||
let video_pixel = video_buffer[x + y * WIDTH];
|
||||
let image_pixel = expected_buffer.get_pixel(x as u32, y as u32);
|
||||
let image_pixel = convert_rgba_to_nearest_gba_colour(image_pixel.0);
|
||||
|
||||
if image_pixel[0..3] != video_pixel.to_le_bytes()[0..3] {
|
||||
return Ok(ComparisonResult {
|
||||
matches: false,
|
||||
image: expected,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ComparisonResult {
|
||||
matches: true,
|
||||
image: expected,
|
||||
})
|
||||
}
|
163
emulator/test-runner/src/main.rs
Normal file
163
emulator/test-runner/src/main.rs
Normal file
|
@ -0,0 +1,163 @@
|
|||
use std::{
|
||||
collections::VecDeque,
|
||||
error::Error,
|
||||
fs::File,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use clap::Parser;
|
||||
use image_compare::compare_image;
|
||||
use mgba::{LogLevel, Logger, MCore, MemoryBacked, VFile};
|
||||
|
||||
mod image_compare;
|
||||
|
||||
static LOGGER: Logger = Logger::new(my_logger);
|
||||
|
||||
static LOGGER_BUFFER: Mutex<VecDeque<(String, LogLevel, String)>> = Mutex::new(VecDeque::new());
|
||||
|
||||
fn my_logger(category: &str, level: LogLevel, s: String) {
|
||||
LOGGER_BUFFER
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back((category.to_string(), level, s));
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
struct CliArguments {
|
||||
rom: PathBuf,
|
||||
}
|
||||
|
||||
struct TestRunner {
|
||||
mgba: MCore,
|
||||
}
|
||||
|
||||
enum Timer {
|
||||
Start(u64),
|
||||
Total(u64),
|
||||
}
|
||||
|
||||
impl TestRunner {
|
||||
fn new<V: VFile>(rom: V) -> Result<Self, Box<dyn Error>> {
|
||||
let mut mgba = MCore::new().ok_or(anyhow!("cannot create core"))?;
|
||||
|
||||
mgba::set_global_default_logger(&LOGGER);
|
||||
|
||||
mgba.load_rom(rom);
|
||||
|
||||
Ok(Self { mgba })
|
||||
}
|
||||
|
||||
fn run(mut self) -> Result<(), Box<dyn Error>> {
|
||||
let mut timer: Timer = Timer::Total(0);
|
||||
|
||||
let mut mark_tests_as_soft_failed = false;
|
||||
let mut mark_this_test_as_soft_failed = false;
|
||||
loop {
|
||||
self.mgba.step();
|
||||
while let Some((category, level, message)) = LOGGER_BUFFER.lock().unwrap().pop_front() {
|
||||
match (category.as_ref(), level, message.as_ref()) {
|
||||
(_, LogLevel::Fatal, fatal_message) => {
|
||||
return Err(anyhow!("Failed with fatal message: {}", fatal_message).into());
|
||||
}
|
||||
("GBA I/O", _, "Stub I/O register write: FFF800") => match timer {
|
||||
Timer::Start(time) => {
|
||||
let total_cycles = self.mgba.current_cycle() - time;
|
||||
timer = Timer::Total(total_cycles);
|
||||
}
|
||||
Timer::Total(_) => {
|
||||
timer = Timer::Start(self.mgba.current_cycle());
|
||||
}
|
||||
},
|
||||
("GBA Debug", _, debug_message) => {
|
||||
if let Some(image_path) = debug_message.strip_prefix("image:") {
|
||||
match compare_image(image_path, self.mgba.video_buffer()).with_context(
|
||||
|| anyhow!("Could not open image {} for comparison", image_path),
|
||||
) {
|
||||
Ok(compare) => {
|
||||
if !compare.success() {
|
||||
eprintln!("Image and video buffer do not match");
|
||||
mark_tests_as_soft_failed = true;
|
||||
mark_this_test_as_soft_failed = true;
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("{}", e),
|
||||
}
|
||||
} else if debug_message.ends_with("...") {
|
||||
eprint!("{}", debug_message);
|
||||
} else if debug_message == "[ok]" {
|
||||
let cycles = match timer {
|
||||
Timer::Start(_) => panic!("test completed with invalid timing"),
|
||||
Timer::Total(c) => c,
|
||||
};
|
||||
if mark_this_test_as_soft_failed {
|
||||
mark_this_test_as_soft_failed = false;
|
||||
eprintln!(
|
||||
"[fail: {} c ≈ {} s]",
|
||||
cycles,
|
||||
((cycles as f64 / (16.78 * 1_000_000.0)) * 100.0).round()
|
||||
/ 100.0
|
||||
);
|
||||
} else {
|
||||
eprintln!(
|
||||
"[ok: {} c ≈ {} s]",
|
||||
cycles,
|
||||
((cycles as f64 / (16.78 * 1_000_000.0)) * 100.0).round()
|
||||
/ 100.0
|
||||
);
|
||||
}
|
||||
} else {
|
||||
eprintln!("{}", debug_message);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if message == "Tests finished successfully" {
|
||||
if mark_tests_as_soft_failed {
|
||||
eprintln!("Tests failed");
|
||||
return Err(anyhow!("Tests failed").into());
|
||||
} else {
|
||||
eprintln!("{}", message);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let args = CliArguments::parse();
|
||||
|
||||
let rom = load_rom(args.rom)?;
|
||||
let rom = MemoryBacked::new(rom);
|
||||
|
||||
TestRunner::new(rom)?.run()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_rom<P: AsRef<Path>>(path: P) -> anyhow::Result<Vec<u8>> {
|
||||
let mut input_file = File::open(path)?;
|
||||
let mut input_file_buffer = Vec::new();
|
||||
|
||||
input_file.read_to_end(&mut input_file_buffer)?;
|
||||
|
||||
let mut elf_buffer = Vec::new();
|
||||
|
||||
if agb_gbafix::write_gba_file(
|
||||
&input_file_buffer,
|
||||
Default::default(),
|
||||
agb_gbafix::PaddingBehaviour::DoNotPad,
|
||||
&mut elf_buffer,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
Ok(elf_buffer)
|
||||
} else {
|
||||
Ok(input_file_buffer)
|
||||
}
|
||||
}
|
1
justfile
1
justfile
|
@ -20,6 +20,7 @@ test:
|
|||
just _test-debug tracker/agb-tracker
|
||||
just _test-debug-arm agb
|
||||
just _test-debug tools
|
||||
just _test-debug emulator
|
||||
|
||||
test-release:
|
||||
just _test-release agb
|
||||
|
|
231
mgba-test-runner/Cargo.lock
generated
231
mgba-test-runner/Cargo.lock
generated
|
@ -1,231 +0,0 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[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 = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "mgba-test-runner"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cc",
|
||||
"image",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
|
@ -1,13 +0,0 @@
|
|||
[package]
|
||||
name = "mgba-test-runner"
|
||||
version = "0.1.0"
|
||||
authors = ["Corwin Kuiper <corwin@kuiper.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
regex = "1"
|
||||
anyhow = "1"
|
||||
image = { version = "0.24", default-features = false, features = [ "png", "bmp" ] }
|
||||
|
||||
[build-dependencies]
|
||||
cc = { version = "1", features = ["parallel"] }
|
|
@ -1,40 +0,0 @@
|
|||
diff --git a/include/mgba/internal/gba/io.h b/include/mgba/internal/gba/io.h
|
||||
index 9875061f3..bdeafdcd3 100644
|
||||
--- a/include/mgba/internal/gba/io.h
|
||||
+++ b/include/mgba/internal/gba/io.h
|
||||
@@ -157,6 +157,7 @@ enum GBAIORegisters {
|
||||
REG_DEBUG_STRING = 0xFFF600,
|
||||
REG_DEBUG_FLAGS = 0xFFF700,
|
||||
REG_DEBUG_ENABLE = 0xFFF780,
|
||||
+ REG_DEBUG_CYCLES = 0xFFF800,
|
||||
};
|
||||
|
||||
mLOG_DECLARE_CATEGORY(GBA_IO);
|
||||
diff --git a/src/gba/io.c b/src/gba/io.c
|
||||
index cc39e1192..d34dcb4b4 100644
|
||||
--- a/src/gba/io.c
|
||||
+++ b/src/gba/io.c
|
||||
@@ -573,6 +573,11 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
||||
case REG_DEBUG_ENABLE:
|
||||
gba->debug = value == 0xC0DE;
|
||||
return;
|
||||
+ case REG_DEBUG_CYCLES: {
|
||||
+ int32_t number_of_cycles = mTimingCurrentTime(&gba->timing);
|
||||
+ mLOG(GBA_DEBUG, INFO, "Cycles: %d Tag: %d", number_of_cycles, value);
|
||||
+ return;
|
||||
+ }
|
||||
case REG_DEBUG_FLAGS:
|
||||
if (gba->debug) {
|
||||
GBADebug(gba, value);
|
||||
@@ -936,6 +941,11 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
||||
return 0x1DEA;
|
||||
}
|
||||
// Fall through
|
||||
+ case REG_DEBUG_CYCLES: {
|
||||
+ int32_t number_of_cycles = mTimingCurrentTime(&gba->timing);
|
||||
+ mLOG(GBA_DEBUG, INFO, "Cycles: %d", number_of_cycles);
|
||||
+ return number_of_cycles;
|
||||
+ }
|
||||
default:
|
||||
mLOG(GBA_IO, GAME_ERROR, "Read from unused I/O register: %03X", address);
|
||||
return GBALoadBad(gba->cpu);
|
|
@ -1,47 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
MGBA_VERSION=$1
|
||||
OUT_DIRECTORY=$2
|
||||
CURRENT_DIRECTORY=$(pwd)
|
||||
|
||||
cd "${OUT_DIRECTORY}" || exit
|
||||
|
||||
if [[ ! -f "mgba-${MGBA_VERSION}.tar.gz" ]]; then
|
||||
curl -L "https://github.com/mgba-emu/mgba/archive/refs/tags/${MGBA_VERSION}.tar.gz" -o "mgba-${MGBA_VERSION}.tar.gz"
|
||||
fi
|
||||
|
||||
if [[ -f libmgba-cycle.a ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
curl -L "https://github.com/mgba-emu/mgba/archive/refs/tags/${MGBA_VERSION}.tar.gz" -o "mgba-${MGBA_VERSION}.tar.gz"
|
||||
tar -xvf "mgba-${MGBA_VERSION}.tar.gz"
|
||||
cd "mgba-${MGBA_VERSION}" || exit
|
||||
rm -rf build
|
||||
patch --strip=1 < "${CURRENT_DIRECTORY}/add_cycles_register.patch"
|
||||
mkdir -p build
|
||||
cd build || exit
|
||||
cmake .. \
|
||||
-DBUILD_STATIC=ON \
|
||||
-DBUILD_SHARED=OFF \
|
||||
-DDISABLE_FRONTENDS=ON \
|
||||
-DBUILD_GL=OFF \
|
||||
-DBUILD_GLES2=OFF \
|
||||
-DUSE_GDB_STUB=OFF \
|
||||
-DUSE_FFMPEG=OFF \
|
||||
-DUSE_ZLIB=OFF \
|
||||
-DUSE_MINIZIP=OFF \
|
||||
-DUSE_PNG=OFF \
|
||||
-DUSE_LIBZIP=OFF \
|
||||
-DUSE_SQLITE3=OFF \
|
||||
-DUSE_ELF=ON \
|
||||
-DM_CORE_GBA=ON \
|
||||
-DM_CORE_GB=OFF \
|
||||
-DUSE_LZMA=OFF \
|
||||
-DUSE_DISCORD_RPC=OFF \
|
||||
-DENABLE_SCRIPTING=OFF \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DUSE_EPOXY=OFF
|
||||
make
|
||||
|
||||
cp libmgba.a ../../libmgba-cycle.a
|
|
@ -1,34 +0,0 @@
|
|||
use std::{env, path::PathBuf};
|
||||
|
||||
const MGBA_VERSION: &str = "0.9.1";
|
||||
|
||||
fn main() {
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
let mgba_directory = out_path.join(format!("mgba-{}", MGBA_VERSION));
|
||||
let out = std::process::Command::new("bash")
|
||||
.arg("build-mgba.sh")
|
||||
.arg(MGBA_VERSION)
|
||||
.arg(&out_path)
|
||||
.output()
|
||||
.expect("should be able to build mgba");
|
||||
if !out.status.success() {
|
||||
panic!(
|
||||
"failed to build mgba!\n{}",
|
||||
String::from_utf8_lossy(&out.stderr),
|
||||
);
|
||||
}
|
||||
|
||||
cc::Build::new()
|
||||
.file("c/test-runner.c")
|
||||
.include(&mgba_directory.join("include"))
|
||||
.static_flag(true)
|
||||
.debug(true)
|
||||
.compile("test-runner");
|
||||
|
||||
println!("cargo:rustc-link-search={}", out_path.to_str().unwrap());
|
||||
println!("cargo:rustc-link-lib=static=mgba-cycle");
|
||||
println!("cargo:rustc-link-lib=elf");
|
||||
|
||||
println!("cargo:rerun-if-changed=build-mgba.sh");
|
||||
println!("cargo:rerun-if-changed=add_cycles_register.patch");
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
#include "test-runner.h"
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/feature/commandline.h>
|
||||
#include <stdio.h>
|
||||
|
||||
_Static_assert(BYTES_PER_PIXEL == 4, "bytes per pixel MUST be four");
|
||||
|
||||
void log_output(struct mLogger* _log, int category, enum mLogLevel level,
|
||||
const char* format, va_list args);
|
||||
char* log_level_str(enum mLogLevel);
|
||||
|
||||
struct MGBA {
|
||||
struct mLogger mlogger;
|
||||
struct mCore* core;
|
||||
struct video_buffer videoBuffer;
|
||||
char* filename;
|
||||
struct callback callback;
|
||||
};
|
||||
|
||||
struct MGBA* new_runner(char* filename) {
|
||||
struct MGBA* mgba = calloc(1, sizeof(struct MGBA));
|
||||
mgba->mlogger.log = log_output;
|
||||
mgba->callback.callback = NULL;
|
||||
|
||||
mLogSetDefaultLogger(&mgba->mlogger);
|
||||
|
||||
char* filename_new = strdup(filename);
|
||||
mgba->filename = filename_new;
|
||||
|
||||
struct mCore* core = mCoreFind(mgba->filename);
|
||||
if (!core) {
|
||||
printf("failed to find core\n");
|
||||
free(mgba);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
core->init(core);
|
||||
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
ssize_t videoBufferSize = width * height * BYTES_PER_PIXEL;
|
||||
|
||||
uint32_t* videoBuffer = malloc(videoBufferSize * sizeof(*videoBuffer));
|
||||
|
||||
core->setVideoBuffer(core, videoBuffer, width);
|
||||
|
||||
// load rom
|
||||
mCoreLoadFile(core, mgba->filename);
|
||||
|
||||
mCoreConfigInit(&core->config, NULL);
|
||||
|
||||
core->reset(core);
|
||||
|
||||
mgba->core = core;
|
||||
mgba->videoBuffer = (struct video_buffer){
|
||||
.buffer = videoBuffer,
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
|
||||
return mgba;
|
||||
}
|
||||
|
||||
void set_logger(struct MGBA* mgba, struct callback callback) {
|
||||
mgba->callback = callback;
|
||||
}
|
||||
|
||||
void free_runner(struct MGBA* mgba) {
|
||||
mgba->core->deinit(mgba->core);
|
||||
mgba->callback.destroy(mgba->callback.data);
|
||||
free(mgba->filename);
|
||||
free(mgba->videoBuffer.buffer);
|
||||
free(mgba);
|
||||
}
|
||||
|
||||
void advance_frame(struct MGBA* mgba) { mgba->core->runFrame(mgba->core); }
|
||||
|
||||
struct video_buffer get_video_buffer(struct MGBA* mgba) {
|
||||
return mgba->videoBuffer;
|
||||
}
|
||||
|
||||
void log_output(struct mLogger* log, int category, enum mLogLevel level,
|
||||
const char* format, va_list args) {
|
||||
// cast log to mgba, this works as the logger is the top entry of the mgba
|
||||
// struct
|
||||
struct MGBA* mgba = (struct MGBA*)log;
|
||||
|
||||
if (level & 31) {
|
||||
int32_t size = 0;
|
||||
|
||||
size += snprintf(NULL, 0, "[%s] %s: ", log_level_str(level),
|
||||
mLogCategoryName(category));
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
size += vsnprintf(NULL, 0, format, args_copy);
|
||||
va_end(args_copy);
|
||||
size += 1;
|
||||
|
||||
char* str = calloc(size, sizeof(*str));
|
||||
|
||||
int32_t offset = snprintf(str, size, "[%s] %s: ", log_level_str(level),
|
||||
mLogCategoryName(category));
|
||||
size -= offset;
|
||||
vsnprintf(&str[offset], size, format, args);
|
||||
|
||||
if (mgba->callback.callback != NULL)
|
||||
mgba->callback.callback(mgba->callback.data, str);
|
||||
else
|
||||
printf("%s\n", str);
|
||||
|
||||
free(str);
|
||||
}
|
||||
}
|
||||
|
||||
char* log_level_str(enum mLogLevel level) {
|
||||
switch (level) {
|
||||
case mLOG_FATAL:
|
||||
return "FATAL";
|
||||
break;
|
||||
case mLOG_ERROR:
|
||||
return "ERROR";
|
||||
break;
|
||||
case mLOG_WARN:
|
||||
return "WARNING";
|
||||
break;
|
||||
case mLOG_INFO:
|
||||
return "INFO";
|
||||
break;
|
||||
case mLOG_DEBUG:
|
||||
return "DEBUG";
|
||||
break;
|
||||
case mLOG_STUB:
|
||||
return "STUB";
|
||||
break;
|
||||
case mLOG_GAME_ERROR:
|
||||
return "GAME ERROR";
|
||||
break;
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct MGBA;
|
||||
|
||||
struct video_buffer {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t* buffer;
|
||||
};
|
||||
|
||||
struct callback {
|
||||
void* data;
|
||||
void (*callback)(void*, char[]);
|
||||
void (*destroy)(void*);
|
||||
};
|
||||
|
||||
struct MGBA* new_runner(char filename[]);
|
||||
void free_runner(struct MGBA* mgba);
|
||||
void set_logger(struct MGBA*, struct callback);
|
||||
void advance_frame(struct MGBA* mgba);
|
||||
struct video_buffer get_video_buffer(struct MGBA* mgba);
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
bindgen c/test-runner.h -o src/bindings.rs
|
|
@ -1,330 +0,0 @@
|
|||
/* automatically generated by rust-bindgen 0.63.0 */
|
||||
|
||||
pub const _STDINT_H: u32 = 1;
|
||||
pub const _FEATURES_H: u32 = 1;
|
||||
pub const _DEFAULT_SOURCE: u32 = 1;
|
||||
pub const __GLIBC_USE_ISOC2X: u32 = 0;
|
||||
pub const __USE_ISOC11: u32 = 1;
|
||||
pub const __USE_ISOC99: u32 = 1;
|
||||
pub const __USE_ISOC95: u32 = 1;
|
||||
pub const __USE_POSIX_IMPLICITLY: u32 = 1;
|
||||
pub const _POSIX_SOURCE: u32 = 1;
|
||||
pub const _POSIX_C_SOURCE: u32 = 200809;
|
||||
pub const __USE_POSIX: u32 = 1;
|
||||
pub const __USE_POSIX2: u32 = 1;
|
||||
pub const __USE_POSIX199309: u32 = 1;
|
||||
pub const __USE_POSIX199506: u32 = 1;
|
||||
pub const __USE_XOPEN2K: u32 = 1;
|
||||
pub const __USE_XOPEN2K8: u32 = 1;
|
||||
pub const _ATFILE_SOURCE: u32 = 1;
|
||||
pub const __USE_MISC: u32 = 1;
|
||||
pub const __USE_ATFILE: u32 = 1;
|
||||
pub const __USE_FORTIFY_LEVEL: u32 = 0;
|
||||
pub const __GLIBC_USE_DEPRECATED_GETS: u32 = 0;
|
||||
pub const __GLIBC_USE_DEPRECATED_SCANF: u32 = 0;
|
||||
pub const _STDC_PREDEF_H: u32 = 1;
|
||||
pub const __STDC_IEC_559__: u32 = 1;
|
||||
pub const __STDC_IEC_559_COMPLEX__: u32 = 1;
|
||||
pub const __STDC_ISO_10646__: u32 = 201706;
|
||||
pub const __GNU_LIBRARY__: u32 = 6;
|
||||
pub const __GLIBC__: u32 = 2;
|
||||
pub const __GLIBC_MINOR__: u32 = 33;
|
||||
pub const _SYS_CDEFS_H: u32 = 1;
|
||||
pub const __glibc_c99_flexarr_available: u32 = 1;
|
||||
pub const __WORDSIZE: u32 = 64;
|
||||
pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1;
|
||||
pub const __SYSCALL_WORDSIZE: u32 = 64;
|
||||
pub const __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI: u32 = 0;
|
||||
pub const __HAVE_GENERIC_SELECTION: u32 = 1;
|
||||
pub const __GLIBC_USE_LIB_EXT2: u32 = 0;
|
||||
pub const __GLIBC_USE_IEC_60559_BFP_EXT: u32 = 0;
|
||||
pub const __GLIBC_USE_IEC_60559_BFP_EXT_C2X: u32 = 0;
|
||||
pub const __GLIBC_USE_IEC_60559_FUNCS_EXT: u32 = 0;
|
||||
pub const __GLIBC_USE_IEC_60559_FUNCS_EXT_C2X: u32 = 0;
|
||||
pub const __GLIBC_USE_IEC_60559_TYPES_EXT: u32 = 0;
|
||||
pub const _BITS_TYPES_H: u32 = 1;
|
||||
pub const __TIMESIZE: u32 = 64;
|
||||
pub const _BITS_TYPESIZES_H: u32 = 1;
|
||||
pub const __OFF_T_MATCHES_OFF64_T: u32 = 1;
|
||||
pub const __INO_T_MATCHES_INO64_T: u32 = 1;
|
||||
pub const __RLIM_T_MATCHES_RLIM64_T: u32 = 1;
|
||||
pub const __STATFS_MATCHES_STATFS64: u32 = 1;
|
||||
pub const __KERNEL_OLD_TIMEVAL_MATCHES_TIMEVAL64: u32 = 1;
|
||||
pub const __FD_SETSIZE: u32 = 1024;
|
||||
pub const _BITS_TIME64_H: u32 = 1;
|
||||
pub const _BITS_WCHAR_H: u32 = 1;
|
||||
pub const _BITS_STDINT_INTN_H: u32 = 1;
|
||||
pub const _BITS_STDINT_UINTN_H: u32 = 1;
|
||||
pub const INT8_MIN: i32 = -128;
|
||||
pub const INT16_MIN: i32 = -32768;
|
||||
pub const INT32_MIN: i32 = -2147483648;
|
||||
pub const INT8_MAX: u32 = 127;
|
||||
pub const INT16_MAX: u32 = 32767;
|
||||
pub const INT32_MAX: u32 = 2147483647;
|
||||
pub const UINT8_MAX: u32 = 255;
|
||||
pub const UINT16_MAX: u32 = 65535;
|
||||
pub const UINT32_MAX: u32 = 4294967295;
|
||||
pub const INT_LEAST8_MIN: i32 = -128;
|
||||
pub const INT_LEAST16_MIN: i32 = -32768;
|
||||
pub const INT_LEAST32_MIN: i32 = -2147483648;
|
||||
pub const INT_LEAST8_MAX: u32 = 127;
|
||||
pub const INT_LEAST16_MAX: u32 = 32767;
|
||||
pub const INT_LEAST32_MAX: u32 = 2147483647;
|
||||
pub const UINT_LEAST8_MAX: u32 = 255;
|
||||
pub const UINT_LEAST16_MAX: u32 = 65535;
|
||||
pub const UINT_LEAST32_MAX: u32 = 4294967295;
|
||||
pub const INT_FAST8_MIN: i32 = -128;
|
||||
pub const INT_FAST16_MIN: i64 = -9223372036854775808;
|
||||
pub const INT_FAST32_MIN: i64 = -9223372036854775808;
|
||||
pub const INT_FAST8_MAX: u32 = 127;
|
||||
pub const INT_FAST16_MAX: u64 = 9223372036854775807;
|
||||
pub const INT_FAST32_MAX: u64 = 9223372036854775807;
|
||||
pub const UINT_FAST8_MAX: u32 = 255;
|
||||
pub const UINT_FAST16_MAX: i32 = -1;
|
||||
pub const UINT_FAST32_MAX: i32 = -1;
|
||||
pub const INTPTR_MIN: i64 = -9223372036854775808;
|
||||
pub const INTPTR_MAX: u64 = 9223372036854775807;
|
||||
pub const UINTPTR_MAX: i32 = -1;
|
||||
pub const PTRDIFF_MIN: i64 = -9223372036854775808;
|
||||
pub const PTRDIFF_MAX: u64 = 9223372036854775807;
|
||||
pub const SIG_ATOMIC_MIN: i32 = -2147483648;
|
||||
pub const SIG_ATOMIC_MAX: u32 = 2147483647;
|
||||
pub const SIZE_MAX: i32 = -1;
|
||||
pub const WINT_MIN: u32 = 0;
|
||||
pub const WINT_MAX: u32 = 4294967295;
|
||||
pub type __u_char = ::std::os::raw::c_uchar;
|
||||
pub type __u_short = ::std::os::raw::c_ushort;
|
||||
pub type __u_int = ::std::os::raw::c_uint;
|
||||
pub type __u_long = ::std::os::raw::c_ulong;
|
||||
pub type __int8_t = ::std::os::raw::c_schar;
|
||||
pub type __uint8_t = ::std::os::raw::c_uchar;
|
||||
pub type __int16_t = ::std::os::raw::c_short;
|
||||
pub type __uint16_t = ::std::os::raw::c_ushort;
|
||||
pub type __int32_t = ::std::os::raw::c_int;
|
||||
pub type __uint32_t = ::std::os::raw::c_uint;
|
||||
pub type __int64_t = ::std::os::raw::c_long;
|
||||
pub type __uint64_t = ::std::os::raw::c_ulong;
|
||||
pub type __int_least8_t = __int8_t;
|
||||
pub type __uint_least8_t = __uint8_t;
|
||||
pub type __int_least16_t = __int16_t;
|
||||
pub type __uint_least16_t = __uint16_t;
|
||||
pub type __int_least32_t = __int32_t;
|
||||
pub type __uint_least32_t = __uint32_t;
|
||||
pub type __int_least64_t = __int64_t;
|
||||
pub type __uint_least64_t = __uint64_t;
|
||||
pub type __quad_t = ::std::os::raw::c_long;
|
||||
pub type __u_quad_t = ::std::os::raw::c_ulong;
|
||||
pub type __intmax_t = ::std::os::raw::c_long;
|
||||
pub type __uintmax_t = ::std::os::raw::c_ulong;
|
||||
pub type __dev_t = ::std::os::raw::c_ulong;
|
||||
pub type __uid_t = ::std::os::raw::c_uint;
|
||||
pub type __gid_t = ::std::os::raw::c_uint;
|
||||
pub type __ino_t = ::std::os::raw::c_ulong;
|
||||
pub type __ino64_t = ::std::os::raw::c_ulong;
|
||||
pub type __mode_t = ::std::os::raw::c_uint;
|
||||
pub type __nlink_t = ::std::os::raw::c_ulong;
|
||||
pub type __off_t = ::std::os::raw::c_long;
|
||||
pub type __off64_t = ::std::os::raw::c_long;
|
||||
pub type __pid_t = ::std::os::raw::c_int;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct __fsid_t {
|
||||
pub __val: [::std::os::raw::c_int; 2usize],
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout___fsid_t() {
|
||||
const UNINIT: ::std::mem::MaybeUninit<__fsid_t> = ::std::mem::MaybeUninit::uninit();
|
||||
let ptr = UNINIT.as_ptr();
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<__fsid_t>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(__fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<__fsid_t>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(__fsid_t))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).__val) as usize - ptr as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(__fsid_t),
|
||||
"::",
|
||||
stringify!(__val)
|
||||
)
|
||||
);
|
||||
}
|
||||
pub type __clock_t = ::std::os::raw::c_long;
|
||||
pub type __rlim_t = ::std::os::raw::c_ulong;
|
||||
pub type __rlim64_t = ::std::os::raw::c_ulong;
|
||||
pub type __id_t = ::std::os::raw::c_uint;
|
||||
pub type __time_t = ::std::os::raw::c_long;
|
||||
pub type __useconds_t = ::std::os::raw::c_uint;
|
||||
pub type __suseconds_t = ::std::os::raw::c_long;
|
||||
pub type __suseconds64_t = ::std::os::raw::c_long;
|
||||
pub type __daddr_t = ::std::os::raw::c_int;
|
||||
pub type __key_t = ::std::os::raw::c_int;
|
||||
pub type __clockid_t = ::std::os::raw::c_int;
|
||||
pub type __timer_t = *mut ::std::os::raw::c_void;
|
||||
pub type __blksize_t = ::std::os::raw::c_long;
|
||||
pub type __blkcnt_t = ::std::os::raw::c_long;
|
||||
pub type __blkcnt64_t = ::std::os::raw::c_long;
|
||||
pub type __fsblkcnt_t = ::std::os::raw::c_ulong;
|
||||
pub type __fsblkcnt64_t = ::std::os::raw::c_ulong;
|
||||
pub type __fsfilcnt_t = ::std::os::raw::c_ulong;
|
||||
pub type __fsfilcnt64_t = ::std::os::raw::c_ulong;
|
||||
pub type __fsword_t = ::std::os::raw::c_long;
|
||||
pub type __ssize_t = ::std::os::raw::c_long;
|
||||
pub type __syscall_slong_t = ::std::os::raw::c_long;
|
||||
pub type __syscall_ulong_t = ::std::os::raw::c_ulong;
|
||||
pub type __loff_t = __off64_t;
|
||||
pub type __caddr_t = *mut ::std::os::raw::c_char;
|
||||
pub type __intptr_t = ::std::os::raw::c_long;
|
||||
pub type __socklen_t = ::std::os::raw::c_uint;
|
||||
pub type __sig_atomic_t = ::std::os::raw::c_int;
|
||||
pub type int_least8_t = __int_least8_t;
|
||||
pub type int_least16_t = __int_least16_t;
|
||||
pub type int_least32_t = __int_least32_t;
|
||||
pub type int_least64_t = __int_least64_t;
|
||||
pub type uint_least8_t = __uint_least8_t;
|
||||
pub type uint_least16_t = __uint_least16_t;
|
||||
pub type uint_least32_t = __uint_least32_t;
|
||||
pub type uint_least64_t = __uint_least64_t;
|
||||
pub type int_fast8_t = ::std::os::raw::c_schar;
|
||||
pub type int_fast16_t = ::std::os::raw::c_long;
|
||||
pub type int_fast32_t = ::std::os::raw::c_long;
|
||||
pub type int_fast64_t = ::std::os::raw::c_long;
|
||||
pub type uint_fast8_t = ::std::os::raw::c_uchar;
|
||||
pub type uint_fast16_t = ::std::os::raw::c_ulong;
|
||||
pub type uint_fast32_t = ::std::os::raw::c_ulong;
|
||||
pub type uint_fast64_t = ::std::os::raw::c_ulong;
|
||||
pub type intmax_t = __intmax_t;
|
||||
pub type uintmax_t = __uintmax_t;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct MGBA {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct video_buffer {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub buffer: *mut u32,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_video_buffer() {
|
||||
const UNINIT: ::std::mem::MaybeUninit<video_buffer> = ::std::mem::MaybeUninit::uninit();
|
||||
let ptr = UNINIT.as_ptr();
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<video_buffer>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(video_buffer))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<video_buffer>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(video_buffer))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).width) as usize - ptr as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(video_buffer),
|
||||
"::",
|
||||
stringify!(width)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).height) as usize - ptr as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(video_buffer),
|
||||
"::",
|
||||
stringify!(height)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).buffer) as usize - ptr as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(video_buffer),
|
||||
"::",
|
||||
stringify!(buffer)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct callback {
|
||||
pub data: *mut ::std::os::raw::c_void,
|
||||
pub callback: ::std::option::Option<
|
||||
unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void, arg2: *mut ::std::os::raw::c_char),
|
||||
>,
|
||||
pub destroy: ::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_callback() {
|
||||
const UNINIT: ::std::mem::MaybeUninit<callback> = ::std::mem::MaybeUninit::uninit();
|
||||
let ptr = UNINIT.as_ptr();
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<callback>(),
|
||||
24usize,
|
||||
concat!("Size of: ", stringify!(callback))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<callback>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(callback))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).data) as usize - ptr as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(callback),
|
||||
"::",
|
||||
stringify!(data)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).callback) as usize - ptr as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(callback),
|
||||
"::",
|
||||
stringify!(callback)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).destroy) as usize - ptr as usize },
|
||||
16usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(callback),
|
||||
"::",
|
||||
stringify!(destroy)
|
||||
)
|
||||
);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn new_runner(filename: *mut ::std::os::raw::c_char) -> *mut MGBA;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn free_runner(mgba: *mut MGBA);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn set_logger(arg1: *mut MGBA, arg2: callback);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn advance_frame(mgba: *mut MGBA);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn get_video_buffer(mgba: *mut MGBA) -> video_buffer;
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
#![allow(clippy::all)]
|
||||
|
||||
mod bindings;
|
||||
mod runner;
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use image::io::Reader;
|
||||
use image::GenericImage;
|
||||
use io::Write;
|
||||
use regex::Regex;
|
||||
use runner::VideoBuffer;
|
||||
use std::cell::Cell;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
enum Status {
|
||||
Running,
|
||||
Failed,
|
||||
Success,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum Timing {
|
||||
None,
|
||||
WaitFor(i32),
|
||||
Difference(i32),
|
||||
}
|
||||
|
||||
const TEST_RUNNER_TAG: u16 = 785;
|
||||
|
||||
fn test_file(file_to_run: &str) -> Status {
|
||||
let finished = Rc::new(Cell::new(Status::Running));
|
||||
let debug_reader_mutex = Regex::new(r"(?s)^\[(.*)\] GBA Debug: (.*)$").unwrap();
|
||||
let tagged_cycles_reader = Regex::new(r"Cycles: (\d*) Tag: (\d*)").unwrap();
|
||||
|
||||
let mut mgba = runner::MGBA::new(file_to_run).unwrap();
|
||||
|
||||
{
|
||||
let finished = finished.clone();
|
||||
let video_buffer = mgba.get_video_buffer();
|
||||
let number_of_cycles = Cell::new(Timing::None);
|
||||
|
||||
mgba.set_logger(move |message| {
|
||||
if let Some(captures) = debug_reader_mutex.captures(message) {
|
||||
let log_level = &captures[1];
|
||||
let out = &captures[2];
|
||||
|
||||
if out.starts_with("image:") {
|
||||
let image_path = out.strip_prefix("image:").unwrap();
|
||||
match check_image_match(image_path, &video_buffer) {
|
||||
Err(e) => {
|
||||
println!("[failed]");
|
||||
println!("{}", e);
|
||||
finished.set(Status::Failed);
|
||||
}
|
||||
Ok(_) => {}
|
||||
}
|
||||
} else if out.ends_with("...") {
|
||||
print!("{}", out);
|
||||
io::stdout().flush().expect("can't flush stdout");
|
||||
} else if out.starts_with("Cycles: ") {
|
||||
if let Some(captures) = tagged_cycles_reader.captures(out) {
|
||||
let num_cycles: i32 = captures[1].parse().unwrap();
|
||||
let tag: u16 = captures[2].parse().unwrap();
|
||||
|
||||
if tag == TEST_RUNNER_TAG {
|
||||
number_of_cycles.set(match number_of_cycles.get() {
|
||||
Timing::WaitFor(n) => Timing::Difference(num_cycles - n),
|
||||
Timing::None => Timing::WaitFor(num_cycles),
|
||||
Timing::Difference(_) => Timing::WaitFor(num_cycles),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if out == "[ok]" {
|
||||
if let Timing::Difference(cycles) = number_of_cycles.get() {
|
||||
println!(
|
||||
"[ok: {} c ≈ {} s]",
|
||||
cycles,
|
||||
((cycles as f64 / (16.78 * 1_000_000.0)) * 100.0).round() / 100.0
|
||||
);
|
||||
} else {
|
||||
println!("{}", out);
|
||||
}
|
||||
} else {
|
||||
println!("{}", out);
|
||||
}
|
||||
|
||||
if log_level == "FATAL" {
|
||||
finished.set(Status::Failed);
|
||||
}
|
||||
|
||||
if out == "Tests finished successfully" {
|
||||
finished.set(Status::Success);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loop {
|
||||
mgba.advance_frame();
|
||||
if finished.get() != Status::Running {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return finished.get();
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let file_to_run = args.get(1).expect("you should provide file to run");
|
||||
|
||||
if !Path::new(file_to_run).exists() {
|
||||
return Err(anyhow!("File to run should exist!"));
|
||||
}
|
||||
|
||||
let output = test_file(file_to_run);
|
||||
|
||||
match output {
|
||||
Status::Failed => Err(anyhow!("Tests failed!")),
|
||||
Status::Success => Ok(()),
|
||||
_ => {
|
||||
unreachable!("very bad thing happened");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gba_colour_to_rgba(colour: u32) -> [u8; 4] {
|
||||
[
|
||||
((colour >> 0) & 0xFF) as u8,
|
||||
((colour >> 8) & 0xFF) as u8,
|
||||
((colour >> 16) & 0xFF) as u8,
|
||||
255,
|
||||
]
|
||||
}
|
||||
|
||||
fn rgba_to_gba_to_rgba(c: [u8; 4]) -> [u8; 4] {
|
||||
let mut n = c.clone();
|
||||
n.iter_mut()
|
||||
.for_each(|a| *a = ((((*a as u32 >> 3) << 3) * 0x21) >> 5) as u8);
|
||||
n
|
||||
}
|
||||
|
||||
fn check_image_match(image_path: &str, video_buffer: &VideoBuffer) -> Result<(), Error> {
|
||||
let expected_image = Reader::open(image_path)?.decode()?;
|
||||
let expected = expected_image.to_rgba8();
|
||||
|
||||
let (buf_dim_x, buf_dim_y) = video_buffer.get_size();
|
||||
let (exp_dim_x, exp_dim_y) = expected.dimensions();
|
||||
if (buf_dim_x != exp_dim_x) || (buf_dim_y != exp_dim_y) {
|
||||
return Err(anyhow!("image sizes do not match"));
|
||||
}
|
||||
|
||||
for y in 0..buf_dim_y {
|
||||
for x in 0..buf_dim_x {
|
||||
let video_pixel = video_buffer.get_pixel(x, y);
|
||||
let image_pixel = expected.get_pixel(x, y);
|
||||
let video_pixel = gba_colour_to_rgba(video_pixel);
|
||||
let image_pixel = rgba_to_gba_to_rgba(image_pixel.0);
|
||||
if image_pixel != video_pixel {
|
||||
let output_file = write_video_buffer(video_buffer);
|
||||
|
||||
return Err(anyhow!(
|
||||
"images do not match, actual output written to {}",
|
||||
output_file
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_video_buffer(video_buffer: &VideoBuffer) -> String {
|
||||
let (width, height) = video_buffer.get_size();
|
||||
let mut output_image = image::DynamicImage::new_rgba8(width, height);
|
||||
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let pixel = video_buffer.get_pixel(x, y);
|
||||
let pixel_as_rgba = gba_colour_to_rgba(pixel);
|
||||
|
||||
output_image.put_pixel(x, y, pixel_as_rgba.into())
|
||||
}
|
||||
}
|
||||
|
||||
let output_folder = std::env::temp_dir();
|
||||
let output_file = "mgba-test-runner-output.png"; // TODO make this random
|
||||
|
||||
let output_file = output_folder.join(output_file);
|
||||
let _ = output_image.save_with_format(&output_file, image::ImageFormat::Png);
|
||||
|
||||
output_file.to_string_lossy().into_owned()
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
use crate::bindings;
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::CString;
|
||||
use std::marker::PhantomData;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
#[allow(
|
||||
non_upper_case_globals,
|
||||
dead_code,
|
||||
non_camel_case_types,
|
||||
non_snake_case
|
||||
)]
|
||||
|
||||
pub struct MGBA<'a> {
|
||||
mgba: *mut bindings::MGBA,
|
||||
_phantom: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
pub struct VideoBuffer {
|
||||
width: u32,
|
||||
height: u32,
|
||||
buffer: *mut u32,
|
||||
}
|
||||
|
||||
impl VideoBuffer {
|
||||
pub fn get_size(&self) -> (u32, u32) {
|
||||
(self.width, self.height)
|
||||
}
|
||||
pub fn get_pixel(&self, x: u32, y: u32) -> u32 {
|
||||
let offset = (y * self.width + x) as isize;
|
||||
assert!(x < self.width, "x must be in range 0 to {}", self.width);
|
||||
assert!(y < self.height, "y must be in range 0 to {}", self.height);
|
||||
unsafe { *self.buffer.offset(offset) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MGBA<'a> {
|
||||
pub fn new(filename: &str) -> Result<Self, anyhow::Error> {
|
||||
let c_str = CString::new(filename).expect("should be able to make cstring from filename");
|
||||
let mgba = unsafe { bindings::new_runner(c_str.as_ptr() as *mut c_char) };
|
||||
if mgba.is_null() {
|
||||
Err(anyhow::anyhow!("could not create core"))
|
||||
} else {
|
||||
Ok(MGBA {
|
||||
mgba,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_video_buffer(&self) -> VideoBuffer {
|
||||
let c_video_buffer = unsafe { bindings::get_video_buffer(self.mgba) };
|
||||
VideoBuffer {
|
||||
width: c_video_buffer.width,
|
||||
height: c_video_buffer.height,
|
||||
buffer: c_video_buffer.buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_frame(&mut self) {
|
||||
unsafe { bindings::advance_frame(self.mgba) }
|
||||
}
|
||||
pub fn set_logger(&mut self, logger: impl Fn(&str) + 'a) {
|
||||
unsafe {
|
||||
let callback = generate_c_callback(move |message: *mut c_char| {
|
||||
logger(
|
||||
CStr::from_ptr(message)
|
||||
.to_str()
|
||||
.expect("should be able to convert logging message to rust String"),
|
||||
);
|
||||
});
|
||||
bindings::set_logger(self.mgba, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn generate_c_callback<F>(f: F) -> bindings::callback
|
||||
where
|
||||
F: FnMut(*mut c_char),
|
||||
{
|
||||
let data = Box::into_raw(Box::new(f));
|
||||
|
||||
bindings::callback {
|
||||
callback: Some(call_closure::<F>),
|
||||
data: data as *mut _,
|
||||
destroy: Some(drop_box::<F>),
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn call_closure<F>(data: *mut c_void, message: *mut c_char)
|
||||
where
|
||||
F: FnMut(*mut c_char),
|
||||
{
|
||||
let callback_ptr = data as *mut F;
|
||||
let callback = unsafe { &mut *callback_ptr };
|
||||
callback(message);
|
||||
}
|
||||
|
||||
extern "C" fn drop_box<T>(data: *mut c_void) {
|
||||
unsafe {
|
||||
Box::from_raw(data as *mut T);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MGBA<'_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { bindings::free_runner(self.mgba) }
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue