diff options
author | Astatin <[email protected]> | 2024-08-29 22:32:10 +0900 |
---|---|---|
committer | Astatin <astatin@redacted> | 2024-08-29 22:32:10 +0900 |
commit | 09104524ed468442bb4957d24e65395955b59ddd (patch) | |
tree | 0aaad5952df29e0a920660c422e1aaf8acea0123 |
I FINALLY COMMITTED IT ! AND YOU WILL HAVE NO GIT HISTORY BC IT'S TO HARD NOT TO DOX MYSELF
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Cargo.lock | 1761 | ||||
-rw-r--r-- | Cargo.toml | 16 | ||||
-rw-r--r-- | README.md | 51 | ||||
-rw-r--r-- | assets/README | 1 | ||||
-rw-r--r-- | src/audio.rs | 561 | ||||
-rw-r--r-- | src/consts.rs | 12 | ||||
-rw-r--r-- | src/display.rs | 359 | ||||
-rw-r--r-- | src/gamepad.rs | 89 | ||||
-rw-r--r-- | src/interrupts_timers.rs | 67 | ||||
-rw-r--r-- | src/io.rs | 257 | ||||
-rw-r--r-- | src/main.rs | 142 | ||||
-rw-r--r-- | src/opcodes.rs | 825 | ||||
-rw-r--r-- | src/state.rs | 377 |
14 files changed, 4522 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7841fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target/ +debug.txt +assets/cgb_boot.bin +assets/dmg_boot.bin diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6cce246 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1761 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44" +dependencies = [ + "alsa-sys", + "bitflags 1.3.2", + "libc", + "nix 0.24.3", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[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 = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[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 0.7.4", +] + +[[package]] +name = "clap" +version = "4.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +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 2.0.24", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "claxon" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys 0.8.4", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "coreaudio-rs" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb17e2d1795b1996419648915df94bc7103c28f7b48062d7acf4652fc371b2ff" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys 0.6.2", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f034b2258e6c4ade2f73bf87b21047567fb913ee9550837c2316d139b0262b24" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d959d90e938c5493000514b446987c07aed46c668faaa7d34d6c7a67b1a578c" +dependencies = [ + "alsa", + "core-foundation-sys 0.8.4", + "coreaudio-rs", + "dasp_sample", + "jni 0.19.0", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "oboe", + "once_cell", + "parking_lot", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.0", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "emulator" +version = "0.1.0" +dependencies = [ + "clap", + "gilrs", + "minifb", + "rodio", +] + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.24", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gilrs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62fd19844d0eb919aca41d3e4ea0e0b6bf60e1e827558b101c269015b8f5f27a" +dependencies = [ + "fnv", + "gilrs-core", + "log", + "uuid", + "vec_map", +] + +[[package]] +name = "gilrs-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85b0f27572f0560cfc4a067a2978a4a490f9fa5cf1326d30b142a288312a965" +dependencies = [ + "core-foundation", + "io-kit-sys", + "js-sys", + "libc", + "libudev-sys", + "log", + "nix 0.26.2", + "uuid", + "vec_map", + "wasm-bindgen", + "web-sys", + "windows", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[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 = "hound" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1" + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "io-kit-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2d4429acc1deff0fbdece0325b4997bdb02b2c245ab7023fd5deca0f6348de" +dependencies = [ + "core-foundation-sys 0.8.4", + "mach2", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix 0.38.8", + "windows-sys", +] + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[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 = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + +[[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 = "libloading" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +dependencies = [ + "cfg-if", + "windows-sys", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "mach2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minifb" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66a1fdd7e946fe33fe9725012e25836bba3655769bee9ee347cce7de3f396df" +dependencies = [ + "cc", + "dlib", + "futures", + "instant", + "js-sys", + "lazy_static", + "libc", + "orbclient", + "raw-window-handle 0.4.3", + "serde", + "serde_derive", + "tempfile", + "wasm-bindgen-futures", + "wayland-client", + "wayland-cursor", + "wayland-protocols", + "winapi", + "x11-dl", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags 1.3.2", + "jni-sys", + "ndk-sys", + "num_enum", + "raw-window-handle 0.5.2", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "static_assertions", +] + +[[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-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "oboe" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8868cc237ee02e2d9618539a23a8d228b9bb3fc2e7a5b11eed3831de77c395d0" +dependencies = [ + "jni 0.20.0", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f44155e7fb718d3cfddcf70690b2b51ac4412f347cd9e4fbe511abe9cd7b5f2" +dependencies = [ + "cc", +] + +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "orbclient" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221d488cd70617f1bd599ed8ceb659df2147d9393717954d82a0f5e8032a6ab1" +dependencies = [ + "libc", + "redox_syscall", + "sdl2", + "sdl2-sys", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.1", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pin-project-lite" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "raw-window-handle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +dependencies = [ + "cty", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" + +[[package]] +name = "rodio" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf1d4dea18dff2e9eb6dca123724f8b60ef44ad74a9ad283cdfe025df7e73fa" +dependencies = [ + "claxon", + "cpal", + "hound", + "lewton", + "symphonia", +] + +[[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.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys", +] + +[[package]] +name = "rustix" +version = "0.38.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.5", + "windows-sys", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sdl2" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a" +dependencies = [ + "bitflags 1.3.2", + "lazy_static", + "libc", + "sdl2-sys", +] + +[[package]] +name = "sdl2-sys" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0" +dependencies = [ + "cfg-if", + "libc", + "version-compare", +] + +[[package]] +name = "serde" +version = "1.0.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd51c3db8f9500d531e6c12dd0fd4ad13d133e9117f5aebac3cdbb8b6d9824b0" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27738cfea0d944ab72c3ed01f3d5f23ec4322af8a1431e40ce630e4c01ea74fd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.24", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "symphonia" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e48dba70095f265fdb269b99619b95d04c89e619538138383e63310b14d941" +dependencies = [ + "lazy_static", + "symphonia-bundle-mp3", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f31d7fece546f1e6973011a9eceae948133bbd18fd3d52f6073b1e38ae6368a" +dependencies = [ + "bitflags 1.3.2", + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c73eb88fee79705268cc7b742c7bc93a7b76e092ab751d0833866970754142" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c3e1937e31d0e068bbe829f66b2f2bfaa28d056365279e0ef897172c3320c0" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36ccaf716a23c35ff908f91c971a86a9a71af5998c1d8f10e828d9f55f68ac00" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg", + "cfg-if", + "fastrand", + "redox_syscall", + "rustix 0.37.23", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.24", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.24", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.24", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags 1.3.2", + "downcast-rs", + "libc", + "nix 0.24.3", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.3", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags 1.3.2", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" +dependencies = [ + "memchr", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xml-rs" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a56c84a8ccd4258aed21c92f70c0f6dea75356b6892ae27c24139da456f9336" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7f19bf6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "emulator" +version = "0.1.0" +edition = "2021" +authors = ["Astatin <astatin@redacted>"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +minifb = "0.24" +rodio = "0.17" +gilrs = "0.10.2" +clap = { version = "4.3.21", features = ["derive"] } + +[profile.dev] +overflow-checks = false diff --git a/README.md b/README.md new file mode 100644 index 0000000..abce9c2 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# Astatin Emulator + +Hii !! This is the emulator I use to make [BunnyLand (Temporary name)](https://github.com/AstatinChan/BunnyLand-Gameboy) on [stream on Twitch](https://www.twitch.tv/astatinchan) + +# Building the emulator + +For reasons related to my Japanese visa, I will not distribute the boot roms in this repo, but I'm sure you know how to use the internet. + +You need to put the dmg boot rom in the file `assets/dmg_boot.bin` and the cgb boot rom in the file `assets/cgb_boot.bin`. + +When you have both the boot roms ready, build the emulator with `cargo build --release`. It will give you a binary in `target/release/emulator`. + +# Usage + +The basic usage is just to provide a rom as a first argument: +```bash +emulator <gameboy_rom> +``` + +## Gamepad + +This emulator needs a gamepad to be connected to play. There is no keyboard option for now. + +If there is the message `No gamepad found` in the first messages, it means your gamepad hasn't been detected or initialized properly. Connect a gamepad and restart the emulator to fix it. It should print `Found Gamepad id: GamepadId(0)` instead. + +(I don't know which gamepad exactly. The 8BitDo SF30 Pro in USB mode on Arch Linux works. That's all I know lol) + +## CPU Usage + +By default the emulator will spin lock instead of using thread::sleep (Bc I'm bad at programming and for some reason I can't manage to get an accurate time using thread::sleep) + +If you're on battery or the 100% CPU usage bothers you, you can use the --thread-sleep option, though it might cause some lags and inaccurate timing. + +```bash +emulator <gameboy_rom> --thread-sleep +``` + +## Speed + +You can adjust the speed with the `-s` argument + +This command makes it run at 2x speed +```bash +emulator <gameboy_rom> -s 2 +``` + +# Contributing + +This emulator is not the fastest one, the most accurate one or the most well made. I'm not even sure in which environment it works (I never tested it on windows). I just made it because it's fun and it's a good way to learn how the gameboy works in detail. + +For this reason, I'm not entirely sure what I would do if someone were to open a PR. If you find a bug or want to change something, I would be more confortable if you talked about it with me on stream or [on discord](https://discord.com/invite/XVTCuYJh) before. diff --git a/assets/README b/assets/README new file mode 100644 index 0000000..73a02c1 --- /dev/null +++ b/assets/README @@ -0,0 +1 @@ +See ? I even created the directory for you. You have NO EXCUSE ! diff --git a/src/audio.rs b/src/audio.rs new file mode 100644 index 0000000..07f7c85 --- /dev/null +++ b/src/audio.rs @@ -0,0 +1,561 @@ +// You are entering a very scary territory of magic numbers and arbitrary math operations. +// I don't remember why I did all of this but it works I guess :3 + +use rodio::{OutputStream, Sink, Source}; + +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +const SAMPLE_RATE: u32 = 65536; + +const SAMPLE_AVERAGING: usize = 5; //20; + +const SQUARE_WAVE_PATTERN_DUTY_0: [u8; 32] = [ + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0, 0, 0, 0, +]; + +const SQUARE_WAVE_PATTERN_DUTY_1: [u8; 32] = [ + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const SQUARE_WAVE_PATTERN_DUTY_2: [u8; 32] = [ + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const SQUARE_WAVE_PATTERN_DUTY_3: [u8; 32] = [ + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, +]; + +/* + * Sometimes, you have to abandon the idea of doing things the right way and just do the thing. + * + * The Gameboy noise wave generation function is stateful and the sample averaging feature (a + * low pass filter to filter out some weird noises created by the "too squared" nature of our + * sound wave) needs a pure function (and fast). It's too hard so I just computed the result + * of the noise function and put it here. It's O(1) :3 + */ + +const NOISE_WAVE: [u16; 1023] = [ + 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1 +]; + +const SQUARE_WAVE_PATTERNS: [[u8; 32]; 4] = [ + SQUARE_WAVE_PATTERN_DUTY_0, + SQUARE_WAVE_PATTERN_DUTY_1, + SQUARE_WAVE_PATTERN_DUTY_2, + SQUARE_WAVE_PATTERN_DUTY_3, +]; + +#[derive(Clone, Debug)] +pub struct Wave { + period_value: u16, + num_sample: usize, + wave_pattern: [u8; 32], + length_timer: u8, + length_timer_enabled: bool, + + env_initial_volume: f32, + env_direction: f32, + env_sweep_pace: u8, + + period_sweep_pace: u8, + period_sweep_direction: u8, + period_sweep_slope: u8, +} + +impl Wave { + pub fn new( + period_value: u16, + wave_pattern: [u8; 32], + env_initial_volume: u8, + env_direction: u8, + env_sweep_pace: u8, + length_timer: u8, + length_timer_enabled: bool, + period_sweep_pace: u8, + period_sweep_direction: u8, + period_sweep_slope: u8, + ) -> Wave { + Wave { + period_value, + num_sample: 0, + wave_pattern, + env_initial_volume: env_initial_volume as f32, + env_direction: if env_direction == 0 { -1. } else { 1. }, + env_sweep_pace, + length_timer, + length_timer_enabled, + period_sweep_pace, + period_sweep_direction, + period_sweep_slope, + } + } +} + +impl Iterator for Wave { + type Item = f32; + + fn next(&mut self) -> Option<f32> { + self.num_sample = self.num_sample.wrapping_add(1); + + let mut period_value = self.period_value; + + if period_value == 0 { + return None; + } + + if self.length_timer_enabled + && self.length_timer < 64 + && SAMPLE_RATE * (64 - self.length_timer as u32) / 256 < self.num_sample as u32 + { + return None; + } + + if self.period_sweep_slope != 0 && self.period_sweep_pace != 0 { + let sweep_i = ((self.num_sample as f32 * (32768 as f32 / SAMPLE_RATE as f32)) as u32 + / 256) + / self.period_sweep_pace as u32; + + if self.period_sweep_direction == 0 { + period_value = 2048 + - ((2048 - period_value) as f32 + * f32::powf( + f32::powf(2., -(self.period_sweep_slope as f32)) + 1., + sweep_i as f32, + )) as u16; + } else { + period_value = 2048 + - ((2048 - period_value) as f32 + * f32::powf( + -f32::powf(2., -(self.period_sweep_slope as f32)) + 1., + sweep_i as f32, + )) as u16; + } + + if period_value > 2048 { + return None; + } + } + + let envelope_time = if self.env_sweep_pace != 0 { + (self.num_sample as f32 / SAMPLE_RATE as f32) * 64. / self.env_sweep_pace as f32 + } else { + 0. + }; + + let envelope = self.env_initial_volume + (self.env_direction * envelope_time); + + let envelope_boundaries = if envelope > 16. { + 16. + } else if envelope < 0. { + 0. + } else { + envelope + }; + + let mut avg = 0.; + + for n in 0..SAMPLE_AVERAGING { + if self.num_sample as i32 + n as i32 - SAMPLE_AVERAGING as i32 >= 0 { + avg += (self.wave_pattern[(((8. * 32768. / (SAMPLE_RATE as f32) + * (self.num_sample + n - (SAMPLE_AVERAGING / 2)) as f32 + / period_value as f32) + * 16.) + % 32.) as u8 as usize] as f32 + * 2. + - 16.) + / 16.; // Before you ask, no I don't remember why it's so complicated :3 + } + } + + Some((avg / SAMPLE_AVERAGING as f32) * envelope_boundaries / 64.) + } +} + +#[derive(Clone, Debug)] +pub struct NoiseWave { + num_sample: usize, + length_timer: u8, + length_timer_enabled: bool, + + env_initial_volume: f32, + env_direction: f32, + env_sweep_pace: u8, + + clock_shift: u8, + lsfr_width: u8, + clock_divider: u8, +} + +impl NoiseWave { + pub fn new( + env_initial_volume: u8, + env_direction: u8, + env_sweep_pace: u8, + length_timer: u8, + length_timer_enabled: bool, + clock_shift: u8, + lsfr_width: u8, + clock_divider: u8, + ) -> NoiseWave { + NoiseWave { + num_sample: 0, + env_initial_volume: env_initial_volume as f32, + env_direction: if env_direction == 0 { -1. } else { 1. }, + env_sweep_pace, + length_timer, + length_timer_enabled, + clock_shift, + lsfr_width, + clock_divider, + } + } +} + +impl Iterator for NoiseWave { + type Item = f32; + + fn next(&mut self) -> Option<f32> { + self.num_sample = self.num_sample.wrapping_add(1); + + let clock_divider = if self.clock_divider == 0 { + 0.5 + } else { + self.clock_divider as f32 + }; + + if self.length_timer_enabled + && self.length_timer < 64 + && SAMPLE_RATE * (64 - self.length_timer as u32) / 256 < self.num_sample as u32 + { + return None; + } + + let envelope_time = if self.env_sweep_pace != 0 { + (self.num_sample as f32 / SAMPLE_RATE as f32) * 64. / self.env_sweep_pace as f32 + } else { + 0. + }; + + let envelope = self.env_initial_volume + (self.env_direction * envelope_time); + + let envelope_boundaries = if envelope > 16. { + 16. + } else if envelope < 0. { + 0. + } else { + envelope + }; + + let mut avg = 0.; + + for n in 0..SAMPLE_AVERAGING { + if self.num_sample as i32 + n as i32 - SAMPLE_AVERAGING as i32 >= 0 { + let ns = ((262144. / ((clock_divider) * (2 << self.clock_shift) as f32)) / 32768.) + * (self.num_sample + n - (SAMPLE_AVERAGING / 2)) as f32; + + let i = (ns as f32 * (32768 as f32 / SAMPLE_RATE as f32)) as usize; + + let up = if self.lsfr_width == 1 { + NOISE_WAVE[i % 63] + } else { + NOISE_WAVE[i % 1023] + }; + + avg += up as f32 * 2. - 1.; + } + } + + Some((avg / SAMPLE_AVERAGING as f32) * envelope_boundaries / 64.) + } +} + +#[derive(Clone, Debug)] +struct MutableWave { + wave_ch1: Arc<Mutex<Option<Wave>>>, + wave_ch2: Arc<Mutex<Option<Wave>>>, + wave_ch3: Arc<Mutex<Option<Wave>>>, + wave_ch4: Arc<Mutex<Option<NoiseWave>>>, +} + +impl MutableWave { + pub fn new( + wave_ch1: Arc<Mutex<Option<Wave>>>, + wave_ch2: Arc<Mutex<Option<Wave>>>, + wave_ch3: Arc<Mutex<Option<Wave>>>, + wave_ch4: Arc<Mutex<Option<NoiseWave>>>, + ) -> Self { + Self { + wave_ch1, + wave_ch2, + wave_ch3, + wave_ch4, + } + } +} + +impl Iterator for MutableWave { + type Item = f32; + + fn next(&mut self) -> Option<f32> { + let mut res = 0.; + + // Imagine using an Arc<Mutex<>> in a sound wave generation function that needs to reliably + // run 65536 times a second. Couldn't be me :3 + if let Ok(mut wave_o) = self.wave_ch1.lock() { + if let Some(wave) = wave_o.as_mut() { + if let Some(result) = wave.next() { + res += result / 4.; + } else { + *wave_o = None; + } + } + } + + if let Ok(mut wave_o) = self.wave_ch2.lock() { + if let Some(wave) = wave_o.as_mut() { + if let Some(result) = wave.next() { + res += result / 4.; + } else { + *wave_o = None; + } + } + } + + if let Ok(mut wave_o) = self.wave_ch3.lock() { + if let Some(wave) = wave_o.as_mut() { + if let Some(result) = wave.next() { + res += result / 4.; + } else { + *wave_o = None; + } + } + } + + if let Ok(mut wave_o) = self.wave_ch4.lock() { + if let Some(wave) = wave_o.as_mut() { + if let Some(result) = wave.next() { + res += result / 4.; + } else { + *wave_o = None; + } + } + } + + Some(res) + } +} + +impl Source for MutableWave { + fn current_frame_len(&self) -> Option<usize> { + None + } + + fn channels(&self) -> u16 { + 1 + } + + fn sample_rate(&self) -> u32 { + SAMPLE_RATE + } + + fn total_duration(&self) -> Option<Duration> { + None + } +} + +pub struct AudioSquareChannel { + wave: Arc<Mutex<Option<Wave>>>, + + pub length_timer: u8, + pub length_timer_enabled: bool, + pub on: bool, + pub period_value: u16, + pub duty: u8, + pub initial_volume: u8, + pub env_direction: u8, + pub sweep: u8, + pub period_sweep_pace: u8, + pub period_sweep_direction: u8, + pub period_sweep_slope: u8, +} + +impl AudioSquareChannel { + pub fn new(wave: Arc<Mutex<Option<Wave>>>) -> Self { + Self { + on: true, + period_value: 0, + duty: 0, + initial_volume: 0, + env_direction: 0, + sweep: 0, + wave, + length_timer: 0, + length_timer_enabled: false, + period_sweep_pace: 0, + period_sweep_direction: 0, + period_sweep_slope: 0, + } + } + + pub fn update(&mut self) { + if let Ok(mut wave) = self.wave.lock() { + if self.on { + *wave = Some(Wave::new( + 2048 - self.period_value, + SQUARE_WAVE_PATTERNS[self.duty as usize], + self.initial_volume, + self.env_direction, + self.sweep, + self.length_timer, + self.length_timer_enabled, + self.period_sweep_pace, + self.period_sweep_direction, + self.period_sweep_slope, + )); + } else { + *wave = None; + } + } + } +} + +pub struct AudioCustomChannel { + wave: Arc<Mutex<Option<Wave>>>, + + pub length_timer: u8, + pub length_timer_enabled: bool, + pub wave_pattern: [u8; 32], + pub on: bool, + pub period_value: u16, + pub initial_volume: u8, +} + +impl AudioCustomChannel { + pub fn new(wave: Arc<Mutex<Option<Wave>>>) -> Self { + Self { + wave_pattern: [0; 32], + on: true, + period_value: 0, + initial_volume: 0, + wave, + length_timer: 0, + length_timer_enabled: false, + } + } + + pub fn update(&mut self) { + if let Ok(mut wave) = self.wave.lock() { + if self.on { + *wave = Some(Wave::new( + 2 * (2048 - (self.period_value * 2)), + self.wave_pattern, + self.initial_volume, + 0, + 0, + self.length_timer, + self.length_timer_enabled, + 0, + 0, + 0, + )); + } else { + *wave = None; + } + } + } +} + +pub struct AudioNoiseChannel { + wave: Arc<Mutex<Option<NoiseWave>>>, + + pub length_timer: u8, + pub length_timer_enabled: bool, + pub on: bool, + pub initial_volume: u8, + pub env_direction: u8, + pub sweep: u8, + pub clock_shift: u8, + pub lsfr_width: u8, + pub clock_divider: u8, +} + +impl AudioNoiseChannel { + pub fn new(wave: Arc<Mutex<Option<NoiseWave>>>) -> Self { + Self { + on: true, + initial_volume: 0, + env_direction: 0, + sweep: 0, + wave, + length_timer: 0, + length_timer_enabled: false, + clock_shift: 0, + lsfr_width: 0, + clock_divider: 0, + } + } + + pub fn update(&mut self) { + if let Ok(mut wave) = self.wave.lock() { + if self.on { + *wave = Some(NoiseWave::new( + self.initial_volume, + self.env_direction, + self.sweep, + self.length_timer, + self.length_timer_enabled, + self.clock_shift, + self.lsfr_width, + self.clock_divider, + )); + } else { + *wave = None; + } + } + } +} + +pub struct Audio { + _stream: OutputStream, + _sink: Sink, + + pub ch1: AudioSquareChannel, + pub ch2: AudioSquareChannel, + pub ch3: AudioCustomChannel, + pub ch4: AudioNoiseChannel, +} + +impl Audio { + pub fn new() -> Self { + let (stream, stream_handle) = OutputStream::try_default().unwrap(); + + let sink = Sink::try_new(&stream_handle).unwrap(); + + let wave_ch1 = Arc::new(Mutex::new(None)); + let wave_ch2 = Arc::new(Mutex::new(None)); + let wave_ch3 = Arc::new(Mutex::new(None)); + let wave_ch4 = Arc::new(Mutex::new(None)); + + sink.append(MutableWave::new( + wave_ch1.clone(), + wave_ch2.clone(), + wave_ch3.clone(), + wave_ch4.clone(), + )); + + Self { + _stream: stream, + _sink: sink, + + ch1: AudioSquareChannel::new(wave_ch1), + ch2: AudioSquareChannel::new(wave_ch2), + ch3: AudioCustomChannel::new(wave_ch3), + ch4: AudioNoiseChannel::new(wave_ch4), + } + } +} diff --git a/src/consts.rs b/src/consts.rs new file mode 100644 index 0000000..dd06796 --- /dev/null +++ b/src/consts.rs @@ -0,0 +1,12 @@ +pub const PROGRAM_START_ADDRESS: u16 = 0x0; +pub const STACK_START_ADDRESS: u16 = 0x0; + +pub const SPEEDUP_FACTOR: f64 = 1.0; + +pub const DISPLAY_UPDATE_RATE: u64 = 60; // Hertz +pub const DISPLAY_UPDATE_SLEEP_TIME_MICROS: u64 = + ((1000000 / DISPLAY_UPDATE_RATE) as f64 / SPEEDUP_FACTOR) as u64; + +pub const CPU_CLOCK_SPEED: u64 = 4_194_304; +pub const CPU_CYCLE_LENGTH_NANOS: u64 = + ((1_000_000_000 / CPU_CLOCK_SPEED) as f64 / SPEEDUP_FACTOR) as u64; diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..8db432f --- /dev/null +++ b/src/display.rs @@ -0,0 +1,359 @@ +// Very readable, much clean wow. + +use crate::consts::DISPLAY_UPDATE_SLEEP_TIME_MICROS; +use crate::state::MemError; +use minifb::{Window, WindowOptions}; +use std::time::SystemTime; + +const COLORS: [u32; 4] = [0x00e0f8d0, 0x0088c070, 0x346856, 0x00081820]; + +const LINE_DOTS: u64 = 456; + +mod lcdc_flags { + pub const _BG_PRIORITY: u8 = 0b1; + pub const OBJ_ENABLE: u8 = 0b10; + pub const OBJ_SIZE: u8 = 0b100; + pub const BG_TILEMAP_AREA: u8 = 0b1000; + pub const BG_TILEDATA_AREA: u8 = 0b10000; + pub const WIN_ENABLE: u8 = 0b100000; + pub const WIN_TILEMAP_AREA: u8 = 0b1000000; + pub const LCD_ENABLE: u8 = 0b10000000; +} + +pub enum DisplayInterrupt { + Vblank, + Stat, + Both, + None, +} + +#[derive(Debug)] +pub struct Display { + window: Window, + framebuffer: [u32; 160 * 144], + bg_buffer: [u8; 160 * 144], + + tiledata: [u8; 0x3000], + bg_map_attr: [u8; 0x400], + tilemaps: [u8; 0x800], + oam: [u8; 0xa0], + + pub cram: [u8; 0x80], + pub bg_palette: u8, + pub obj_palettes: [u8; 2], + pub viewport_y: u8, + pub viewport_x: u8, + pub lcdc: u8, + pub ly: u8, + pub lyc: u8, + pub lcd_interrupt_mode: u8, + pub vram_bank: u8, + + pub cgb_mode: bool, + + pub window_x: u8, + pub window_y: u8, + + last_dt: SystemTime, + + pub stat: u64, +} + +impl Display { + pub fn new() -> Self { + Self { + window: Window::new( + "Gameboy Emulator", + 512, 461, + /* 1200, 1080, */ + WindowOptions::default(), + ) + .unwrap(), + framebuffer: [0; 160 * 144], + bg_buffer: [0; 160 * 144], + tiledata: [0; 0x3000], + bg_map_attr: [0; 0x400], + cram: [0; 0x80], + tilemaps: [0; 0x800], + oam: [0; 0xa0], + bg_palette: 0, + vram_bank: 0, + obj_palettes: [0; 2], + viewport_y: 0, + viewport_x: 0, + lcdc: 0, + ly: 0, + window_x: 0, + window_y: 0, + last_dt: SystemTime::now(), + stat: 0, + lyc: 0, + cgb_mode: false, + lcd_interrupt_mode: 0xff, + } + } + + pub fn cls(&mut self) { + self.framebuffer = [COLORS[0]; 160 * 144]; + } + + pub fn update(&mut self) { + self.window + .update_with_buffer(&self.framebuffer, 160, 144) + .unwrap(); + } + + pub fn color_palette(&self, color_byte: u8, palette: u8, cgb_mode: bool) -> u32 { + if cgb_mode { + let color_pointer = palette * 8 + color_byte * 2; + let color16b: u16 = (self.cram[color_pointer as usize] as u16) + | ((self.cram[color_pointer as usize + 1] as u16) << 8); + + let red = ((0b11111 & color16b) << 3) as u32; + let green = (((color16b >> 5) & 0b11111) << 3) as u32; + let blue = (((color16b >> 10) & 0b11111) << 3) as u32; + + (red << 16) | (green << 8) | blue + } else { + COLORS[((palette >> (color_byte << 1)) & 0b11) as usize] + } + } + + pub fn print_tile(&mut self, tile: u8, x: u8, y: u8, l: usize, bg_map_attr: u8) { + let tile_pointer = if self.lcdc & lcdc_flags::BG_TILEDATA_AREA != 0 { + ((tile as u16) << 4) as usize + } else { + ((tile as i8 as i32) * 16) as usize + 0x1000 + } + if bg_map_attr & 0b1000 != 0 { 0x1800 } else { 0 }; + + for b in (0..8).rev() { + let data = (((self.tiledata[tile_pointer + l * 2] as u8) >> b) & 1) + | ((((self.tiledata[tile_pointer + l * 2 + 1] as u8) >> b) & 1) << 1); + + let pxx = (x as i32 + 7 - b as i32) as u8; + let pxy = y as i32; + + if pxy < 144 && pxx < 160 { + self.framebuffer[pxy as usize * 160 + pxx as usize] = self.color_palette( + data, + if self.cgb_mode { + bg_map_attr & 0b111 + } else { + self.bg_palette + }, + self.cgb_mode, + ); + self.bg_buffer[pxy as usize * 160 + pxx as usize] = data; + } + } + } + pub fn print_all_tiles(&mut self) { + for i in 0..=255 { + for l in 0..8 { + self.print_tile(i, (i % 20) * 8, (i / 20) * 8, l, 0); + } + } + } + + pub fn w(&mut self, addr: u16, value: u8) -> Result<(), MemError> { + if self.vram_bank == 0 { + if addr < 0x1800 { + self.tiledata[addr as usize] = value; + } else if addr >= 0x7e00 { + self.oam[addr as usize - 0x7e00] = value; + } else { + self.tilemaps[addr as usize - 0x1800] = value; + } + } else { + if addr < 0x1800 { + self.tiledata[addr as usize + 0x1800] = value; + } else if addr < 0x1c00 { + self.bg_map_attr[addr as usize - 0x1800] = value; + } + } + Ok(()) + } + + pub fn r(&self, addr: u16) -> Result<u8, MemError> { + if self.vram_bank == 0 { + if addr < 0x1800 { + Ok(self.tiledata[addr as usize]) + } else if addr >= 0x7e00 { + Ok(self.oam[addr as usize - 0x7e00]) + } else { + Ok(self.tilemaps[addr as usize - 0x1800]) + } + } else { + if addr < 0x1800 { + Ok(self.tiledata[addr as usize + 0x1800]) + } else if addr < 0x1c00 { + Ok(self.bg_map_attr[addr as usize - 0x1800]) + } else { + Ok(0) + } + } + } + + pub fn print_bg(&mut self) { + let tilemap_pointer = if self.lcdc & lcdc_flags::BG_TILEMAP_AREA != 0 { + 0x400 + } else { + 0 + }; + + let y_tile = (self.ly + self.viewport_y) as usize; + + for x in 0..32 { + let tile = self.tilemaps[tilemap_pointer + (y_tile / 8) * 32 + x]; + let bg_map_attr = self.bg_map_attr[(y_tile / 8) * 32 + x]; + self.print_tile( + tile, + x as u8 * 8 - self.viewport_x, + self.ly, + (y_tile % 8) as usize, + bg_map_attr, + ); + } + } + + pub fn print_win(&mut self) { + if self.lcdc & lcdc_flags::WIN_ENABLE == 0 { + return; + } + + let tilemap_pointer = if self.lcdc & lcdc_flags::WIN_TILEMAP_AREA != 0 { + 0x400 + } else { + 0 + }; + + let y_tile = (self.ly - self.window_y) as usize; + + for x in 0..32 { + if tilemap_pointer + (y_tile / 8) * 32 + x >= 2048 { + return; + } + let tile = self.tilemaps[tilemap_pointer + (y_tile / 8) * 32 + x]; + if x * 8 + self.window_x as usize - 7 < 160 && self.ly >= self.window_y { + self.print_tile( + tile, + x as u8 * 8 + self.window_x - 7, + self.ly, + (y_tile % 8) as usize, + 0, + ); + } + } + } + + pub fn print_obj(&mut self) { + if self.lcdc & lcdc_flags::OBJ_ENABLE == 0 { + return; + } + + // Making unreadable magic code is my brand + for o in (0..40).rev() { + let mut y = self.oam[o * 4] - 9; + let x = self.oam[o * 4 + 1]; + let mut tile = self.oam[o * 4 + 2]; + let opts = self.oam[o * 4 + 3]; + let bg_priority_flag = opts & 0b10000000 != 0; + let x_flip = opts & 0b100000 != 0; + let y_flip = opts & 0b1000000 != 0; + let palette = (opts >> 4) & 1; + let tile_vram = if self.cgb_mode && opts & 0b1000 != 0 { + 0x1800 + } else { + 0 + }; + let cgb_palette = opts & 0b111; + let obj_size = if self.lcdc & lcdc_flags::OBJ_SIZE != 0 { + tile &= 0xfe; + y += 8; + 16 + } else { + 8 + }; + + let tile_pointer = ((tile as u16) << 4) as usize + tile_vram; + + if y < self.ly || y >= self.ly + obj_size { + continue; + } + + let l = if y_flip { + y - self.ly + } else { + obj_size - 1 - (y - self.ly) + }; + + for b in 0..8 { + let pxx = if x_flip { + x as i32 + b as i32 - 8 as u8 as i32 + } else { + x as i32 + 7 - b as i32 - 8 as u8 as i32 + }; + let pxy = self.ly as i32; + + let data = (((self.tiledata[tile_pointer + l as usize * 2] as u8) >> b) & 1) + | ((((self.tiledata[tile_pointer + l as usize * 2 + 1] as u8) >> b) & 1) << 1); + + if pxy < 144 && pxx < 160 && pxy >= 0 && pxx >= 0 { + if data != 0 + && !((bg_priority_flag/* && self.lcdc & lcdc_flags::BG_PRIORITY != 0 */) + && self.bg_buffer[pxy as usize * 160 + pxx as usize] != 0) + { + self.framebuffer[pxy as usize * 160 + pxx as usize] = self.color_palette( + data, + if self.cgb_mode { + cgb_palette + 8 + } else { + self.obj_palettes[palette as usize] + }, + self.cgb_mode, + ); + } + } + } + } + } + + pub fn update_display(&mut self, cycles: u64) -> DisplayInterrupt { + let mut ret_interrupt = DisplayInterrupt::None; + self.stat += cycles; + if self.lcdc & lcdc_flags::LCD_ENABLE != 0 && self.stat >= LINE_DOTS { + self.print_bg(); + self.print_win(); + self.print_obj(); + self.ly = (self.ly + 1) % 154; + self.stat %= LINE_DOTS; + if self.ly == 0x90 { + ret_interrupt = DisplayInterrupt::Vblank; + if self.lcd_interrupt_mode == 1 { + ret_interrupt = DisplayInterrupt::Both; + } + if SystemTime::now() + .duration_since(self.last_dt) + .unwrap() + .as_micros() + > DISPLAY_UPDATE_SLEEP_TIME_MICROS as u128 + { + self.update(); + self.last_dt = SystemTime::now(); + } + } + if self.ly < 0x90 && (self.lcd_interrupt_mode == 0 || self.lcd_interrupt_mode == 2) { + ret_interrupt = DisplayInterrupt::Stat; + } + + if self.lcd_interrupt_mode == 3 && self.ly == self.lyc + 1 { + ret_interrupt = DisplayInterrupt::Stat; + } + } + if self.lcdc & lcdc_flags::LCD_ENABLE == 0 { + self.ly = 0; + } + + return ret_interrupt; + } +} diff --git a/src/gamepad.rs b/src/gamepad.rs new file mode 100644 index 0000000..8f05333 --- /dev/null +++ b/src/gamepad.rs @@ -0,0 +1,89 @@ +use crate::state::GBState; +use gilrs::{Button, GamepadId, Gilrs, Event}; + +pub struct Gamepad { + gilrs: Gilrs, + gamepad_id: Option<GamepadId>, +} + +impl Gamepad { + pub fn new() -> Self { + let mut gilrs = Gilrs::new().unwrap(); + + let gamepad_id = if let Some((gamepad_id, _gamepad)) = gilrs.gamepads().next() { + println!("Found Gamepad id: {:?}", gamepad_id); + Some(gamepad_id) + } else { + println!("No gamepad found"); + None + }; + + + Self { gilrs, gamepad_id } + } + + pub fn update_events(&mut self) { + while let Some(_) = self.gilrs.next_event() {} + } + + pub fn check_special_actions(&self, state: &mut GBState) { + if let Some(gamepad_id) = self.gamepad_id { + if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { + state.is_debug = gamepad.is_pressed(Button::West); + } + } + } + + pub fn get_action_gamepad_reg(&self) -> u8 { + + let mut res = 0xf; + + if let Some(gamepad_id) = self.gamepad_id { + if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { + if gamepad.is_pressed(Button::East) { + res &= 0b1110; + } + + if gamepad.is_pressed(Button::South) { + res &= 0b1101; + } + + if gamepad.is_pressed(Button::Select) { + res &= 0b1011; + } + + if gamepad.is_pressed(Button::Start) { + res &= 0b0111; + } + } + } + + res + } + + pub fn get_direction_gamepad_reg(&self) -> u8 { + let mut res = 0xf; + + if let Some(gamepad_id) = self.gamepad_id { + if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { + if gamepad.is_pressed(Button::DPadRight) { + res &= 0b1110; + } + + if gamepad.is_pressed(Button::DPadLeft) { + res &= 0b1101; + } + + if gamepad.is_pressed(Button::DPadUp) { + res &= 0b1011; + } + + if gamepad.is_pressed(Button::DPadDown) { + res &= 0b0111; + } + } + } + + res + } +} diff --git a/src/interrupts_timers.rs b/src/interrupts_timers.rs new file mode 100644 index 0000000..c339ce8 --- /dev/null +++ b/src/interrupts_timers.rs @@ -0,0 +1,67 @@ +use crate::display::DisplayInterrupt; +use crate::opcodes; +use crate::state::{GBState, MemError}; + +const TIMA_TIMER_SPEEDS: [u64; 4] = [1024, 16, 64, 256]; + +impl GBState { + pub fn check_interrupts(&mut self) -> Result<(), MemError> { + if self.mem.ime { + let interrupts = self.mem.io[0x0f] & self.mem.interrupts_register & 0b11111; + for i in 0..5 { + if interrupts & (1 << i) != 0 { + opcodes::push(self, self.cpu.pc)?; + + self.mem.ime = false; + self.cpu.pc = 0x40 + (i << 3); + self.mem.halt = false; + + self.mem.io[0x0f] &= !(1 << i); + break; + } + } + } + Ok(()) + } + + pub fn tima_timer(&mut self, c: u64) { + if self.mem.timer_enabled + && self.tima_cycles >= TIMA_TIMER_SPEEDS[self.mem.timer_speed as usize] + { + if self.mem.tima == 0xff { + self.mem.io[0x0f] |= 0b100; + self.mem.tima = self.mem.tma; + } else { + self.mem.tima += 1; + } + self.tima_cycles %= TIMA_TIMER_SPEEDS[self.mem.timer_speed as usize]; + } + self.tima_cycles += c; + } + + pub fn update_display_interrupts(&mut self, c: u64) { + let interrupt = self.mem.display.update_display(c); + + match interrupt { + DisplayInterrupt::Vblank => { + self.mem.io[0x0f] |= 1; + } + DisplayInterrupt::Stat => { + self.mem.io[0xf] |= 2; + } + DisplayInterrupt::Both => { + self.mem.io[0xf] |= 3; + } + _ => {} + } + } + + pub fn div_timer(&mut self, c: u64) { + if self.div_cycles >= 256 { + self.mem.div += 1; + + self.div_cycles = 0; + } + self.div_cycles += c; + } +} diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..df50c51 --- /dev/null +++ b/src/io.rs @@ -0,0 +1,257 @@ +use crate::state::{MemError, Memory}; + +impl Memory { + pub fn r_io(&self, addr: u8) -> u8 { + if addr > 0x50 { + println!("Reading from 0xff{:02x} not implemented yet", addr); + } + match addr { + 0x00 => { + if self.joypad_is_action { + (self.joypad_reg >> 4) | 0b11010000 + } else { + (self.joypad_reg & 0xf) | 0b11100000 + } + } + 0x04 => self.div, + 0x40 => self.display.lcdc, + 0x42 => self.display.viewport_y, + 0x43 => self.display.viewport_x, + 0x41 => { + let mut ret = match self.display.lcd_interrupt_mode { + 3 => 0b01000000, + 2 => 0b00100000, + 1 => 0b00010000, + 0 => 0b00001000, + _ => 0, + }; + + ret |= if self.display.ly > 0x90 { + 1 + } else if self.display.stat < 80 { + 2 + } else if self.display.stat < 280 { + 3 + } else { + 0 + }; + + if self.display.ly == self.display.lyc + 1 { + ret |= 0b100; + } + + ret + } + 0x44 => self.display.ly, + 0x45 => self.display.lyc, + 0x47 => self.display.bg_palette, + 0x48 => self.display.obj_palettes[0], + 0x49 => self.display.obj_palettes[1], + 0x4a => self.display.window_y, + 0x4b => self.display.window_x, + 0x50 => { + if self.boot_rom_on { + 0xfe + } else { + 0xff + } + } + _ => { + // println!("Reading from 0xff{:02x} not implemented yet", addr); + self.io[addr as usize] + } + } + } + + pub fn w_io(&mut self, addr: u8, value: u8) -> Result<(), MemError> { + match addr { + 0x00 => { + self.joypad_is_action = !value & 0b00100000 != 0; + } + 0x04 => { + self.div = 0; + } + 0x05 => { + self.tima = value; + } + 0x06 => { + self.tma = value; + } + 0x07 => { + self.timer_enabled = value & 0b100 != 0; + self.timer_speed = value & 0b11; + } + 0x0f => { + self.io[0x0f] = value; + } + 0x10 => { + self.audio.ch1.period_sweep_pace = (0b1110000 & value) >> 4; + self.audio.ch1.period_sweep_direction = (0b1000 & value) >> 3; + self.audio.ch1.period_sweep_slope = 0b111 & value; + } + 0x11 => { + self.audio.ch1.duty = value >> 6; + self.audio.ch1.length_timer = value & 0b111111; + } + 0x12 => { + self.audio.ch1.initial_volume = value >> 4; + self.audio.ch1.env_direction = (value & 0xf) >> 3; + self.audio.ch1.sweep = value & 0b111; + } + 0x13 => { + self.audio.ch1.period_value &= 0xff00; + self.audio.ch1.period_value |= value as u16; + } + 0x14 => { + self.audio.ch1.period_value &= 0xff; + self.audio.ch1.period_value |= ((value & 0b111) as u16) << 8; + self.audio.ch1.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch1.update(); + } + } + 0x16 => { + self.audio.ch2.duty = value >> 6; + self.audio.ch2.length_timer = value & 0b111111; + } + 0x17 => { + self.audio.ch2.initial_volume = value >> 4; + self.audio.ch2.env_direction = (value & 0xf) >> 3; + self.audio.ch2.sweep = value & 0b111; + } + 0x18 => { + self.audio.ch2.period_value &= 0xff00; + self.audio.ch2.period_value |= value as u16; + } + 0x19 => { + self.audio.ch2.period_value &= 0xff; + self.audio.ch2.period_value |= ((value & 0b111) as u16) << 8; + self.audio.ch2.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch2.update(); + } + } + 0x1a => { + if value & 0b10000000 != 0 { + self.audio.ch3.on = true; + } else { + self.audio.ch3.on = false; + } + self.audio.ch3.update(); + } + 0x1b => { + self.audio.ch3.length_timer = value & 0b111111; + } + 0x1c => { + let s = (value >> 5) & 0b11; + if s == 0 { + self.audio.ch3.initial_volume = 0; + } else { + self.audio.ch3.initial_volume = 0xf >> (s - 1); + } + } + 0x1d => { + self.audio.ch3.period_value &= 0xff00; + self.audio.ch3.period_value |= value as u16; + } + 0x1e => { + self.audio.ch3.period_value &= 0xff; + self.audio.ch3.period_value |= ((value & 0b111) as u16) << 8; + self.audio.ch3.period_value /= 2; + self.audio.ch3.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch3.update(); + } + } + 0x20 => { + self.audio.ch4.length_timer = value & 0b111111; + } + 0x21 => { + self.audio.ch4.initial_volume = value >> 4; + self.audio.ch4.env_direction = (value & 0xf) >> 3; + self.audio.ch4.sweep = value & 0b111; + } + 0x22 => { + self.audio.ch4.clock_shift = value >> 4; + self.audio.ch4.lsfr_width = (value & 0xf) >> 3; + self.audio.ch4.clock_divider = value & 0b111; + } + 0x23 => { + self.audio.ch4.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch4.update(); + } + } + 0x40 => self.display.lcdc = value, + 0x41 => { + if value & 0b01000000 != 0 { + self.display.lcd_interrupt_mode = 3; + } else if value & 0b00100000 != 0 { + self.display.lcd_interrupt_mode = 2; + } else if value & 0b00010000 != 0 { + self.display.lcd_interrupt_mode = 1; + } else if value & 0b00001000 != 0 { + self.display.lcd_interrupt_mode = 0; + } + } + 0x45 => self.display.lyc = value, + 0x42 => self.display.viewport_y = value, + 0x43 => self.display.viewport_x = value, + 0x46 => { + if value < 0xe0 { + let addr = (value as u16) << 8; + + for i in 0..0xa0 { + self.w(0xfe00 | i, self.r(addr | i)?)?; + } + } + } + 0x47 => self.display.bg_palette = value, + 0x48 => self.display.obj_palettes[0] = value, + 0x49 => self.display.obj_palettes[1] = value, + 0x4a => self.display.window_y = value, + 0x4b => self.display.window_x = value, + 0x4f => self.display.vram_bank = value & 1, + 0x50 => self.boot_rom_on = value & 1 == 0 && self.boot_rom_on, + 0x68 => { + self.bgcram_pointer = 0b111111 & value; + self.bgcram_pointer_autoincrement = value & 0b10000000 != 0; + } + 0x69 => { + self.display.cram[self.bgcram_pointer as usize] = value; + if self.bgcram_pointer_autoincrement { + self.bgcram_pointer += 1; + self.bgcram_pointer &= 0b111111; + } + } + 0x6a => { + self.obcram_pointer = 0b111111 & value; + self.obcram_pointer_autoincrement = value & 0b10000000 != 0; + } + 0x6b => { + self.display.cram[self.obcram_pointer as usize + 0x40] = value; + if self.obcram_pointer_autoincrement { + self.obcram_pointer += 1; + self.obcram_pointer &= 0b111111; + } + } + _ => { + if addr >= 0x4d { + println!( + "Writing to 0xff{:02x} not implemented yet ({:02x})", + addr, value + ); + } + } + } + self.io[addr as usize] = value; + + if addr >= 0x30 && addr <= 0x3f { + let i = (addr - 0x30) as usize; + self.audio.ch3.wave_pattern[i * 2] = value >> 4; + self.audio.ch3.wave_pattern[i * 2 + 1] = value & 0xf; + } + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3d18789 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,142 @@ +pub mod audio; +pub mod consts; +pub mod display; +pub mod gamepad; +pub mod interrupts_timers; +pub mod io; +pub mod opcodes; +pub mod state; + +use crate::gamepad::Gamepad; +use crate::state::{GBState, MemError}; +use clap::Parser; +use std::time::SystemTime; +use std::{thread, time}; + +pub fn exec_opcode(state: &mut GBState) -> Result<u64, MemError> { + let opcode = state.mem.r(state.cpu.pc)?; + + if state.is_debug { + println!( + "{:02x}:{:04x} = {:02x} (IME: {})", + state.mem.rom_bank, state.cpu.pc, opcode, state.mem.ime + ); + } + + state.cpu.pc += 1; + + let n1 = (opcode >> 3) & 0b111; + let n2 = opcode & 0b111; + + match opcode >> 6 { + 0b00 => opcodes::op00(state, n1, n2), + 0b01 => opcodes::op01(state, n1, n2), + 0b10 => opcodes::op10(state, n1, n2), + 0b11 => opcodes::op11(state, n1, n2), + _ => panic!(), + } +} + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + /// The gameboy rom file + rom: String, + + /// Setting this saves battery by using thread::sleep instead of spin_sleeping. It can result in lag and inconsistent timing. + #[arg(long)] + thread_sleep: bool, + + #[arg(short, long, default_value_t = 1.0)] + speed: f32, +} + +fn main() { + let cli = Cli::parse(); + + println!("Initializing Gamepad..."); + + let mut gamepad = Gamepad::new(); + + println!("Starting {:?}...", &cli.rom); + + let mut state = GBState::new(); + + let save_file = format!("{}.sav", &cli.rom); + + state.mem.load_rom(&cli.rom).unwrap(); + + if let Err(_) = state.mem.load_external_ram(&save_file) { + println!( + "\"{}\" not found. Initializing new external ram.", + save_file + ); + } + + let mut nanos_sleep: i128 = 0; + let mut halt_time = 0; + let mut was_previously_halted = false; + + let mut last_ram_bank_enabled = false; + + loop { + if was_previously_halted && !state.mem.halt { + println!("Halt cycles {}", halt_time); + halt_time = 0; + } + was_previously_halted = state.mem.halt; + let now = SystemTime::now(); + let c = if !state.mem.halt { + exec_opcode(&mut state).unwrap() + } else { + halt_time += 4; + 4 + }; + + state.div_timer(c); + state.tima_timer(c); + state.update_display_interrupts(c); + state.check_interrupts().unwrap(); + + nanos_sleep += c as i128 * (consts::CPU_CYCLE_LENGTH_NANOS as f32 / cli.speed) as i128; + if nanos_sleep > 0 { + gamepad.update_events(); + + let action_button_reg = gamepad.get_action_gamepad_reg(); + let direction_button_reg = gamepad.get_direction_gamepad_reg(); + gamepad.check_special_actions(&mut state); + + if state.mem.joypad_is_action + && (action_button_reg & (state.mem.joypad_reg >> 4)) + != (state.mem.joypad_reg >> 4) + || (!state.mem.joypad_is_action + && (direction_button_reg & state.mem.joypad_reg & 0b1111) + != (state.mem.joypad_reg & 0b1111)) + { + state.mem.io[0x0f] |= 0b10000; + } + + state.mem.joypad_reg = direction_button_reg | (action_button_reg << 4); + + if cli.thread_sleep { + thread::sleep(time::Duration::from_nanos(nanos_sleep as u64 / 10)); + } else { + while SystemTime::now().duration_since(now).unwrap().as_nanos() + < nanos_sleep as u128 + {} + } + + nanos_sleep = + nanos_sleep - SystemTime::now().duration_since(now).unwrap().as_nanos() as i128; + + if last_ram_bank_enabled && !state.mem.ram_bank_enabled { + println!("Saving to \"{}\"...", save_file); + + if let Err(_) = state.mem.save_external_ram(&save_file) { + println!("Failed to save external RAM"); + } + } + last_ram_bank_enabled = state.mem.ram_bank_enabled; + } + } +} diff --git a/src/opcodes.rs b/src/opcodes.rs new file mode 100644 index 0000000..f238239 --- /dev/null +++ b/src/opcodes.rs @@ -0,0 +1,825 @@ +use crate::state::{flag, reg, GBState, MemError}; + +// The opcodes functions are returning the number of cycles used. + +pub fn r_16b_from_pc(state: &mut GBState) -> Result<u16, MemError> { + let p: u16 = state.mem.r(state.cpu.pc)? as u16 | ((state.mem.r(state.cpu.pc + 1)? as u16) << 8); + state.cpu.pc += 2; + + Ok(p) +} + +pub fn r_8b_from_pc(state: &mut GBState) -> Result<u8, MemError> { + let p = state.mem.r(state.cpu.pc)?; + state.cpu.pc += 1; + + Ok(p) +} + +pub fn ldrr(state: &mut GBState, n1: u8, n2: u8) -> Result<(), MemError> { + // Load a register into another register + // LD r, r + state.w_reg(n1, state.r_reg(n2)?) +} + +pub fn ldr8(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Load an raw 8b value into a register + let p = r_8b_from_pc(state)?; + + state.w_reg(n1, p)?; + Ok(8) +} + +pub fn ldrr16(state: &mut GBState, rr: u8, x: u16) { + // Load a raw 16b value into a register + state.cpu.w16(rr, x); +} + +pub fn ldnnsp(state: &mut GBState) -> Result<u64, MemError> { + // Load SP into an arbitrary position in memory + let p = r_16b_from_pc(state)?; + + state.mem.w(p, (state.cpu.sp & 0xff) as u8)?; + state.mem.w(p + 1, (state.cpu.sp >> 8) as u8)?; + Ok(20) +} + +pub fn ldsphl(state: &mut GBState) -> u64 { + state.cpu.sp = state.cpu.r16(reg::HL); + 8 +} + +pub fn ldnna(state: &mut GBState, nn: u16) -> Result<(), MemError> { + // Load A into an arbitrary position in memory + state.mem.w(nn, state.cpu.r[reg::A as usize])?; + Ok(()) +} + +pub fn ldann(state: &mut GBState, nn: u16) -> Result<(), MemError> { + // Load A from an arbitrary position in memory + state.cpu.r[reg::A as usize] = state.mem.r(nn)?; + Ok(()) +} + +pub fn push(state: &mut GBState, x: u16) -> Result<(), MemError> { + state.cpu.sp -= 2; + + state.mem.w(state.cpu.sp, (x & 0xff) as u8)?; + + state.mem.w(state.cpu.sp + 1, (x >> 8) as u8)?; + + Ok(()) +} + +pub fn pop(state: &mut GBState) -> Result<u16, MemError> { + let res = state.mem.r(state.cpu.sp)? as u16 | ((state.mem.r(state.cpu.sp + 1)? as u16) << 8); + + state.cpu.sp += 2; + + Ok(res) +} + +pub fn jr8(state: &mut GBState) -> Result<u64, MemError> { + // Unconditional relative jump + let p = r_8b_from_pc(state)?; + + state.cpu.pc = (state.cpu.pc as i16 + p as i8 as i16) as u16; + + Ok(12) +} + +pub fn jrcc8(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Conditional relative jump + let p = r_8b_from_pc(state)?; + let mut cycles = 8; + + if state.cpu.check_flag(n1 & 0b11) { + cycles += 4; + state.cpu.pc = (state.cpu.pc as i16 + p as i8 as i16) as u16; + } + + Ok(cycles) +} + +pub fn jp16(state: &mut GBState) -> Result<u64, MemError> { + // Unconditional absolute jump + let p = r_16b_from_pc(state)?; + + state.cpu.pc = p; + + Ok(16) +} + +pub fn jphl(state: &mut GBState) -> u64 { + // Unconditional absolute jump to HL + state.cpu.pc = state.cpu.r16(reg::HL); + + 4 +} + +pub fn jpcc16(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Conditional absolute jump + let p = r_16b_from_pc(state)?; + let mut cycles = 8; + + if state.cpu.check_flag(n1 & 0b11) { + cycles += 4; + state.cpu.pc = p; + } + + Ok(cycles) +} + +pub fn call(state: &mut GBState) -> Result<u64, MemError> { + // Unconditional function call + let p = r_16b_from_pc(state)?; + + push(state, state.cpu.pc)?; + state.cpu.pc = p; + + Ok(24) +} + +pub fn callcc(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Conditional function call + let p = r_16b_from_pc(state)?; + let mut cycles = 12; + + if state.cpu.check_flag(n1 & 0b11) { + cycles += 12; + push(state, state.cpu.pc)?; + state.cpu.pc = p; + } + + Ok(cycles) +} + +pub fn ret(state: &mut GBState) -> Result<u64, MemError> { + let res = pop(state)?; + + if res == 0 { + println!("DEBUG: {:?}", state.cpu); + panic!("RET to start"); + } + + state.cpu.pc = res; + + Ok(16) +} + +pub fn retcc(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + let mut cycles = 8; + if state.cpu.check_flag(n1 & 0b11) { + cycles += 12; + state.cpu.pc = pop(state)?; + } + + Ok(cycles) +} + +pub fn ld00a(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Load register A into or from memory pointed by rr (BC, DE or HL(+/-)) + // LD (rr), A + // LD A, (rr) + let ptr_reg = match n1 & 0b110 { + 0b000 => reg::B, + 0b010 => reg::C, + _ => reg::HL, + }; + + if n1 & 0b001 == 1 { + state.cpu.r[reg::A as usize] = state.mem.r(state.cpu.r16(ptr_reg))?; + } else { + state + .mem + .w(state.cpu.r16(ptr_reg), state.cpu.r[reg::A as usize])?; + } + + if n1 & 0b110 == 0b100 { + state.cpu.w16(reg::HL, state.cpu.r16(reg::HL) + 1); // (HL+) + } + + if n1 & 0b110 == 0b110 { + state.cpu.w16(reg::HL, state.cpu.r16(reg::HL) - 1); // (HL-) + } + + Ok(8) +} + +pub fn inc8(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Increment 8 bit register + state.w_reg(n1, state.r_reg(n1)? + 1)?; + state.cpu.r[reg::F as usize] &= !(flag::N | flag::ZF | flag::H); + if state.r_reg(n1)? == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + if state.r_reg(n1)? & 0xf == 0x0 { + state.cpu.r[reg::F as usize] |= flag::H; + } + + Ok(4) +} + +pub fn dec8(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Decrement 8 bit register + state.w_reg(n1, state.r_reg(n1)? - 1)?; + state.cpu.r[reg::F as usize] |= flag::N; + + state.cpu.r[reg::F as usize] &= !(flag::ZF | flag::H); + if state.r_reg(n1)? == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + if state.r_reg(n1)? & 0xf == 0xf { + state.cpu.r[reg::F as usize] |= flag::H; + } + + Ok(4) +} + +pub fn inc16(state: &mut GBState, rr: u8) -> u64 { + // Increment 16 bit register + state.cpu.w16(rr, state.cpu.r16(rr) + 1); + 8 +} + +pub fn dec16(state: &mut GBState, rr: u8) -> u64 { + // Decrement 16 bit register + state.cpu.w16(rr, state.cpu.r16(rr) - 1); + 8 +} + +pub fn ccf(state: &mut GBState) { + // Flip carry flag + state.cpu.r[reg::F as usize] = (state.cpu.r[reg::F as usize] & 0b10011111) ^ 0b00010000 +} + +pub fn scf(state: &mut GBState) { + // Set carry flag + state.cpu.r[reg::F as usize] = (state.cpu.r[reg::F as usize] & 0b10011111) | 0b00010000 +} + +pub fn daa(state: &mut GBState) { + // Decimal Adjust Accumulator + // Adjust the A register after a addition or substraction to keep valid BCD representation + let nibble_low = state.cpu.r[reg::A as usize] & 0b1111; + let sub_flag = (state.cpu.r[reg::F as usize] & flag::N) != 0; + let half_carry_flag = (state.cpu.r[reg::F as usize] & flag::H) != 0; + + if (half_carry_flag || nibble_low > 9) && !sub_flag { + state.cpu.r[reg::A as usize] += 0x06; + } + if (half_carry_flag || nibble_low > 9) && sub_flag { + state.cpu.r[reg::A as usize] -= 0x06; + } + + let nibble_high = state.cpu.r[reg::A as usize] >> 4; + + state.cpu.r[reg::F as usize] &= !(flag::CY | flag::ZF); + + if nibble_high > 9 && !sub_flag { + state.cpu.r[reg::A as usize] += 0x60; + state.cpu.r[reg::F as usize] |= flag::CY; + } + if nibble_high > 9 && sub_flag { + state.cpu.r[reg::A as usize] -= 0x60; + state.cpu.r[reg::F as usize] |= flag::CY; + } + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + state.cpu.r[reg::F as usize] &= !flag::H; +} + +pub fn cpl(state: &mut GBState) { + // Flip all bits in register A + state.cpu.r[reg::F as usize] = state.cpu.r[reg::F as usize] | flag::N | flag::H; + state.cpu.r[reg::A as usize] ^= 0xff; +} + +pub fn addsp8(state: &mut GBState) -> Result<u64, MemError> { + let n = r_8b_from_pc(state)? as i8; + + state.cpu.sp = (state.cpu.sp as i32 + n as i32) as u16; + + state.cpu.r[reg::F as usize] &= !(flag::N | flag::H | flag::CY); + + if (state.cpu.sp & 0xff) as i32 + n as i32 & !0xff != 0 { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if (state.cpu.sp as i32 + n as i32) & !0xffff != 0 { + state.cpu.r[reg::F as usize] |= flag::CY; + } + Ok(16) +} + +pub fn add(state: &mut GBState, x: u8) { + // ADD a number to A and store the result in A + let res = x as u16 + state.cpu.r[reg::A as usize] as u16; + + state.cpu.r[reg::F as usize] = 0; + + if (x & 0xf) + (state.cpu.r[reg::A as usize] & 0xf) > 0xf { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if res > 0xff { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + state.cpu.r[reg::A as usize] = res as u8; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn addhlrr(state: &mut GBState, rr: u8) -> u64 { + let n = state.cpu.r16(rr); + let hl = state.cpu.r16(reg::HL); + + state.cpu.w16(reg::HL, (hl as i32 + n as i32) as u16); + + state.cpu.r[reg::F as usize] &= !(flag::N | flag::H | flag::CY); + + if (hl & 0xff) as i32 + n as i32 & !0xff != 0 { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if (hl as i32 + n as i32) & !0xffff != 0 { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + 8 +} + +pub fn adc(state: &mut GBState, x: u8) { + // ADD a number and the carry flag to A and store the result in A + let carry = (state.cpu.r[reg::F as usize] & flag::CY) >> 4; + let res = x as u16 + state.cpu.r[reg::A as usize] as u16 + carry as u16; + + state.cpu.r[reg::F as usize] = 0; + + if (x & 0xf) + ((state.cpu.r[reg::A as usize] & 0xf) + carry) > 0xf { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if res > 0xff { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + state.cpu.r[reg::A as usize] = res as u8; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn sub(state: &mut GBState, x: u8) { + // SUB a number to A and store the result in A + state.cpu.r[reg::F as usize] = flag::N; + + if (x & 0xf) > (state.cpu.r[reg::A as usize] & 0xf) { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if x > state.cpu.r[reg::A as usize] { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + state.cpu.r[reg::A as usize] = state.cpu.r[reg::A as usize] - x; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn sbc(state: &mut GBState, x: u8) { + // SUB a number and the carry flag to A and store the result in A + let carry = (state.cpu.r[reg::F as usize] & flag::CY) >> 4; + state.cpu.r[reg::F as usize] = flag::N; + + if (x & 0xf) > (state.cpu.r[reg::A as usize] & 0xf) - carry { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if x as i32 > state.cpu.r[reg::A as usize] as i32 - carry as i32 { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + state.cpu.r[reg::A as usize] = state.cpu.r[reg::A as usize] - x - carry; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn and(state: &mut GBState, x: u8) { + // AND a number to A and store the result in A + state.cpu.r[reg::A as usize] &= x; + + state.cpu.r[reg::F as usize] = flag::H; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn xor(state: &mut GBState, x: u8) { + // XOR a number to A and store the result in A + state.cpu.r[reg::A as usize] ^= x; + + state.cpu.r[reg::F as usize] = 0; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn or(state: &mut GBState, x: u8) { + // OR a number to A and store the result in A + state.cpu.r[reg::A as usize] |= x; + + state.cpu.r[reg::F as usize] = 0; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn cp(state: &mut GBState, x: u8) { + // SUB a number to A and update the flags accordingly without updating A + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + + state.cpu.r[reg::F as usize] |= flag::N; + + if x & 0xf > state.cpu.r[reg::A as usize] & 0xf { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if x > state.cpu.r[reg::A as usize] { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + let res = state.cpu.r[reg::A as usize] - x; + + if res == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn rlc(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // ROTATE LEFT the input register + let mut n = state.r_reg(r_i)?; + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n >> 7) << 4; + n <<= 1; + n |= (state.cpu.r[reg::F as usize] & flag::CY) >> 4; + state.w_reg(r_i, n) +} + +pub fn rrc(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // ROTATE RIGHT the input register + let mut n = state.r_reg(r_i)?; + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n & 1) << 4; + n >>= 1; + n |= ((state.cpu.r[reg::F as usize] & flag::CY) >> 4) << 7; + state.w_reg(r_i, n) +} + +pub fn rl(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // ROTATE LEFT THROUGH CARRY the input register + // (RLC IS ROTATE AND RL IS ROTATE THROUGH CARRY ! IT DOESN'T MAKE ANY SENSE !!) + let mut n = state.r_reg(r_i)?; + let carry = (state.cpu.r[reg::F as usize] & flag::CY) >> 4; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n >> 7) << 4; + n <<= 1; + n |= carry; + state.w_reg(r_i, n) +} + +pub fn rr(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // ROTATE RIGHT THROUGH CARRY the input register + let mut n = state.r_reg(r_i)?; + let carry = (state.cpu.r[reg::F as usize] & flag::CY) >> 4; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n & 1) << 4; + n >>= 1; + n |= carry << 7; + state.w_reg(r_i, n) +} + +pub fn sla(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // Shift left Arithmetic (b0=0) the input register + let mut n = state.r_reg(r_i)?; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n >> 7) << 4; + n <<= 1; + + if n == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + state.w_reg(r_i, n) +} + +pub fn sra(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // Shift right Arithmetic (b7=b7) the input register + let mut n = state.r_reg(r_i)?; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n & 0b1) << 4; + let b7 = n & 0b10000000; + n >>= 1; + n |= b7; + + if n == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + state.w_reg(r_i, n) +} + +pub fn swap(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // Swap the high nibble and low nibble + let mut n = state.r_reg(r_i)?; + + let nibble_low = n & 0b1111; + let nibble_high = n >> 4; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + + n = nibble_high | (nibble_low << 4); + + if n == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + state.w_reg(r_i, n) +} + +pub fn srl(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // Shift right Logical (b7=0) the input register + let mut n = state.r_reg(r_i)?; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n & 0b1) << 4; + n >>= 1; + + if n == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + state.w_reg(r_i, n) +} + +pub fn bit(state: &mut GBState, n1: u8, n2: u8) -> Result<(), MemError> { + let z = (((state.r_reg(n2)? >> n1) & 1) ^ 1) << 7; + + state.cpu.r[reg::F as usize] &= !(flag::N | flag::ZF); + state.cpu.r[reg::F as usize] |= flag::H | z; + Ok(()) +} + +pub fn set(state: &mut GBState, n1: u8, n2: u8) -> Result<(), MemError> { + state.w_reg(n2, state.r_reg(n2)? | (1 << n1)) +} + +pub fn res(state: &mut GBState, n1: u8, n2: u8) -> Result<(), MemError> { + state.w_reg(n2, state.r_reg(n2)? & !(1 << n1)) +} + +// I don't remember why I separated op00, op01, op10 and op11 AND I'M NOT GOING TO CHANGE IT +// BECAUSE I LOVE CHAOS + +pub fn op00(state: &mut GBState, n1: u8, n2: u8) -> Result<u64, MemError> { + // Dispatcher for the instructions starting with 0b00 based on their 3 LSB + match n2 { + 0b000 => match n1 { + 0b000 => Ok(4), + 0b001 => ldnnsp(state), + 0b010 => todo!("STOP"), + 0b011 => jr8(state), + _ => jrcc8(state, n1), + }, + 0b001 => match n1 { + 0b001 | 0b011 | 0b101 | 0b111 => Ok(addhlrr(state, n1 >> 1)), + 0b000 | 0b010 | 0b100 | 0b110 => { + let p = r_16b_from_pc(state)?; + ldrr16(state, n1 >> 1, p); + Ok(12) + } + _ => panic!(), + }, + 0b010 => ld00a(state, n1), + 0b011 => match n1 { + 0b001 | 0b011 | 0b101 | 0b111 => Ok(dec16(state, n1 >> 1)), + 0b000 | 0b010 | 0b100 | 0b110 => Ok(inc16(state, n1 >> 1)), + _ => panic!(), + }, + 0b100 => inc8(state, n1), + 0b101 => dec8(state, n1), + 0b110 => ldr8(state, n1), + 0b111 => { + match n1 { + 0b000 => rlc(state, 7)?, + 0b001 => rrc(state, 7)?, + 0b010 => rl(state, 7)?, + 0b011 => rr(state, 7)?, + 0b100 => daa(state), + 0b101 => cpl(state), + 0b110 => scf(state), + 0b111 => ccf(state), + _ => panic!(), + }; + Ok(4) + } + _ => panic!(), + } +} + +pub fn op01(state: &mut GBState, n1: u8, n2: u8) -> Result<u64, MemError> { + // Dispatcher for the instructions starting with 0b01 (LD r,r and HALT) + if n1 == 0b110 && n2 == 0b110 { + state.mem.halt = true; + Ok(4) + } else { + ldrr(state, n1, n2)?; + + if n1 == 0b110 || n2 == 0b110 { + Ok(8) + } else { + Ok(4) + } + } +} + +pub fn op10(state: &mut GBState, n1: u8, n2: u8) -> Result<u64, MemError> { + // Dispatcher for the instructions starting with 0b10 (Arithmetic) + match n1 { + 0b000 => add(state, state.r_reg(n2)?), + 0b001 => adc(state, state.r_reg(n2)?), + 0b010 => sub(state, state.r_reg(n2)?), + 0b011 => sbc(state, state.r_reg(n2)?), + 0b100 => and(state, state.r_reg(n2)?), + 0b101 => xor(state, state.r_reg(n2)?), + 0b110 => or(state, state.r_reg(n2)?), + 0b111 => cp(state, state.r_reg(n2)?), + _ => panic!(), + } + + if n2 == 0b110 { + Ok(8) + } else { + Ok(4) + } +} + +pub fn op11(state: &mut GBState, n1: u8, n2: u8) -> Result<u64, MemError> { + match n2 { + 0b000 => match n1 { + 0b100 => { + let n = r_8b_from_pc(state)?; + ldnna(state, n as u16 | 0xff00)?; + Ok(12) + } + 0b101 => addsp8(state), + 0b110 => { + let n = r_8b_from_pc(state)?; + ldann(state, n as u16 | 0xff00)?; + Ok(12) + } + 0b111 => { + let n = r_8b_from_pc(state)?; + ldrr16(state, reg::HL, n as u16 + state.cpu.sp); + Ok(12) + } + _ => retcc(state, n1 & 0b11), + }, + 0b001 => match n1 { + 0b001 => ret(state), + 0b011 => { + state.mem.ime = true; + + ret(state) + } + 0b101 => Ok(jphl(state)), + 0b111 => Ok(ldsphl(state)), + _ => { + let p = pop(state)?; + state.cpu.r[(n1 >> 1) as usize * 2 + 1] = (p & 0xff) as u8; + state.cpu.r[(n1 >> 1) as usize * 2] = (p >> 8) as u8; + Ok(12) + } + }, + 0b010 => match n1 { + 0b100 => { + ldnna(state, state.cpu.r[reg::C as usize] as u16 | 0xff00)?; + Ok(8) + } + 0b101 => { + let nn = r_16b_from_pc(state)?; + ldnna(state, nn)?; + Ok(16) + } + 0b110 => { + ldann(state, state.cpu.r[reg::C as usize] as u16 | 0xff00)?; + Ok(8) + } + 0b111 => { + let nn = r_16b_from_pc(state)?; + ldann(state, nn)?; + Ok(16) + } + _ => jpcc16(state, n1 & 0b11), + }, + 0b011 => match n1 { + 0b000 => jp16(state), + 0b001 => op_bitwise(state), // Bitwise operations + 0b010 | 0b011 | 0b100 | 0b101 => unimplemented!(), + 0b110 => { + state.mem.ime = false; + Ok(4) + } + 0b111 => { + state.mem.ime = true; + Ok(4) + } + _ => panic!(), + }, + 0b100 => callcc(state, n1 & 0b11), + 0b101 => match n1 { + 0b001 => call(state), + 0b011 | 0b101 | 0b111 => unimplemented!(), + _ => { + let value = state.cpu.r[(n1 >> 1) as usize * 2 + 1] as u16 + | ((state.cpu.r[(n1 >> 1) as usize * 2] as u16) << 8); + push(state, value)?; + Ok(16) + } + }, + 0b110 => { + let p = r_8b_from_pc(state)?; + + match n1 { + 0b000 => add(state, p), + 0b001 => adc(state, p), + 0b010 => sub(state, p), + 0b011 => sbc(state, p), + 0b100 => and(state, p), + 0b101 => xor(state, p), + 0b110 => or(state, p), + 0b111 => cp(state, p), + _ => panic!(), + } + Ok(8) + } + 0b111 => { + let p = n1 << 3; + + push(state, state.cpu.pc)?; + state.cpu.pc = p as u16; + Ok(16) + } // RST + _ => panic!(), + } +} + +pub fn op_bitwise(state: &mut GBState) -> Result<u64, MemError> { + let p = r_8b_from_pc(state)?; + let opcode = p >> 6; + let n1 = p >> 3 & 0b111; + let n2 = p & 0b111; + + match opcode { + 0b00 => match n1 { + 0b000 => rlc(state, n2), + 0b001 => rrc(state, n2), + 0b010 => rl(state, n2), + 0b011 => rr(state, n2), + 0b100 => sla(state, n2), + 0b101 => sra(state, n2), + 0b110 => swap(state, n2), + 0b111 => srl(state, n2), + _ => panic!(), + }, + 0b01 => bit(state, n1, n2), + 0b10 => res(state, n1, n2), + 0b11 => set(state, n1, n2), + _ => panic!(), + }?; + if n2 == 0b110 { + Ok(16) + } else { + Ok(8) + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..d2f93d9 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,377 @@ +use crate::audio::Audio; +use crate::consts::{PROGRAM_START_ADDRESS, STACK_START_ADDRESS}; +use crate::display::Display; +use std::fs::File; +use std::io::{Read, Write}; + +pub mod reg { + pub const B: u8 = 0; + pub const C: u8 = 1; + pub const D: u8 = 2; + pub const E: u8 = 3; + pub const H: u8 = 4; + pub const L: u8 = 5; + pub const A: u8 = 6; + pub const F: u8 = 7; + + pub const BC: u8 = 0; + pub const DE: u8 = 1; + pub const HL: u8 = 2; + pub const SP: u8 = 3; +} + +pub mod flag { + pub const NZ: u8 = 0; + pub const Z: u8 = 1; + pub const NC: u8 = 2; + pub const C: u8 = 3; + + pub const CY: u8 = 1 << 4; + pub const H: u8 = 1 << 5; + pub const N: u8 = 1 << 6; + pub const ZF: u8 = 1 << 7; +} + +#[derive(Debug)] +pub struct CPU { + /* B, C, D, E, H, L, A, F registers. + * A is usually represented by 111 even though it's in index 6. + * (HL) usually takes the 110 representation. + * F isn't usually used by the 8bits register operations. */ + pub r: [u8; 8], + + pub pc: u16, // program counter + pub sp: u16, // stack pointer +} + +impl CPU { + pub fn new() -> Self { + Self { + r: [0; 8], + + pc: PROGRAM_START_ADDRESS, + sp: STACK_START_ADDRESS, + } + } + + pub fn r16(&self, r: u8) -> u16 { + if r == reg::SP { + return self.sp; + } else { + return self.r[r as usize * 2 + 1] as u16 | ((self.r[r as usize * 2] as u16) << 8); + } + } + + pub fn w16(&mut self, r: u8, value: u16) { + if r == reg::SP { + self.sp = value; + } else { + self.r[r as usize * 2 + 1] = (value & 0xff) as u8; + self.r[r as usize * 2] = (value >> 8) as u8; + } + } + + pub fn check_flag(&self, flag: u8) -> bool { + let f = self.r[reg::F as usize]; + + match flag { + flag::NZ => f >> 7 == 0, + flag::Z => f >> 7 == 1, + flag::NC => (f >> 4) & 1 == 0, + flag::C => (f >> 4) & 1 == 1, + _ => unimplemented!(), + } + } +} + +pub struct Memory { + boot_rom: [u8; 0x900], + + pub cgb_mode: bool, + + pub bgcram_pointer: u8, + + pub bgcram_pointer_autoincrement: bool, + + pub obcram_pointer: u8, + + pub obcram_pointer_autoincrement: bool, + + pub boot_rom_on: bool, + + pub rom_bank: u8, + + pub ram_bank: u8, + + pub ram_bank_enabled: bool, + + // 32 KiB ROM bank 00 + rom: [u8; 0x200000], + + // 4 KiB Work RAM 00 + wram_00: [u8; 0x1000], + + // 4 KiB Work RAM 00 + wram_01: [u8; 0x1000], + + // External RAM + external_ram: [u8; 0x8000], + + // 8 KiB Video RAM + pub display: Display, + + pub io: [u8; 0x80], + + // High RAM + hram: [u8; 0x7f], + + pub audio: Audio, + + pub ime: bool, + + pub div: u8, + + pub joypad_reg: u8, + + pub joypad_is_action: bool, + + pub interrupts_register: u8, + + pub halt: bool, + + pub tima: u8, + + pub tma: u8, + + pub timer_enabled: bool, + + pub timer_speed: u8, +} + +#[derive(Debug)] +pub enum MemError { + WritingToROM, + Unimplemented, + NotUsable, +} + +impl Memory { + pub fn new() -> Self { + let mut display = Display::new(); + + display.cls(); + + Self { + boot_rom: [0; 0x900], + boot_rom_on: true, + cgb_mode: false, + bgcram_pointer: 0, + bgcram_pointer_autoincrement: false, + obcram_pointer: 0, + obcram_pointer_autoincrement: false, + rom_bank: 1, + ram_bank: 0, + ram_bank_enabled: false, + rom: [0; 0x200000], + wram_00: [0; 0x1000], + wram_01: [0; 0x1000], + external_ram: [0; 0x8000], + display, + io: [0; 0x80], + hram: [0; 0x7f], + audio: Audio::new(), + ime: false, + interrupts_register: 0, + joypad_is_action: false, + joypad_reg: 0, + div: 0, + halt: false, + tima: 0, + tma: 0, + timer_enabled: false, + timer_speed: 0, + } + } + + pub fn load_dmg_boot_rom(&mut self) { + let bytes = include_bytes!("../assets/dmg_boot.bin"); + + self.boot_rom[..0x100].copy_from_slice(bytes); + } + + pub fn load_cgb_boot_rom(&mut self) { + let bytes = include_bytes!("../assets/cgb_boot.bin"); + + self.boot_rom[..0x900].copy_from_slice(bytes); + } + + pub fn load_rom(&mut self, file: &str) -> Result<(), std::io::Error> { + let mut f = File::open(file)?; + + f.read(&mut self.rom)?; + + println!("MBC: {:02x}", self.rom[0x147]); + println!("CGB: {:02x}", self.rom[0x143]); + + if self.rom[0x143] == 0x80 || self.rom[0x143] == 0xc0 { + self.load_cgb_boot_rom(); + self.cgb_mode = true; + self.display.cgb_mode = true; + } else { + self.load_dmg_boot_rom(); + } + + Ok(()) + } + + pub fn load_external_ram(&mut self, file: &str) -> Result<(), std::io::Error> { + let mut f = File::open(file)?; + + f.read(&mut self.external_ram)?; + + println!("Save file loaded from \"{}\"!", file); + + Ok(()) + } + + pub fn save_external_ram(&self, file: &str) -> Result<(), std::io::Error> { + let mut f = File::create(file)?; + + f.write_all(&self.external_ram)?; + + println!("Save written to \"{}\"!", file); + + Ok(()) + } + + pub fn r(&self, addr: u16) -> Result<u8, MemError> { + if (addr < 0x100 || (addr >= 0x200 && addr < 0x900)) && self.boot_rom_on { + Ok(self.boot_rom[addr as usize]) + } else if addr < 0x4000 { + Ok(self.rom[addr as usize]) + } else if addr < 0x8000 { + Ok(self.rom[self.rom_bank as usize * 0x4000 + addr as usize - 0x4000 as usize]) + } else if addr >= 0xa000 && addr < 0xc000 { + if self.ram_bank_enabled { + Ok(self.external_ram[self.ram_bank as usize * 0x2000 + addr as usize - 0xa000]) + } else { + Ok(0xff) + } + } else if addr >= 0xc000 && addr < 0xd000 { + Ok(self.wram_00[addr as usize - 0xc000]) + } else if addr >= 0xd000 && addr < 0xe000 { + Ok(self.wram_01[addr as usize - 0xd000]) + } else if (addr >= 0x8000 && addr < 0xa000) || (addr >= 0xfe00 && addr < 0xfea0) { + self.display.r(addr & !0x8000) + } else if addr >= 0xff00 && addr < 0xff80 { + Ok(self.r_io((addr & 0xff) as u8)) + } else if addr >= 0xff80 && addr < 0xffff { + Ok(self.hram[addr as usize - 0xff80]) + } else if addr == 0xffff { + Ok(self.interrupts_register) + } else { + println!( + "Trying to read at address 0x{:04x} which is unimplemented", + addr + ); + Ok(0) //Err(MemError::Unimplemented) + } + } + + pub fn w(&mut self, addr: u16, value: u8) -> Result<(), MemError> { + if addr < 0x2000 { + self.ram_bank_enabled = value == 0x0a; + Ok(()) + } else if addr >= 0x2000 && addr < 0x4000 { + if value == 0 { + self.rom_bank = 1 + } else { + self.rom_bank = value & 0b1111111; + } + Ok(()) + } else if addr >= 0x4000 && addr < 0x6000 { + self.ram_bank = value & 0b11; + Ok(()) + } else if addr >= 0xa000 && addr < 0xc000 { + self.external_ram[self.ram_bank as usize * 0x2000 + addr as usize - 0xa000] = value; + Ok(()) + } else if addr >= 0xc000 && addr < 0xd000 { + self.wram_00[addr as usize - 0xc000] = value; + Ok(()) + } else if addr >= 0xd000 && addr < 0xe000 { + self.wram_01[addr as usize - 0xd000] = value; + Ok(()) + } else if (addr >= 0x8000 && addr < 0xa000) || (addr >= 0xfe00 && addr < 0xfea0) { + self.display.w(addr & !0x8000, value) + } else if addr >= 0xff00 && addr < 0xff80 { + Ok(self.w_io((addr & 0xff) as u8, value)?) + } else if addr >= 0xff80 && addr < 0xffff { + self.hram[addr as usize - 0xff80] = value; + Ok(()) + } else if addr == 0xffff { + self.interrupts_register = value; + Ok(()) + } else { + println!( + "Trying to write at address 0x{:04x} which is unimplemented (value: {:02x})", + addr, value + ); + Ok(()) //Err(MemError::Unimplemented) + } + } +} + +pub struct GBState { + pub cpu: CPU, + pub mem: Memory, + pub is_debug: bool, + + pub div_cycles: u64, + pub tima_cycles: u64, +} + +impl GBState { + pub fn new() -> Self { + let mem = Memory::new(); + + Self { + cpu: CPU::new(), + mem, + is_debug: false, + + div_cycles: 0, + tima_cycles: 0, + } + } + + pub fn r_reg(&self, r_i: u8) -> Result<u8, MemError> { + if r_i < 6 { + Ok(self.cpu.r[r_i as usize]) + } else if r_i == 7 { + Ok(self.cpu.r[6]) + } else if r_i == 6 { + self.mem.r(self.cpu.r16(reg::HL)) + } else { + panic!("r_i must be a 3 bits register input number") + } + } + + pub fn w_reg(&mut self, r_i: u8, value: u8) -> Result<(), MemError> { + if r_i < 6 { + self.cpu.r[r_i as usize] = value; + } else if r_i == 7 { + self.cpu.r[6] = value; + } else if r_i == 6 { + self.mem.w(self.cpu.r16(reg::HL), value)?; + } else { + panic!("r_i must be a 3 bits register input number") + } + Ok(()) + } + + pub fn debug(&self, s: &str) -> () { + if self.is_debug { + println!("{}", s); + } + } +} |