diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
new file mode 100644
index 00000000..9fd17576
--- /dev/null
+++ b/.github/workflows/rust.yml
@@ -0,0 +1,22 @@
+name: Rust
+
+on:
+ push:
+ branches: [ dev, master ]
+ pull_request:
+ branches: [ dev, master, feature/*, fix/* ]
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Build
+ run: cargo build --verbose
+ - name: Run tests
+ run: cargo test --verbose
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 00000000..0660ac1c
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,1587 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bstr"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
+dependencies = [
+ "lazy_static",
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+
+[[package]]
+name = "bzip2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.11+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cast"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a"
+dependencies = [
+ "rustc_version",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "bitflags",
+ "textwrap",
+ "unicode-width",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
+
+[[package]]
+name = "crc32fast"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "criterion"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10"
+dependencies = [
+ "atty",
+ "cast",
+ "clap",
+ "criterion-plot",
+ "csv",
+ "itertools",
+ "lazy_static",
+ "num-traits",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_cbor",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+ "lazy_static",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
+dependencies = [
+ "cfg-if 1.0.0",
+ "lazy_static",
+]
+
+[[package]]
+name = "csv"
+version = "1.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
+dependencies = [
+ "bstr",
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "fern"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "redox_syscall",
+ "winapi",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80edafed416a46fb378521624fab1cfa2eb514784fd8921adbe8a8d8321da811"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crc32fast",
+ "libc",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
+
+[[package]]
+name = "futures-io"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
+
+[[package]]
+name = "futures-task"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
+
+[[package]]
+name = "futures-util"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
+dependencies = [
+ "autocfg",
+ "futures-core",
+ "futures-io",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "gb"
+version = "1.0.0"
+dependencies = [
+ "cfg-if 1.0.0",
+ "chrono",
+ "crossbeam-channel",
+ "fern",
+ "lib_gb",
+ "log",
+ "sdl2",
+ "wav",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "h2"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "half"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac5956d4e63858efaec57e0d6c1c2f6a41e1487f830314a324ccd7e2223a7ca0"
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "http"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+
+[[package]]
+name = "httpdate"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
+
+[[package]]
+name = "hyper"
+version = "0.14.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
+dependencies = [
+ "bytes",
+ "hyper",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
+
+[[package]]
+name = "itertools"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "js-sys"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
+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 = "lib_gb"
+version = "1.0.0"
+dependencies = [
+ "criterion",
+ "log",
+ "reqwest",
+ "zip",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "memoffset"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
+dependencies = [
+ "adler",
+ "autocfg",
+]
+
+[[package]]
+name = "mio"
+version = "0.7.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
+dependencies = [
+ "libc",
+ "log",
+ "miow",
+ "ntapi",
+ "winapi",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+
+[[package]]
+name = "oorandom"
+version = "11.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+
+[[package]]
+name = "openssl"
+version = "0.10.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a"
+dependencies = [
+ "bitflags",
+ "cfg-if 1.0.0",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69df2d8dfc6ce3aaf44b40dec6f487d5a886516cf6879c49e98e0710f310a058"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
+
+[[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.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
+
+[[package]]
+name = "plotters"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rayon"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
+dependencies = [
+ "autocfg",
+ "crossbeam-deque",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "lazy_static",
+ "num_cpus",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-tls",
+ "ipnet",
+ "js-sys",
+ "lazy_static",
+ "log",
+ "mime",
+ "native-tls",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-native-tls",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "winreg",
+]
+
+[[package]]
+name = "riff"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9b1a3d5f46d53f4a3478e2be4a5a5ce5108ea58b100dcd139830eae7f79a3a1"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[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 = "schannel"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
+dependencies = [
+ "lazy_static",
+ "winapi",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "sdl2"
+version = "0.34.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "deecbc3fa9460acff5a1e563e05cb5f31bba0aa0c214bb49a43db8159176d54b"
+dependencies = [
+ "bitflags",
+ "lazy_static",
+ "libc",
+ "sdl2-sys",
+]
+
+[[package]]
+name = "sdl2-sys"
+version = "0.34.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41a29aa21f175b5a41a6e26da572d5e5d1ee5660d35f9f9d0913e8a802098f74"
+dependencies = [
+ "cfg-if 0.1.10",
+ "cmake",
+ "flate2",
+ "libc",
+ "tar",
+ "unidiff",
+ "version-compare",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
+
+[[package]]
+name = "serde"
+version = "1.0.130"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
+
+[[package]]
+name = "serde_cbor"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
+dependencies = [
+ "half",
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.130"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
+
+[[package]]
+name = "socket2"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f5515d3add52e0bbdcad7b83c388bb36ba7b754dda3b5f5bc2d38640cdba5c"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "rand",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi",
+ "winapi",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5241dd6f21443a3606b432718b166d3cedc962fd4b8bea54a8bc7f514ebda986"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
+[[package]]
+name = "tracing"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "unidiff"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a62719acf1933bfdbeb73a657ecd9ecece70b405125267dd549e2e2edc232c"
+dependencies = [
+ "encoding_rs",
+ "lazy_static",
+ "regex",
+]
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version-compare"
+version = "0.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1"
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
+dependencies = [
+ "cfg-if 1.0.0",
+ "serde",
+ "serde_json",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
+dependencies = [
+ "cfg-if 1.0.0",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
+
+[[package]]
+name = "wav"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a65e199c799848b4f997072aa4d673c034f80f40191f97fe2f0a23f410be1609"
+dependencies = [
+ "riff",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
+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 = "winreg"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "xattr"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "zip"
+version = "0.5.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815"
+dependencies = [
+ "byteorder",
+ "bzip2",
+ "crc32fast",
+ "flate2",
+ "thiserror",
+ "time",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 00000000..5c90c705
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = [
+ "gb",
+ "lib_gb"
+]
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..f288702d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/README.md b/README.md
index 219893b1..c8646236 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# MagenBoy
-A GameBoy emulator developped by me.
+A GameBoy emulator developed by me.
The main goal of this project is to be able to play Pokemon on my own emulator.
@@ -11,18 +11,35 @@ The main goal of this project is to be able to play Pokemon on my own emulator.
**More will be added if neccessary (and by neccessary I mean if games I want to play will require them)**
+## How to use
+
+```shell
+magenboy [path_to_rom] [other_optional_flags]
+```
+
+### Optional flags
+
+* `--log` - Print logs in debug mode to a file
+* `--file-audio` - Saves the audio to a file
+* `--full-screen` - Full screen mode
+* `--no-vsync` - Disable vsync
+* `--bootrom [path to bootrom file]` - Specify the path for a bootrom (If not specified the emualtor will look for `dmg_boot.bin` at the cwd)
+
## GameBoy
### Development Status
-- [Blargg's cpu_instrs](https://github.com/retrio/gb-test-roms/tree/master/cpu_instrs) - :thumbsup:
-- [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) - :thumbsup:
-- [TurtleTests](https://github.com/Powerlated/TurtleTests) - :thumbsup:
-- Accurate emulation -
+- CPU - Cycle accurate CPU
+- PPU - Cycle accurate fifo PPU
+- Timer - Mostly accurate timer
+- APU - Cycle mostly accurate APU
+- Tests
+ - [Blargg's cpu_instrs](https://github.com/retrio/gb-test-roms/tree/master/cpu_instrs) - :thumbsup:
+ - [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) - :thumbsup:
+ - [TurtleTests](https://github.com/Powerlated/TurtleTests) - :thumbsup:
- [CPU cycle accurate](https://github.com/retrio/gb-test-roms/tree/master/instr_timing) - :thumbsup:
- - PPU currently opcoce accurate - :thumbsup:
- - APU currently cycle accurate, passes some of [blargs dmg_sound tests](https://github.com/retrio/gb-test-roms/tree/master/dmg_sound)- :thumbsup:
- - Timer cycle acurate, passes most of [mooneye-gb tests](https://github.com/wilbertpol/mooneye-gb/tree/master/tests/acceptance/timer) - :thumbsup:
+ - APU passes some of [blargs dmg_sound tests](https://github.com/retrio/gb-test-roms/tree/master/dmg_sound)- :thumbsup:
+ - Timer passes most of [mooneye-gb tests](https://github.com/Gekkio/mooneye-gb/tree/master/tests/acceptance/timer) - :thumbsup:
### Games Tested
- Pokemon Red - :thumbsup:
@@ -30,4 +47,13 @@ The main goal of this project is to be able to play Pokemon on my own emulator.
## GameBoy Color
-Curerently there is no Support (support is planned in the future)
\ No newline at end of file
+Curerently there is no Support (support is planned in the future)
+
+## Resources
+- [The Pandocs](https://gbdev.io/pandocs/)
+- [gbops](https://izik1.github.io/gbops/index.html)
+- [The GameBoy Programming Manual](http://index-of.es/Varios-2/Game%20Boy%20Programming%20Manual.pdf)
+- [gbdev gameboy sound hardware](https://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware)
+- [Hactix's awsome blog post](https://hacktix.github.io/GBEDG/)
+- [Nightshade's awsome blog post](https://nightshade256.github.io/2021/03/27/gb-sound-emulation.html)
+- [The Ultimate GameBoy Talk](https://www.youtube.com/watch?v=HyzD8pNlpwI)
\ No newline at end of file
diff --git a/gb/Cargo.toml b/gb/Cargo.toml
index f9339ee7..7eac1e63 100644
--- a/gb/Cargo.toml
+++ b/gb/Cargo.toml
@@ -1,14 +1,27 @@
[package]
-name = "magenboy"
+name = "gb"
version = "1.0.0"
authors = ["alloncm "]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[[bin]]
+name = "magenboy"
+path = "src/main.rs"
+
[dependencies]
lib_gb = {path = "../lib_gb/"}
log = "0.4"
fern = "0.6.0"
chrono = "0.4"
-sdl2 = {version = "0.34", features = ["bundled","static-link"]}
-wav = "0.6.0"
\ No newline at end of file
+sdl2 = "0.34"
+wav = "1.0"
+crossbeam-channel = "0.5"
+cfg-if = "1.0"
+
+[features]
+default = ["static-sdl"]
+sdl-resample = []
+push-audio = []
+static-sdl = ["sdl2/bundled", "sdl2/static-link"]
+static-scale = []
\ No newline at end of file
diff --git a/gb/src/audio/magen_audio_resampler.rs b/gb/src/audio/magen_audio_resampler.rs
new file mode 100644
index 00000000..69f868c1
--- /dev/null
+++ b/gb/src/audio/magen_audio_resampler.rs
@@ -0,0 +1,79 @@
+use lib_gb::apu::audio_device::{BUFFER_SIZE, DEFAULT_SAPMPLE, Sample, StereoSample};
+use super::AudioResampler;
+
+pub struct MagenAudioResampler{
+ to_skip:u32,
+ sampling_buffer:Vec,
+ sampling_counter:u32,
+ reminder_steps:f32,
+ reminder_counter:f32,
+ alternate_to_skip:u32,
+ skip_to_use:u32,
+}
+
+impl MagenAudioResampler{
+ fn interpolate_sample(samples:&[StereoSample])->StereoSample{
+ let interpulated_left_sample = samples.iter().fold(DEFAULT_SAPMPLE, |acc, x| acc + x.left_sample) / samples.len() as Sample;
+ let interpulated_right_sample = samples.iter().fold(DEFAULT_SAPMPLE, |acc, x| acc + x.right_sample) / samples.len() as Sample;
+
+ return StereoSample{left_sample: interpulated_left_sample, right_sample: interpulated_right_sample};
+ }
+}
+
+impl AudioResampler for MagenAudioResampler{
+ fn new(original_frequency:u32, target_frequency:u32)->Self{
+ // Calling round in order to get the nearest integer and resample as precise as possible
+ let div = original_frequency as f32 / target_frequency as f32;
+
+ let lower_to_skip = div.floor() as u32;
+ let upper_to_skip = div.ceil() as u32;
+ let mut reminder = div.fract();
+ let (to_skip, alt_to_skip) = if reminder < 0.5{
+ (lower_to_skip, upper_to_skip)
+ }
+ else{
+ reminder = 1.0 - reminder;
+ (upper_to_skip, lower_to_skip)
+ };
+
+ if lower_to_skip == 0{
+ std::panic!("target freqency is too high: {}", target_frequency);
+ }
+
+ MagenAudioResampler{
+ to_skip:to_skip,
+ sampling_buffer:Vec::with_capacity(upper_to_skip as usize),
+ sampling_counter: 0,
+ reminder_steps:reminder,
+ reminder_counter:0.0,
+ alternate_to_skip: alt_to_skip,
+ skip_to_use:to_skip
+ }
+ }
+
+ fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{
+ let mut output = Vec::new();
+ for sample in buffer.into_iter(){
+ self.sampling_buffer.push(sample.clone());
+ self.sampling_counter += 1;
+
+ if self.sampling_counter == self.skip_to_use {
+ let interpolated_sample = Self::interpolate_sample(&self.sampling_buffer);
+ self.sampling_counter = 0;
+ self.sampling_buffer.clear();
+
+ output.push(interpolated_sample);
+ if self.reminder_counter >= 1.0{
+ self.skip_to_use = self.alternate_to_skip;
+ self.reminder_counter -= 1.0;
+ }
+ else{
+ self.skip_to_use = self.to_skip;
+ self.reminder_counter += self.reminder_steps;
+ }
+ }
+ }
+
+ return output;
+ }
+}
\ No newline at end of file
diff --git a/gb/src/audio/mod.rs b/gb/src/audio/mod.rs
new file mode 100644
index 00000000..bb17c34e
--- /dev/null
+++ b/gb/src/audio/mod.rs
@@ -0,0 +1,86 @@
+pub mod multi_device_audio;
+pub mod wav_file_audio_device;
+cfg_if::cfg_if!{
+ if #[cfg(feature = "push-audio")]{
+ pub mod sdl_push_audio_device;
+ pub type ChosenAudioDevice = sdl_push_audio_device::SdlPushAudioDevice;
+ }
+ else{
+ pub mod sdl_pull_audio_device;
+ pub type ChosenAudioDevice = sdl_pull_audio_device::SdlPullAudioDevice;
+ }
+}
+cfg_if::cfg_if!{
+ if #[cfg(feature = "sdl-resample")]{
+ pub mod sdl_audio_resampler;
+ pub type ChosenResampler = sdl_audio_resampler::SdlAudioResampler;
+ }
+ else{
+ pub mod magen_audio_resampler;
+ pub type ChosenResampler = magen_audio_resampler::MagenAudioResampler;
+ }
+}
+
+use std::{ffi::CStr, mem::MaybeUninit};
+use lib_gb::apu::audio_device::{AudioDevice, BUFFER_SIZE, Sample, StereoSample};
+use sdl2::{libc::c_char, sys::*};
+
+fn get_sdl_error_message()->&'static str{
+ unsafe{
+ let error_message:*const c_char = SDL_GetError();
+
+ return CStr::from_ptr(error_message).to_str().unwrap();
+ }
+}
+
+fn init_sdl_audio_device(audio_spec:&SDL_AudioSpec)->SDL_AudioDeviceID{
+ let mut uninit_audio_spec:MaybeUninit = MaybeUninit::uninit();
+
+ unsafe{
+ SDL_Init(SDL_INIT_AUDIO);
+ SDL_ClearError();
+ let id = SDL_OpenAudioDevice(std::ptr::null(), 0, audio_spec, uninit_audio_spec.as_mut_ptr() , 0);
+
+ if id == 0{
+ std::panic!("{}", get_sdl_error_message());
+ }
+
+ let init_audio_spec:SDL_AudioSpec = uninit_audio_spec.assume_init();
+
+ if init_audio_spec.freq != audio_spec.freq {
+ std::panic!("Error initializing audio could not use the frequency: {}", audio_spec.freq);
+ }
+
+ //This will start the audio processing
+ SDL_PauseAudioDevice(id, 0);
+ return id;
+ }
+}
+
+pub trait AudioResampler{
+ fn new(original_frequency:u32, target_frequency:u32)->Self;
+ fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec;
+}
+
+pub trait ResampledAudioDevice : AudioDevice{
+ const VOLUME:Sample = 10 as Sample;
+
+ fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]){
+ let resample = self.get_resampler().resample(buffer);
+ for sample in resample{
+ let(buffer, index) = self.get_audio_buffer();
+ buffer[*index] = sample.left_sample * Self::VOLUME;
+ buffer[*index + 1] = sample.left_sample * Self::VOLUME;
+ *index += 2;
+ if *index == BUFFER_SIZE{
+ *index = 0;
+ self.full_buffer_callback().unwrap();
+ }
+ }
+ }
+
+ fn get_audio_buffer(&mut self)->(&mut [Sample;BUFFER_SIZE], &mut usize);
+ fn get_resampler(&mut self)->&mut AR;
+ fn full_buffer_callback(&mut self)->Result<(), String>;
+ fn new(frequency:i32, turbo_mul:u8)->Self;
+}
diff --git a/gb/src/multi_device_audio.rs b/gb/src/audio/multi_device_audio.rs
similarity index 84%
rename from gb/src/multi_device_audio.rs
rename to gb/src/audio/multi_device_audio.rs
index fe169ad1..e90ba597 100644
--- a/gb/src/multi_device_audio.rs
+++ b/gb/src/audio/multi_device_audio.rs
@@ -11,7 +11,7 @@ impl MultiAudioDevice{
}
impl AudioDevice for MultiAudioDevice{
- fn push_buffer(&mut self, buffer:&[Sample]) {
+ fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) {
for device in self.devices.iter_mut(){
device.push_buffer(buffer);
}
diff --git a/gb/src/audio/sdl_audio_resampler.rs b/gb/src/audio/sdl_audio_resampler.rs
new file mode 100644
index 00000000..00e1c339
--- /dev/null
+++ b/gb/src/audio/sdl_audio_resampler.rs
@@ -0,0 +1,51 @@
+use std::mem::MaybeUninit;
+use lib_gb::apu::audio_device::{BUFFER_SIZE, StereoSample};
+use sdl2::sys::*;
+use super::AudioResampler;
+
+pub struct SdlAudioResampler{
+ cvt: SDL_AudioCVT
+}
+
+impl AudioResampler for SdlAudioResampler{
+ fn new(original_frequency:u32, target_frequency:u32)->Self{
+ let mut cvt = unsafe{
+ let mut cvt:MaybeUninit = MaybeUninit::uninit();
+ SDL_BuildAudioCVT(cvt.as_mut_ptr(), AUDIO_S16 as u16, 2, original_frequency as i32,
+ AUDIO_S16 as u16, 2, target_frequency as i32);
+ cvt.assume_init()
+ };
+
+ if cvt.needed != 1{
+ std::panic!("Cannot resample between freqs");
+ }
+
+ cvt.len = (BUFFER_SIZE * std::mem::size_of::()) as i32;
+
+ log::error!("help");
+
+ Self{cvt}
+ }
+
+ fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{
+ let mut buf:Vec:: = vec![0;(self.cvt.len * self.cvt.len_mult) as usize];
+
+ unsafe{
+ std::ptr::copy_nonoverlapping(buffer.as_ptr(), buf.as_mut_ptr() as *mut StereoSample, BUFFER_SIZE);
+
+ self.cvt.buf = buf.as_mut_ptr();
+ let status_code = SDL_ConvertAudio(&mut self.cvt) != 0;
+ if status_code{
+ std::panic!("error while converting audio, status code: {}", status_code);
+ }
+
+ let buf_ptr = self.cvt.buf as *mut StereoSample;
+ let length = self.cvt.len_cvt as usize / std::mem::size_of::();
+ let mut output = vec![StereoSample::const_defualt();length];
+
+ std::ptr::copy_nonoverlapping(buf_ptr, output.as_mut_ptr(), length);
+
+ return output;
+ }
+ }
+}
\ No newline at end of file
diff --git a/gb/src/audio/sdl_pull_audio_device.rs b/gb/src/audio/sdl_pull_audio_device.rs
new file mode 100644
index 00000000..efacca24
--- /dev/null
+++ b/gb/src/audio/sdl_pull_audio_device.rs
@@ -0,0 +1,123 @@
+use std::ffi::c_void;
+use lib_gb::{GB_FREQUENCY, apu::audio_device::*};
+use super::{AudioResampler, ResampledAudioDevice, init_sdl_audio_device};
+
+use sdl2::sys::*;
+use crossbeam_channel::{Receiver, Sender, bounded};
+
+const BUFFERS_NUMBER:usize = 3;
+
+struct UserData{
+ rx: Receiver,
+ current_buf: Option,
+ current_buf_index:usize,
+}
+
+pub struct SdlPullAudioDevice{
+ resampler: AR,
+ buffers: [[Sample;BUFFER_SIZE];BUFFERS_NUMBER],
+ buffer_number_index:usize,
+ buffer_index:usize,
+
+ tarnsmiter: Sender,
+ userdata: UserData,
+ device_id:SDL_AudioDeviceID,
+}
+
+impl ResampledAudioDevice for SdlPullAudioDevice{
+ fn new(frequency:i32, turbo_mul:u8)->Self{
+
+ // cap of less than 2 hurts the fps
+ let(s,r) = bounded(BUFFERS_NUMBER - 1);
+ let data = UserData{
+ current_buf:Option::None,
+ current_buf_index:0,
+ rx:r
+ };
+
+ let mut device = SdlPullAudioDevice{
+ buffers:[[DEFAULT_SAPMPLE;BUFFER_SIZE];BUFFERS_NUMBER],
+ buffer_index:0,
+ buffer_number_index:0,
+ resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32),
+ tarnsmiter:s,
+ userdata:data,
+ device_id:0
+ };
+
+ let desired_audio_spec = SDL_AudioSpec{
+ freq: frequency,
+ format: AUDIO_S16SYS as u16,
+ channels: 2,
+ silence: 0,
+ samples: BUFFER_SIZE as u16,
+ padding: 0,
+ size: 0,
+ callback: Option::Some(audio_callback),
+ userdata: (&mut device.userdata) as *mut UserData as *mut c_void
+ };
+
+ // Ignore device id
+ device.device_id = init_sdl_audio_device(&desired_audio_spec);
+
+ return device;
+ }
+
+ fn full_buffer_callback(&mut self) ->Result<(), String> {
+ let result = self.tarnsmiter.send(self.buffers[self.buffer_number_index].as_ptr() as usize).map_err(|e|e.to_string());
+ self.buffer_number_index = (self.buffer_number_index + 1) % BUFFERS_NUMBER;
+
+ return result;
+ }
+
+ fn get_audio_buffer(&mut self) ->(&mut [Sample;BUFFER_SIZE], &mut usize) {
+ (&mut self.buffers[self.buffer_number_index], &mut self.buffer_index)
+ }
+
+ fn get_resampler(&mut self) ->&mut AR {
+ &mut self.resampler
+ }
+}
+
+impl AudioDevice for SdlPullAudioDevice{
+ fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) {
+ ResampledAudioDevice::push_buffer(self, buffer);
+ }
+}
+
+impl Drop for SdlPullAudioDevice{
+ fn drop(&mut self) {
+ unsafe{
+ SDL_CloseAudioDevice(self.device_id);
+ }
+ }
+}
+
+unsafe extern "C" fn audio_callback(userdata:*mut c_void, buffer:*mut u8, length:i32){
+ let length = length as usize;
+ let safe_userdata = &mut *(userdata as *mut UserData);
+
+ if safe_userdata.current_buf.is_none(){
+ safe_userdata.current_buf = Some(safe_userdata.rx.recv().unwrap());
+ }
+
+ let samples = &*((safe_userdata.current_buf.unwrap()) as *const [Sample;BUFFER_SIZE]);
+ let samples_size = (samples.len() * std::mem::size_of::()) - safe_userdata.current_buf_index;
+ let samples_ptr = (samples.as_ptr() as *mut u8).add(safe_userdata.current_buf_index);
+ std::ptr::copy_nonoverlapping(samples_ptr, buffer, std::cmp::min(length, samples_size));
+
+ if length > samples_size && safe_userdata.rx.is_empty(){
+ safe_userdata.current_buf = Option::None;
+ safe_userdata.current_buf_index = 0;
+ std::ptr::write_bytes(buffer.add(samples.len() as usize), 0, length - samples_size);
+ }
+ else if length > samples_size{
+ safe_userdata.current_buf = Option::None;
+ safe_userdata.current_buf_index = 0;
+ audio_callback(userdata, buffer.add(samples_size), (length - samples_size) as i32);
+ }
+ else{
+ safe_userdata.current_buf_index = length;
+ }
+}
+
diff --git a/gb/src/audio/sdl_push_audio_device.rs b/gb/src/audio/sdl_push_audio_device.rs
new file mode 100644
index 00000000..dbe1ec08
--- /dev/null
+++ b/gb/src/audio/sdl_push_audio_device.rs
@@ -0,0 +1,79 @@
+use std::{ffi::c_void, str::FromStr};
+use lib_gb::{GB_FREQUENCY, apu::audio_device::{AudioDevice, BUFFER_SIZE, DEFAULT_SAPMPLE, Sample, StereoSample}};
+use sdl2::sys::*;
+use super::{AudioResampler, ResampledAudioDevice, get_sdl_error_message, init_sdl_audio_device};
+
+//After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing
+const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 16;
+
+pub struct SdlPushAudioDevice{
+ device_id: SDL_AudioDeviceID,
+ resampler: AR,
+
+ buffer: [Sample;BUFFER_SIZE],
+ buffer_index:usize,
+}
+
+impl SdlPushAudioDevice{
+ fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(),&str>{
+ let audio_ptr: *const c_void = audio.as_ptr() as *const c_void;
+ let data_byte_len = (audio.len() * std::mem::size_of::()) as u32;
+
+ unsafe{
+ while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{
+ SDL_Delay(1);
+ }
+
+ SDL_ClearError();
+ if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{
+ return Err(get_sdl_error_message());
+ }
+
+ Ok(())
+ }
+ }
+}
+
+impl ResampledAudioDevice for SdlPushAudioDevice{
+ fn new(frequency:i32, turbo_mul:u8)->Self{
+ let desired_audio_spec = SDL_AudioSpec{
+ freq: frequency,
+ format: AUDIO_S16SYS as u16,
+ channels: 2,
+ silence: 0,
+ samples: BUFFER_SIZE as u16,
+ padding: 0,
+ size: 0,
+ callback: Option::None,
+ userdata: std::ptr::null_mut()
+ };
+
+
+ let device_id = init_sdl_audio_device(&desired_audio_spec);
+
+ return SdlPushAudioDevice{
+ device_id: device_id,
+ buffer:[DEFAULT_SAPMPLE;BUFFER_SIZE],
+ buffer_index:0,
+ resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32)
+ };
+ }
+
+ fn get_audio_buffer(&mut self) ->(&mut [Sample;BUFFER_SIZE], &mut usize) {
+ (&mut self.buffer, &mut self.buffer_index)
+ }
+
+ fn get_resampler(&mut self) ->&mut AR {
+ &mut self.resampler
+ }
+
+ fn full_buffer_callback(&mut self)->Result<(), String> {
+ self.push_audio_to_device(&self.buffer).map_err(|e|String::from_str(e).unwrap())
+ }
+}
+
+impl AudioDevice for SdlPushAudioDevice{
+ fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) {
+ ResampledAudioDevice::push_buffer(self, buffer);
+ }
+}
\ No newline at end of file
diff --git a/gb/src/wav_file_audio_device.rs b/gb/src/audio/wav_file_audio_device.rs
similarity index 50%
rename from gb/src/wav_file_audio_device.rs
rename to gb/src/audio/wav_file_audio_device.rs
index 713c581c..e2377fcc 100644
--- a/gb/src/wav_file_audio_device.rs
+++ b/gb/src/audio/wav_file_audio_device.rs
@@ -1,15 +1,14 @@
use lib_gb::apu::audio_device::*;
+use super::AudioResampler;
-use crate::audio_resampler::AudioResampler;
-
-pub struct WavfileAudioDevice{
+pub struct WavfileAudioDevice{
target_frequency:u32,
- resampler: AudioResampler,
+ resampler: AR,
filename:&'static str,
- samples_buffer:Vec::
+ samples_buffer:Vec::
}
-impl WavfileAudioDevice{
+impl WavfileAudioDevice{
pub fn new(target_freq:u32, original_freq:u32, filename:&'static str)->Self{
WavfileAudioDevice{
filename,
@@ -20,22 +19,22 @@ impl WavfileAudioDevice{
}
}
-impl AudioDevice for WavfileAudioDevice{
- fn push_buffer(&mut self, buffer:&[Sample]) {
+impl AudioDevice for WavfileAudioDevice{
+ fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) {
self.samples_buffer.append(self.resampler.resample(buffer).as_mut());
}
}
-impl Drop for WavfileAudioDevice{
+impl Drop for WavfileAudioDevice{
fn drop(&mut self) {
- let header = wav::header::Header::new(wav::WAV_FORMAT_IEEE_FLOAT, 2, self.target_frequency, 32);
- let mut floats = Vec::with_capacity(self.samples_buffer.len() * 2);
+ let header = wav::header::Header::new(wav::WAV_FORMAT_PCM, 2, self.target_frequency, 16);
+ let mut samples = Vec::with_capacity(self.samples_buffer.len() * 2);
for sample in self.samples_buffer.iter(){
- floats.push(sample.left_sample);
- floats.push(sample.right_sample);
+ samples.push(sample.left_sample);
+ samples.push(sample.right_sample);
}
- let data = wav::BitDepth::ThirtyTwoFloat(floats);
+ let data = wav::BitDepth::Sixteen(samples);
let mut otuput_file = std::fs::File::create(self.filename).unwrap();
wav::write(header, &data, &mut otuput_file).unwrap();
}
diff --git a/gb/src/audio_resampler.rs b/gb/src/audio_resampler.rs
deleted file mode 100644
index 0001d5e1..00000000
--- a/gb/src/audio_resampler.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use lib_gb::apu::audio_device::Sample;
-
-pub struct AudioResampler{
- to_skip:u32,
- sampling_buffer:Vec,
- sampling_counter:u32
-}
-
-impl AudioResampler{
- pub fn new(original_frequency:u32, target_frequency:u32)->Self{
- let to_skip = original_frequency / target_frequency as u32;
- if to_skip == 0{
- std::panic!("target freqency is too high: {}", target_frequency);
- }
-
- AudioResampler{
- to_skip:to_skip,
- sampling_buffer:Vec::with_capacity(to_skip as usize),
- sampling_counter: 0
- }
- }
-
- pub fn resample(&mut self, buffer:&[Sample])->Vec{
- let mut output = Vec::new();
- for sample in buffer.into_iter(){
- self.sampling_buffer.push(*sample);
- self.sampling_counter += 1;
-
- if self.sampling_counter == self.to_skip {
- let (interpulated_left_sample, interpulated_right_sample) = Self::interpolate_sample(&self.sampling_buffer);
- let interpolated_sample = Sample{left_sample: interpulated_left_sample, right_sample: interpulated_right_sample};
- self.sampling_counter = 0;
- self.sampling_buffer.clear();
-
- output.push(interpolated_sample);
- }
- }
-
- return output;
- }
-
- fn interpolate_sample(samples:&[Sample])->(f32, f32){
-
- let interpulated_left_sample = samples.iter().fold(0.0, |acc, x| acc + x.left_sample) / samples.len() as f32;
- let interpulated_right_sample = samples.iter().fold(0.0, |acc, x| acc + x.right_sample) / samples.len() as f32;
-
- return (interpulated_left_sample, interpulated_right_sample);
- }
-}
\ No newline at end of file
diff --git a/gb/src/main.rs b/gb/src/main.rs
index 10f0dd15..ce6dee6a 100644
--- a/gb/src/main.rs
+++ b/gb/src/main.rs
@@ -1,35 +1,17 @@
mod mbc_handler;
mod sdl_joypad_provider;
-mod sdl_audio_device;
-mod audio_resampler;
-mod wav_file_audio_device;
-mod multi_device_audio;
-
-use crate::{mbc_handler::*, sdl_joypad_provider::*, multi_device_audio::*};
-use lib_gb::{keypad::button::Button, machine::gameboy::GameBoy, mmu::gb_mmu::BOOT_ROM_SIZE, ppu::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, GB_FREQUENCY, apu::audio_device::*};
-use std::{
- ffi::{c_void, CString},
- fs, env, result::Result, vec::Vec
-};
+mod sdl_gfx_device;
+mod mpmc_gfx_device;
+mod audio;
+
+use crate::{audio::{ChosenResampler, multi_device_audio::*, ResampledAudioDevice}, mbc_handler::*, mpmc_gfx_device::MpmcGfxDevice, sdl_joypad_provider::*};
+use lib_gb::{GB_FREQUENCY, apu::audio_device::*, keypad::button::Button, machine::gameboy::GameBoy, mmu::gb_mmu::BOOT_ROM_SIZE, ppu::{gb_ppu::{BUFFERS_NUMBER, SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice}};
+use std::{fs, env, result::Result, vec::Vec};
use log::info;
use sdl2::sys::*;
-
-fn extend_vec(vec:Vec, scale:usize, w:usize, h:usize)->Vec{
- let mut new_vec = vec![0;vec.len()*scale*scale];
- for y in 0..h{
- let sy = y*scale;
- for x in 0..w{
- let sx = x*scale;
- for i in 0..scale{
- for j in 0..scale{
- new_vec[(sy+i)*(w*scale)+sx+j] = vec[y*w+x];
- }
- }
- }
- }
- return new_vec;
-}
+const SCREEN_SCALE:usize = 4;
+const TURBO_MUL:u8 = 1;
fn init_logger(debug:bool)->Result<(), fern::InitError>{
let level = if debug {log::LevelFilter::Debug} else {log::LevelFilter::Info};
@@ -37,7 +19,7 @@ fn init_logger(debug:bool)->Result<(), fern::InitError>{
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}] {}",
- chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
+ chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S.%f]"),
record.level(),
message
))
@@ -74,8 +56,6 @@ fn check_for_terminal_feature_flag(args:&Vec::, flag:&str)->bool{
}
fn main() {
- let screen_scale:u32 = 4;
-
let args: Vec = env::args().collect();
let debug_level = check_for_terminal_feature_flag(&args, "--log");
@@ -85,85 +65,91 @@ fn main() {
Result::Err(error)=>std::panic!("error initing logger: {}", error)
}
- let buffer_width = SCREEN_WIDTH as u32 * screen_scale;
- let buffer_height = SCREEN_HEIGHT as u32* screen_scale;
- let program_name = CString::new("MagenBoy").unwrap();
- let (_window, renderer, texture): (*mut SDL_Window, *mut SDL_Renderer, *mut SDL_Texture) = unsafe{
- SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
- let wind:*mut SDL_Window = SDL_CreateWindow(
- program_name.as_ptr(),
- SDL_WINDOWPOS_UNDEFINED_MASK as i32, SDL_WINDOWPOS_UNDEFINED_MASK as i32,
- buffer_width as i32, buffer_height as i32, 0);
-
- let rend: *mut SDL_Renderer = SDL_CreateRenderer(wind, -1, 0);
+ let mut sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new("MagenBoy", SCREEN_SCALE, TURBO_MUL,
+ check_for_terminal_feature_flag(&args, "--no-vsync"), check_for_terminal_feature_flag(&args, "--full-screen"));
+
+ let (s,r) = crossbeam_channel::bounded(BUFFERS_NUMBER - 1);
+ let mpmc_device = MpmcGfxDevice::new(s);
- let tex: *mut SDL_Texture = SDL_CreateTexture(rend,
- SDL_PixelFormatEnum::SDL_PIXELFORMAT_ARGB8888 as u32, SDL_TextureAccess::SDL_TEXTUREACCESS_STREAMING as i32,
- buffer_width as i32, buffer_height as i32);
-
- (wind, rend, tex)
- };
+ let program_name = args[1].clone();
+
+ let mut running = true;
+ // Casting to ptr cause you cant pass a raw ptr (*const/mut T) to another thread
+ let running_ptr:usize = (&running as *const bool) as usize;
+
+ let emualation_thread = std::thread::Builder::new().name("Emualtion Thread".to_string()).spawn(
+ move || emulation_thread_main(args, program_name, mpmc_device, running_ptr)
+ ).unwrap();
+
+ unsafe{
+ let mut event: std::mem::MaybeUninit = std::mem::MaybeUninit::uninit();
+ loop{
+
+ if SDL_PollEvent(event.as_mut_ptr()) != 0{
+ let event: SDL_Event = event.assume_init();
+ if event.type_ == SDL_EventType::SDL_QUIT as u32{
+ break;
+ }
+ }
+
+ let buffer = r.recv().unwrap();
+ sdl_gfx_device.swap_buffer(&*(buffer as *const [u32; SCREEN_WIDTH * SCREEN_HEIGHT]));
+ }
+
+ drop(r);
+ std::ptr::write_volatile(&mut running as *mut bool, false);
+ emualation_thread.join().unwrap();
+
+ SDL_Quit();
+ }
+}
- let audio_device = sdl_audio_device::SdlAudioDevie::new(44100);
+// Receiving usize and not raw ptr cause in rust you cant pass a raw ptr to another thread
+fn emulation_thread_main(args: Vec, program_name: String, spsc_gfx_device: MpmcGfxDevice, running_ptr: usize) {
+ let audio_device = audio::ChosenAudioDevice::::new(44100, TURBO_MUL);
+
let mut devices: Vec::> = Vec::new();
devices.push(Box::new(audio_device));
if check_for_terminal_feature_flag(&args, "--file-audio"){
- let wav_ad = wav_file_audio_device::WavfileAudioDevice::new(44100, GB_FREQUENCY, "output.wav");
+ let wav_ad = audio::wav_file_audio_device::WavfileAudioDevice::::new(44100, GB_FREQUENCY, "output.wav");
devices.push(Box::new(wav_ad));
+ log::info!("Writing audio to file: output.wav");
}
-
let audio_devices = MultiAudioDevice::new(devices);
-
- let program_name = &args[1];
- let mut mbc = initialize_mbc(program_name);
+ let mut mbc = initialize_mbc(&program_name);
let joypad_provider = SdlJoypadProvider::new(buttons_mapper);
+ let bootrom_path = if check_for_terminal_feature_flag(&args, "--bootrom"){
+ let index = args.iter().position(|v| *v == String::from("--bootrom")).unwrap();
+ args.get(index + 1).expect("Error! you must specify a value for the --bootrom parameter").clone()
+ }else{
+ String::from("dmg_boot.bin")
+ };
- let mut gameboy = match fs::read("Dependencies\\Init\\dmg_boot.bin"){
+ let mut gameboy = match fs::read(bootrom_path){
Result::Ok(file)=>{
info!("found bootrom!");
-
+
let mut bootrom:[u8;BOOT_ROM_SIZE] = [0;BOOT_ROM_SIZE];
for i in 0..BOOT_ROM_SIZE{
bootrom[i] = file[i];
}
-
- GameBoy::new_with_bootrom(&mut mbc, joypad_provider,audio_devices, bootrom)
+
+ GameBoy::new_with_bootrom(&mut mbc, joypad_provider,audio_devices, spsc_gfx_device, bootrom)
}
Result::Err(_)=>{
info!("could not find bootrom... booting directly to rom");
-
- GameBoy::new(&mut mbc, joypad_provider, audio_devices)
+
+ GameBoy::new(&mut mbc, joypad_provider, audio_devices, spsc_gfx_device)
}
};
-
info!("initialized gameboy successfully!");
unsafe{
- let mut event: std::mem::MaybeUninit = std::mem::MaybeUninit::uninit();
- loop{
- if SDL_PollEvent(event.as_mut_ptr()) != 0{
- let event: SDL_Event = event.assume_init();
- if event.type_ == SDL_EventType::SDL_QUIT as u32{
- break;
- }
- }
-
- let frame_buffer:Vec = gameboy.cycle_frame().to_vec();
- let scaled_buffer = extend_vec(frame_buffer, screen_scale as usize, SCREEN_WIDTH, SCREEN_HEIGHT);
-
- let mut pixels: *mut c_void = std::ptr::null_mut();
- let mut length: std::os::raw::c_int = 0;
- SDL_LockTexture(texture, std::ptr::null(), &mut pixels, &mut length);
- std::ptr::copy_nonoverlapping(scaled_buffer.as_ptr(),pixels as *mut u32, scaled_buffer.len());
- SDL_UnlockTexture(texture);
-
- SDL_RenderClear(renderer);
- SDL_RenderCopy(renderer, texture, std::ptr::null(), std::ptr::null());
- SDL_RenderPresent(renderer);
+ while std::ptr::read_volatile(running_ptr as *const bool){
+ gameboy.cycle_frame();
}
-
- SDL_Quit();
}
drop(gameboy);
- release_mbc(program_name, mbc);
-}
+ release_mbc(&program_name, mbc);
+ log::info!("released the gameboy succefully");
+}
\ No newline at end of file
diff --git a/gb/src/mbc_handler.rs b/gb/src/mbc_handler.rs
index 879dbfa7..31619eb9 100644
--- a/gb/src/mbc_handler.rs
+++ b/gb/src/mbc_handler.rs
@@ -3,22 +3,17 @@ use std::boxed::Box;
use std::fs;
use log::info;
-const CARTRIDGE_TYPE_ADDRESS:usize = 0x147;
const PROGRAM_SUFFIX:&str = ".gb";
pub const SAVE_SUFFIX:&str = ".sav";
pub fn initialize_mbc(program_name:&String)->Box{
let program_path = format!("{}{}",program_name,PROGRAM_SUFFIX);
- let program = fs::read(program_path).expect("No program found, notice that function must have a `.gb` suffix");
-
- let mbc_type = program[CARTRIDGE_TYPE_ADDRESS];
-
- info!("initializing cartridge of type: {:#X}", mbc_type);
-
+ let error_message = format!("No program found, notice that the file must have a `.gb` suffix - {}\n", program_name);
+ let program = fs::read(program_path).expect(error_message.as_str());
let save_data = try_get_save_data(program_name);
- return lib_gb::machine::mbc_initializer::initialize_mbc(mbc_type, program, save_data);
+ return lib_gb::machine::mbc_initializer::initialize_mbc(program, save_data);
}
fn try_get_save_data(name:&String)->Option>{
diff --git a/gb/src/mpmc_gfx_device.rs b/gb/src/mpmc_gfx_device.rs
new file mode 100644
index 00000000..a42aca95
--- /dev/null
+++ b/gb/src/mpmc_gfx_device.rs
@@ -0,0 +1,19 @@
+use lib_gb::ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice};
+
+pub struct MpmcGfxDevice{
+ sender: crossbeam_channel::Sender
+}
+
+impl MpmcGfxDevice{
+ pub fn new(sender:crossbeam_channel::Sender)->Self{
+ Self{sender}
+ }
+}
+
+impl GfxDevice for MpmcGfxDevice{
+ fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) {
+ if self.sender.send(buffer.as_ptr() as usize).is_err(){
+ log::debug!("The receiver endpoint has been closed");
+ }
+ }
+}
\ No newline at end of file
diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs
deleted file mode 100644
index f2a13657..00000000
--- a/gb/src/sdl_audio_device.rs
+++ /dev/null
@@ -1,101 +0,0 @@
-use std::{vec::Vec,mem::MaybeUninit,ffi::{CStr, c_void}};
-use lib_gb::{GB_FREQUENCY, apu::audio_device::*};
-use sdl2::{sys::*,libc::c_char};
-use crate::audio_resampler::AudioResampler;
-
-//After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing
-const BUFFER_SIZE:usize = 1024 * 2;
-const SAMPLES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 4;
-
-pub struct SdlAudioDevie{
- device_id: SDL_AudioDeviceID,
- resampler: AudioResampler,
-
- buffer: Vec
-}
-
-impl SdlAudioDevie{
- pub fn new(frequency:i32)->Self{
-
- let desired_audio_spec = SDL_AudioSpec{
- freq: frequency,
- format: AUDIO_F32SYS as u16,
- channels: 2,
- silence: 0,
- samples: BUFFER_SIZE as u16,
- padding: 0,
- size: 0,
- callback: Option::None,
- userdata: std::ptr::null_mut()
- };
-
-
- let mut uninit_audio_spec:MaybeUninit = MaybeUninit::uninit();
-
- let device_id = unsafe{
- SDL_ClearError();
- let id = SDL_OpenAudioDevice(std::ptr::null(), 0, &desired_audio_spec, uninit_audio_spec.as_mut_ptr() , 0);
-
- if id == 0{
- std::panic!("{}",Self::get_sdl_error_message());
- }
-
- let init_audio_spec:SDL_AudioSpec = uninit_audio_spec.assume_init();
-
- if init_audio_spec.freq != frequency {
- std::panic!("Error initializing audio could not use the frequency: {}", frequency);
- }
-
- //This will start the audio processing
- SDL_PauseAudioDevice(id, 0);
-
- id
- };
-
- return SdlAudioDevie{
- device_id: device_id,
- buffer:Vec::with_capacity(BUFFER_SIZE),
- resampler: AudioResampler::new(GB_FREQUENCY, frequency as u32)
- };
- }
-
- fn get_sdl_error_message()->&'static str{
- unsafe{
- let error_message:*const c_char = SDL_GetError();
-
- return CStr::from_ptr(error_message).to_str().unwrap();
- }
- }
-
-
- fn push_audio_to_device(&self, audio:&[f32])->Result<(),&str>{
- let audio_ptr: *const c_void = audio.as_ptr() as *const c_void;
- let data_byte_len = (audio.len() * std::mem::size_of::()) as u32;
-
- unsafe{
- while SDL_GetQueuedAudioSize(self.device_id) > SAMPLES_TO_WAIT{}
-
- SDL_ClearError();
- if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{
- return Err(Self::get_sdl_error_message());
- }
-
- Ok(())
- }
- }
-}
-
-impl AudioDevice for SdlAudioDevie{
- fn push_buffer(&mut self, buffer:&[Sample]){
- for sample in self.resampler.resample(buffer){
-
- self.buffer.push(sample.left_sample);
- self.buffer.push(sample.right_sample);
-
- if self.buffer.len() == BUFFER_SIZE{
- self.push_audio_to_device(&self.buffer).unwrap();
- self.buffer.clear();
- }
- }
- }
-}
\ No newline at end of file
diff --git a/gb/src/sdl_gfx_device.rs b/gb/src/sdl_gfx_device.rs
new file mode 100644
index 00000000..28b51a5e
--- /dev/null
+++ b/gb/src/sdl_gfx_device.rs
@@ -0,0 +1,121 @@
+use std::ffi::{CString, c_void};
+
+use lib_gb::ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice};
+use sdl2::sys::*;
+
+pub struct SdlGfxDevice{
+ _window_name: CString,
+ renderer: *mut SDL_Renderer,
+ texture: *mut SDL_Texture,
+ discard:u8,
+ turbo_mul:u8,
+ #[cfg(feature = "static-scale")]
+ screen_scale:usize,
+}
+
+impl SdlGfxDevice{
+ pub fn new(window_name:&str, screen_scale: usize, turbo_mul:u8, disable_vsync:bool, full_screen:bool)->Self{
+ let cs_wnd_name = CString::new(window_name).unwrap();
+
+ let (_window, renderer, texture): (*mut SDL_Window, *mut SDL_Renderer, *mut SDL_Texture) = unsafe{
+ SDL_Init(SDL_INIT_VIDEO);
+
+ let window_flags = if full_screen{
+ #[cfg(feature = "static-scale")]
+ log::warn!("Please notice that this binary have been compiled with the static-scale feature and you are running with the full screen option.\nThe rendering window might be in wrong scale.");
+
+ SDL_WindowFlags::SDL_WINDOW_FULLSCREEN_DESKTOP as u32
+ }
+ else{
+ 0
+ };
+
+ let wind:*mut SDL_Window = SDL_CreateWindow(
+ cs_wnd_name.as_ptr(),
+ SDL_WINDOWPOS_UNDEFINED_MASK as i32, SDL_WINDOWPOS_UNDEFINED_MASK as i32,
+ SCREEN_WIDTH as i32 * screen_scale as i32, SCREEN_HEIGHT as i32 * screen_scale as i32,
+ window_flags);
+
+ let mut render_flags = SDL_RendererFlags::SDL_RENDERER_ACCELERATED as u32;
+ if !disable_vsync{
+ render_flags |= SDL_RendererFlags::SDL_RENDERER_PRESENTVSYNC as u32;
+ }
+
+ let rend: *mut SDL_Renderer = SDL_CreateRenderer(wind, -1, render_flags);
+
+ let texture_width:i32;
+ let texture_height:i32;
+
+ cfg_if::cfg_if!{
+ if #[cfg(feature = "static-scale")]{
+ texture_height = SCREEN_HEIGHT as i32 * screen_scale as i32;
+ texture_width = SCREEN_WIDTH as i32 * screen_scale as i32;
+ }
+ else{
+ if SDL_RenderSetLogicalSize(rend, (SCREEN_WIDTH as u32) as i32, (SCREEN_HEIGHT as u32) as i32) != 0{
+ std::panic!("Error while setting logical rendering");
+ }
+ texture_height = SCREEN_HEIGHT as i32;
+ texture_width = SCREEN_WIDTH as i32;
+ }
+ }
+
+ let tex: *mut SDL_Texture = SDL_CreateTexture(rend,
+ SDL_PixelFormatEnum::SDL_PIXELFORMAT_ARGB8888 as u32, SDL_TextureAccess::SDL_TEXTUREACCESS_STREAMING as i32,
+ texture_width, texture_height);
+
+ (wind, rend, tex)
+ };
+
+ Self{
+ _window_name: cs_wnd_name,
+ renderer,
+ texture,
+ discard:0,
+ turbo_mul,
+ #[cfg(feature = "static-scale")]
+ screen_scale
+ }
+ }
+
+ #[cfg(feature = "static-scale")]
+ fn extend_vec(vec:&[u32], scale:usize, w:usize, h:usize)->Vec{
+ let mut new_vec = vec![0;vec.len()*scale*scale];
+ for y in 0..h{
+ let sy = y*scale;
+ for x in 0..w{
+ let sx = x*scale;
+ for i in 0..scale{
+ for j in 0..scale{
+ new_vec[(sy+i)*(w*scale)+sx+j] = vec[y*w+x];
+ }
+ }
+ }
+ }
+ return new_vec;
+ }
+}
+
+impl GfxDevice for SdlGfxDevice{
+ fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) {
+ self.discard = (self.discard + 1) % self.turbo_mul;
+ if self.discard != 0{
+ return;
+ }
+
+ #[cfg(feature = "static-scale")]
+ let buffer = Self::extend_vec(buffer, self.screen_scale, SCREEN_WIDTH, SCREEN_HEIGHT);
+
+ unsafe{
+ let mut pixels: *mut c_void = std::ptr::null_mut();
+ let mut length: std::os::raw::c_int = 0;
+ SDL_LockTexture(self.texture, std::ptr::null(), &mut pixels, &mut length);
+ std::ptr::copy_nonoverlapping(buffer.as_ptr(),pixels as *mut u32, buffer.len());
+ SDL_UnlockTexture(self.texture);
+
+ //There is no need to call SDL_RenderClear since im replacing the whole buffer
+ SDL_RenderCopy(self.renderer, self.texture, std::ptr::null(), std::ptr::null());
+ SDL_RenderPresent(self.renderer);
+ }
+ }
+}
\ No newline at end of file
diff --git a/gb/src/sdl_joypad_provider.rs b/gb/src/sdl_joypad_provider.rs
index 9deaf697..b6d931d8 100644
--- a/gb/src/sdl_joypad_provider.rs
+++ b/gb/src/sdl_joypad_provider.rs
@@ -5,32 +5,41 @@ use lib_gb::keypad::{
button::Button
};
+const PUMP_THRESHOLD:u32 = 500;
+
pub struct SdlJoypadProviderSDL_Scancode>{
- mapper: F
+ mapper: F,
+ pump_counter:u32,
+ keyborad_state: *const u8
}
implSDL_Scancode> SdlJoypadProvider{
pub fn new(mapper:F)->Self{
SdlJoypadProvider{
- mapper
+ mapper,
+ pump_counter:PUMP_THRESHOLD,
+ keyborad_state: unsafe{SDL_GetKeyboardState(std::ptr::null_mut())}
}
}
}
implSDL_Scancode> JoypadProvider for SdlJoypadProvider{
- fn provide(&self, joypad:&mut Joypad) {
+ fn provide(&mut self, joypad:&mut Joypad) {
let mapper = &(self.mapper);
unsafe{
- let keyborad_state:*const u8 = SDL_GetKeyboardState(std::ptr::null_mut());
+ self.pump_counter = (self.pump_counter + 1) % PUMP_THRESHOLD;
+ if self.pump_counter == 0{
+ SDL_PumpEvents();
+ }
- joypad.buttons[Button::A as usize] = *keyborad_state.offset(mapper(Button::A) as isize) != 0;
- joypad.buttons[Button::B as usize] = *keyborad_state.offset(mapper(Button::B) as isize) != 0;
- joypad.buttons[Button::Start as usize] = *keyborad_state.offset(mapper(Button::Start) as isize) != 0;
- joypad.buttons[Button::Select as usize] = *keyborad_state.offset(mapper(Button::Select) as isize) != 0;
- joypad.buttons[Button::Up as usize] = *keyborad_state.offset(mapper(Button::Up) as isize) != 0;
- joypad.buttons[Button::Down as usize] = *keyborad_state.offset(mapper(Button::Down) as isize) != 0;
- joypad.buttons[Button::Right as usize] = *keyborad_state.offset(mapper(Button::Right) as isize) != 0;
- joypad.buttons[Button::Left as usize] = *keyborad_state.offset(mapper(Button::Left) as isize) != 0;
+ joypad.buttons[Button::A as usize] = *self.keyborad_state.offset(mapper(Button::A) as isize) != 0;
+ joypad.buttons[Button::B as usize] = *self.keyborad_state.offset(mapper(Button::B) as isize) != 0;
+ joypad.buttons[Button::Start as usize] = *self.keyborad_state.offset(mapper(Button::Start) as isize) != 0;
+ joypad.buttons[Button::Select as usize] = *self.keyborad_state.offset(mapper(Button::Select) as isize) != 0;
+ joypad.buttons[Button::Up as usize] = *self.keyborad_state.offset(mapper(Button::Up) as isize) != 0;
+ joypad.buttons[Button::Down as usize] = *self.keyborad_state.offset(mapper(Button::Down) as isize) != 0;
+ joypad.buttons[Button::Right as usize] = *self.keyborad_state.offset(mapper(Button::Right) as isize) != 0;
+ joypad.buttons[Button::Left as usize] = *self.keyborad_state.offset(mapper(Button::Left) as isize) != 0;
}
}
}
\ No newline at end of file
diff --git a/lib_gb/Cargo.toml b/lib_gb/Cargo.toml
index ca607529..29631e6f 100644
--- a/lib_gb/Cargo.toml
+++ b/lib_gb/Cargo.toml
@@ -3,5 +3,15 @@ name = "lib_gb"
version = "1.0.0"
authors = ["alloncm "]
edition = "2018"
+
[dependencies]
log = "0.4"
+
+[dev-dependencies]
+criterion = "0.3"
+reqwest = { version = "0.11", features = ["blocking"] }
+zip = "0.5"
+
+[[bench]]
+name = "lib_gb_bench"
+harness = false
\ No newline at end of file
diff --git a/lib_gb/benches/lib_gb_bench.rs b/lib_gb/benches/lib_gb_bench.rs
new file mode 100644
index 00000000..0fa41513
--- /dev/null
+++ b/lib_gb/benches/lib_gb_bench.rs
@@ -0,0 +1,51 @@
+use criterion::*;
+use lib_gb::apu::{
+ audio_device::*, channel::Channel,
+ gb_apu::*, sound_terminal::SoundTerminal,
+ square_sample_producer::SquareSampleProducer
+};
+
+pub fn criterion_bench(c: &mut Criterion){
+ struct StubApu;
+ impl AudioDevice for StubApu{
+ fn push_buffer(&mut self, _buffer:&[StereoSample; BUFFER_SIZE]){}
+ }
+
+ c.bench_function("test apu", |b| b.iter(||{
+ let mut apu = GbApu::new(StubApu{});
+ apu.enabled = true;
+ apu.sweep_tone_channel.enabled = true;
+ for _ in 0..100{
+ apu.cycle(10);
+ }
+ }));
+}
+
+pub fn apu_sweep_tone_channel(c: &mut Criterion){
+
+ c.bench_function("test square channel", |b|b.iter(||{
+ let mut channel = Channel::::new(SquareSampleProducer::new_with_sweep());
+ channel.sound_length = 63;
+ channel.enabled = true;
+ channel.length_enable = true;
+ while channel.enabled{
+ let _ = channel.get_audio_sample();
+ channel.update_length_register();
+ }
+ }));
+}
+
+pub fn apu_sound_terminal(c:&mut Criterion){
+ let mut sound_terminal = SoundTerminal::default();
+ for i in 0..4{
+ sound_terminal.set_channel_state(i, true);
+ }
+ sound_terminal.volume = 8;
+ c.bench_function("Sound terminal", |b| b.iter(||{
+ let samples:[Sample;4] = [100 as Sample,200 as Sample,5 as Sample,7 as Sample];
+ let _ = sound_terminal.mix_terminal_samples(black_box(&samples));
+ }));
+}
+
+criterion_group!(benches, criterion_bench, apu_sweep_tone_channel, apu_sound_terminal);
+criterion_main!(benches);
\ No newline at end of file
diff --git a/lib_gb/src/apu/apu_registers_updater.rs b/lib_gb/src/apu/apu_registers_updater.rs
index 40d33124..a864b130 100644
--- a/lib_gb/src/apu/apu_registers_updater.rs
+++ b/lib_gb/src/apu/apu_registers_updater.rs
@@ -1,5 +1,4 @@
-use crate::{mmu::{gb_mmu::GbMmu, memory::UnprotectedMemory, io_ports::*}, utils::{bit_masks::*, memory_registers::*}};
-
+use crate::{mmu::io_ports::*, utils::bit_masks::*};
use super::{
audio_device::AudioDevice,
channel::Channel,
@@ -14,195 +13,196 @@ use super::{
sound_utils::NUMBER_OF_CHANNELS
};
-pub fn update_apu_registers(memory:&mut GbMmu, apu:&mut GbApu){
- prepare_control_registers(apu, memory);
- if apu.enabled{
- prepare_wave_channel(&mut apu.wave_channel, memory, &apu.frame_sequencer);
- prepare_tone_sweep_channel(&mut apu.sweep_tone_channel, memory, &apu.frame_sequencer);
- prepare_noise_channel(&mut apu.noise_channel, memory, &apu.frame_sequencer);
- prepare_tone_channel(&mut apu.tone_channel, memory, &apu.frame_sequencer);
- }
+pub fn set_nr41(channel:&mut Channel, value:u8){
+ let length_data = value & 0b11_1111;
+ channel.sound_length = 64 - length_data as u16
}
-fn prepare_tone_channel(channel:&mut Channel, memory:&mut GbMmu,fs:&FrameSequencer){
-
- if memory.io_ports.get_ports_cycle_trigger()[NR21_REGISTER_INDEX as usize]{
- channel.sound_length = 64 - (memory.read_unprotected(NR21_REGISTER_ADDRESS) & 0b11_1111) as u16;
- }
- if memory.io_ports.get_ports_cycle_trigger()[NR22_REGISTER_INDEX as usize]{
- update_volume_envelope(memory.read_unprotected(NR22_REGISTER_ADDRESS), &mut channel.sample_producer.envelop);
-
- if !is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope){
- channel.enabled = false;
- }
- }
- if memory.io_ports.get_ports_cycle_trigger()[NR23_REGISTER_INDEX as usize]{
- //discard lower bit
- channel.frequency &= 0xFF00;
- channel.frequency |= memory.read_unprotected(NR23_REGISTER_ADDRESS) as u16;
- }
- if memory.io_ports.get_ports_cycle_trigger()[NR24_REGISTER_INDEX as usize]{
- let nr24 = memory.read_unprotected(NR24_REGISTER_ADDRESS);
- //discrad upper bit
- channel.frequency <<= 8;
- channel.frequency >>= 8;
- channel.frequency |= (nr24 as u16 & 0b111) << 8;
- let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope);
- update_channel_conrol_register(channel, dac_enabled, nr24, 64, fs);
- if nr24 & BIT_7_MASK != 0{
- //volume
- channel.sample_producer.envelop.envelop_duration_counter = channel.sample_producer.envelop.number_of_envelope_sweep;
- channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume;
- }
+pub fn set_nr42(channel:&mut Channel, value:u8){
+ update_volume_envelope( value, &mut channel.sample_producer.envelop);
+ if !is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope){
+ channel.enabled = false;
}
}
-fn prepare_noise_channel(channel:&mut Channel, memory:&mut GbMmu,fs:&FrameSequencer){
+pub fn set_nr43(channel:&mut Channel, nr43:u8){
+ channel.sample_producer.bits_to_shift_divisor = (nr43 & 0b1111_0000) >> 4;
+ channel.sample_producer.width_mode = (nr43 & BIT_3_MASK) != 0;
+ channel.sample_producer.divisor_code = nr43 & 0b111;
+}
- if memory.io_ports.get_ports_cycle_trigger()[NR41_REGISTER_INDEX as usize]{
- let length_data = memory.read_unprotected(NR41_REGISTER_ADDRESS) & 0b11_1111;
- channel.sound_length = 64 - length_data as u16
- }
- if memory.io_ports.get_ports_cycle_trigger()[NR42_REGISTER_INDEX as usize]{
- update_volume_envelope( memory.read_unprotected(NR42_REGISTER_ADDRESS), &mut channel.sample_producer.envelop);
- if !is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope){
- channel.enabled = false;
- }
- }
- if memory.io_ports.get_ports_cycle_trigger()[NR43_REGISTER_INDEX as usize]{
- let nr43 = memory.read_unprotected(NR43_REGISTER_ADDRESS);
- channel.sample_producer.bits_to_shift_divisor = (nr43 & 0b1111_0000) >> 4;
- channel.sample_producer.width_mode = (nr43 & BIT_3_MASK) != 0;
- channel.sample_producer.divisor_code = nr43 & 0b111;
- }
- if memory.io_ports.get_ports_cycle_trigger()[NR44_REGISTER_INDEX as usize]{
- let nr44 = memory.read_unprotected(NR44_REGISTER_ADDRESS);
- let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope);
- update_channel_conrol_register(channel, dac_enabled, nr44, 64, fs);
- if (nr44 & BIT_7_MASK) != 0{
- //On trigger all the LFSR bits are set (lfsr is 15 bit register)
- channel.sample_producer.lfsr = 0x7FFF;
-
-
- channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume;
- }
+pub fn set_nr44(channel:&mut Channel, fs:&FrameSequencer, nr44:u8){
+ let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope);
+ update_channel_conrol_register(channel, dac_enabled, nr44, 64, fs);
+ if (nr44 & BIT_7_MASK) != 0{
+ //On trigger all the LFSR bits are set (lfsr is 15 bit register)
+ channel.sample_producer.lfsr = 0x7FFF;
+
+ channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume;
}
}
-fn prepare_control_registers(apu:&mut GbApu, memory:&impl UnprotectedMemory){
- let channel_control = memory.read_unprotected(NR50_REGISTER_ADDRESS);
- apu.right_terminal.enabled = channel_control & BIT_3_MASK != 0;
- apu.left_terminal.enabled = channel_control & BIT_7_MASK != 0;
-
- apu.right_terminal.volume = channel_control & 0b111;
- apu.left_terminal.volume = (channel_control & 0b111_0000) >> 4;
+pub fn set_nr50(apu:&mut GbApu, nr50:u8){
+ apu.right_terminal.volume = nr50 & 0b111;
+ apu.left_terminal.volume = (nr50 & 0b111_0000) >> 4;
+}
- let channels_output_terminals = memory.read_unprotected(NR51_REGISTER_ADDRESS);
+pub fn set_nr51(apu:&mut GbApu, nr51:u8){
for i in 0..NUMBER_OF_CHANNELS{
- apu.right_terminal.channels[i as usize] = channels_output_terminals & (1 << i) != 0;
+ apu.right_terminal.set_channel_state(i, nr51 & (1 << i) != 0);
}
for i in 0..NUMBER_OF_CHANNELS{
- apu.left_terminal.channels[i as usize] = channels_output_terminals & (0b1_0000 << i) != 0;
+ apu.left_terminal.set_channel_state(i, nr51 & (0b1_0000 << i) != 0);
}
-
- let master_sound = memory.read_unprotected(NR52_REGISTER_ADDRESS);
- apu.enabled = master_sound & BIT_7_MASK != 0;
}
-fn prepare_wave_channel(channel:&mut Channel, memory:&mut GbMmu,fs:&FrameSequencer){
+pub fn set_nr52(apu:&mut GbApu, ports:&mut [u8;IO_PORTS_SIZE], nr52:u8){
+ let prev_apu_state = apu.enabled;
+ apu.enabled = nr52 & BIT_7_MASK != 0;
- if memory.io_ports.get_ports_cycle_trigger()[NR30_REGISTER_INDEX as usize]{
- if (memory.read_unprotected(NR30_REGISTER_ADDRESS) & BIT_7_MASK) == 0{
- channel.enabled = false;
- }
- }
- if memory.io_ports.get_ports_cycle_trigger()[NR31_REGISTER_INDEX as usize]{
- channel.sound_length = 256 - (memory.read_unprotected(NR31_REGISTER_ADDRESS) as u16);
+ // Apu turned off
+ if !apu.enabled && prev_apu_state{
+ apu.reset();
}
- if memory.io_ports.get_ports_cycle_trigger()[NR32_REGISTER_INDEX as usize]{
- //I want bits 5-6
- let nr32 = memory.read_unprotected(NR32_REGISTER_ADDRESS);
- channel.sample_producer.volume = (nr32 & 0b110_0000) >> 5;
+
+ for i in NR10_REGISTER_INDEX..NR52_REGISTER_INDEX{
+ ports[i as usize] = 0;
}
- if memory.io_ports.get_ports_cycle_trigger()[NR33_REGISTER_INDEX as usize]{
- //discard lower 8 bits
- channel.frequency &= 0xFF00;
- channel.frequency |= memory.read_unprotected(NR33_REGISTER_ADDRESS) as u16;
+}
+
+pub fn get_nr52(apu:&GbApu, nr52:&mut u8){
+ set_bit_u8(nr52, 3, apu.noise_channel.enabled && apu.noise_channel.length_enable && apu.noise_channel.sound_length != 0);
+ set_bit_u8(nr52, 2, apu.wave_channel.enabled && apu.wave_channel.length_enable && apu.wave_channel.sound_length != 0);
+ set_bit_u8(nr52, 1, apu.tone_channel.enabled && apu.tone_channel.length_enable && apu.tone_channel.sound_length != 0);
+ set_bit_u8(nr52, 0, apu.sweep_tone_channel.enabled && apu.sweep_tone_channel.length_enable && apu.sweep_tone_channel.sound_length != 0);
+}
+
+pub fn set_nr30(channel:&mut Channel, value:u8){
+ if (value & BIT_7_MASK) == 0{
+ channel.enabled = false;
}
- if memory.io_ports.get_ports_cycle_trigger()[NR34_REGISTER_INDEX as usize]{
- let nr34 = memory.read_unprotected(NR34_REGISTER_ADDRESS);
+}
- //clear the upper 8 bits
- channel.frequency &= 0xFF;
- channel.frequency |= ((nr34 & 0b111) as u16) << 8;
+pub fn set_nr31(channel:&mut Channel, value:u8){
+ channel.sound_length = 256 - (value as u16);
+}
- let dac_enabled = (memory.read_unprotected(NR30_REGISTER_ADDRESS) & BIT_7_MASK) != 0;
- update_channel_conrol_register(channel, dac_enabled, nr34, 256, fs);
+pub fn set_nr32(channel:&mut Channel, nr32:u8){
+ //I want bits 5-6
+ channel.sample_producer.volume = (nr32 & 0b110_0000) >> 5;
+}
- if nr34 & BIT_7_MASK != 0{
- channel.sample_producer.reset_counter();
- }
+pub fn set_nr33(channel:&mut Channel, nr33:u8){
+ //discard lower 8 bits
+ channel.frequency &= 0xFF00;
+ channel.frequency |= nr33 as u16;
+}
+
+pub fn set_nr34(channel:&mut Channel, fs:&FrameSequencer, nr30:u8, nr34:u8){
+ //clear the upper 8 bits
+ channel.frequency &= 0xFF;
+ channel.frequency |= ((nr34 & 0b111) as u16) << 8;
+
+ let dac_enabled = (nr30 & BIT_7_MASK) != 0;
+ update_channel_conrol_register(channel, dac_enabled, nr34, 256, fs);
+
+ if nr34 & BIT_7_MASK != 0{
+ channel.sample_producer.reset_counter();
}
+}
- for i in 0..=0xF{
- channel.sample_producer.wave_samples[i] = memory.read_unprotected(0xFF30 + i as u16);
+pub fn set_nr10(channel:&mut Channel, value:u8){
+ let sweep = channel.sample_producer.sweep.as_mut().unwrap();
+ sweep.sweep_decrease = (value & 0b1000) != 0;
+ sweep.sweep_shift = value & 0b111;
+ sweep.sweep_period = (value & 0b111_0000) >> 4;
+}
+
+pub fn set_nr11(channel:&mut Channel, value:u8){
+ channel.sample_producer.wave_duty = (value & 0b1100_0000) >> 6;
+ channel.sound_length = 64 - (value & 0b11_1111) as u16
+}
+ pub fn set_nr12(channel:&mut Channel, value:u8){
+ update_volume_envelope(value, &mut channel.sample_producer.envelop);
+
+ if !is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope){
+ channel.enabled = false;
}
}
-fn prepare_tone_sweep_channel(channel:&mut Channel, memory:&mut GbMmu, fs:&FrameSequencer){
- let nr10 = memory.read_unprotected(NR10_REGISTER_ADDRESS);
- let nr11 = memory.read_unprotected(NR11_REGISTER_ADDRESS);
- let nr12 = memory.read_unprotected(NR12_REGISTER_ADDRESS);
- let nr13 = memory.read_unprotected(NR13_REGISTER_ADDRESS);
- let nr14 = memory.read_unprotected(NR14_REGISTER_ADDRESS);
+ pub fn set_nr13(channel:&mut Channel, value:u8){
+ //discard lower bits
+ channel.frequency &= 0xFF00;
+ channel.frequency |= value as u16;
+}
+
+pub fn set_nr14(channel:&mut Channel, fs:&FrameSequencer, nr14:u8){
+ //discard upper bits
+ channel.frequency &= 0xFF;
+ channel.frequency |= ((nr14 & 0b111) as u16) << 8;
+
+ let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope);
+ update_channel_conrol_register(channel, dac_enabled, nr14, 64, fs);
- if memory.io_ports.get_ports_cycle_trigger()[NR10_REGISTER_INDEX as usize]{
+ if nr14 & BIT_7_MASK != 0{
+ //volume
+ channel.sample_producer.envelop.envelop_duration_counter = channel.sample_producer.envelop.number_of_envelope_sweep;
+ channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume;
+
//sweep
let sweep = channel.sample_producer.sweep.as_mut().unwrap();
- sweep.sweep_decrease = (nr10 & 0b1000) != 0;
- sweep.sweep_shift = nr10 & 0b111;
- sweep.sweep_period = (nr10 & 0b111_0000) >> 4;
+ sweep.channel_trigger(channel.frequency);
+ if sweep.sweep_shift > 0{
+ let freq = sweep.calculate_new_frequency();
+ channel.enabled = !FreqSweep::check_overflow(freq);
+ }
+
}
- if memory.io_ports.get_ports_cycle_trigger()[NR11_REGISTER_INDEX as usize]{
- channel.sample_producer.wave_duty = (nr11 & 0b1100_0000) >> 6;
- channel.sound_length = 64 - (nr11 & 0b11_1111) as u16
+}
+
+pub fn set_nr24(channel:&mut Channel, fs:&FrameSequencer, nr14:u8){
+ //discard upper bits
+ channel.frequency &= 0xFF;
+ channel.frequency |= ((nr14 & 0b111) as u16) << 8;
+
+ let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope);
+ update_channel_conrol_register(channel, dac_enabled, nr14, 64, fs);
+
+ if nr14 & BIT_7_MASK != 0{
+ //volume
+ channel.sample_producer.envelop.envelop_duration_counter = channel.sample_producer.envelop.number_of_envelope_sweep;
+ channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume;
}
- if memory.io_ports.get_ports_cycle_trigger()[NR12_REGISTER_INDEX as usize]{
- update_volume_envelope(nr12, &mut channel.sample_producer.envelop);
-
- if !is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope){
- channel.enabled = false;
- }
+}
+
+pub fn set_wave_ram(wave_channel: &mut Channel, address:u16, value:u8){
+ // This is not the most accurate behaviour.
+ // Om DMG if accessing within a small time margin after the apu accessed this ram,
+ // you will access the current byte the apu accessed, otherwise it will do noting.
+ // On other hardware it will access the current byte the apu accessed,
+ // currently implementing it like the original DMG on CGB will need to handle it differently.
+ if !wave_channel.enabled{
+ wave_channel.sample_producer.wave_samples[address as usize - 0x30] = value;
}
- if memory.io_ports.get_ports_cycle_trigger()[NR13_REGISTER_INDEX as usize]{
- //discard lower bits
- channel.frequency &= 0xFF00;
- channel.frequency |= nr13 as u16;
+ else{
+ log::warn!("Writing wave channel when its on");
}
- if memory.io_ports.get_ports_cycle_trigger()[NR14_REGISTER_INDEX as usize]{
- //discard upper bits
- channel.frequency &= 0xFF;
- channel.frequency |= ((nr14 & 0b111) as u16) << 8;
-
- let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope);
- update_channel_conrol_register(channel, dac_enabled, nr14, 64, fs);
-
- if nr14 & BIT_7_MASK != 0{
- //volume
- channel.sample_producer.envelop.envelop_duration_counter = channel.sample_producer.envelop.number_of_envelope_sweep;
- channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume;
-
- //sweep
- let sweep = channel.sample_producer.sweep.as_mut().unwrap();
- sweep.channel_trigger(channel.frequency);
- if sweep.sweep_shift > 0{
- let freq = sweep.calculate_new_frequency();
- channel.enabled = !FreqSweep::check_overflow(freq);
- }
-
- }
+}
+
+pub fn get_wave_ram(wave_channel: &Channel, address:u16)->u8{
+ // This is not the most accurate behaviour.
+ // Om DMG if accessing within a small time margin after the apu accessed this ram,
+ // you will access the current byte the apu accessed, otherwise it will return 0xFF.
+ // On other hardware it will access the current byte the apu accessed,
+ // currently implementing it like the original DMG on CGB will need to handle it differently.
+ if !wave_channel.enabled{
+ wave_channel.sample_producer.wave_samples[address as usize - 0x30]
+ }
+ else{
+ log::warn!("Reading wave channel when its on");
+ 0xFF
}
}
diff --git a/lib_gb/src/apu/audio_device.rs b/lib_gb/src/apu/audio_device.rs
index 76a2f988..a4f4939d 100644
--- a/lib_gb/src/apu/audio_device.rs
+++ b/lib_gb/src/apu/audio_device.rs
@@ -1,9 +1,26 @@
-#[derive(Copy, Clone)]
-pub struct Sample{
- pub left_sample:f32,
- pub right_sample:f32
+pub type Sample = i16;
+pub const DEFAULT_SAPMPLE:Sample = 0 as Sample;
+
+pub const BUFFER_SIZE:usize = 2048;
+
+#[repr(C, packed)]
+pub struct StereoSample{
+ pub left_sample:Sample,
+ pub right_sample:Sample
+}
+
+impl StereoSample{
+ pub const fn const_defualt()->Self{
+ Self{left_sample:DEFAULT_SAPMPLE, right_sample:DEFAULT_SAPMPLE}
+ }
+}
+
+impl Clone for StereoSample{
+ fn clone(&self) -> Self {
+ Self{left_sample:self.left_sample,right_sample:self.right_sample}
+ }
}
pub trait AudioDevice{
- fn push_buffer(&mut self, buffer:&[Sample]);
+ fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]);
}
\ No newline at end of file
diff --git a/lib_gb/src/apu/channel.rs b/lib_gb/src/apu/channel.rs
index bc3e19e4..9d7d6a41 100644
--- a/lib_gb/src/apu/channel.rs
+++ b/lib_gb/src/apu/channel.rs
@@ -1,3 +1,4 @@
+use super::audio_device::{DEFAULT_SAPMPLE, Sample};
use super::sample_producer::SampleProducer;
use super::timer::Timer;
@@ -9,7 +10,7 @@ pub struct Channel{
pub sample_producer:Procuder,
pub timer:Timer,
- last_sample:u8,
+ last_sample:Sample,
}
impl Channel{
@@ -21,7 +22,7 @@ impl Channel{
length_enable:false,
timer: Timer::new(sample_producer.get_updated_frequency_ticks(0)),
sample_producer,
- last_sample: 0
+ last_sample: DEFAULT_SAPMPLE
}
}
@@ -44,15 +45,15 @@ impl Channel{
self.timer.update_cycles_to_tick(self.sample_producer.get_updated_frequency_ticks(self.frequency));
self.sample_producer.reset();
- self.last_sample = 0;
+ self.last_sample = DEFAULT_SAPMPLE;
}
- pub fn get_audio_sample(&mut self)->f32{
+ pub fn get_audio_sample(&mut self)->Sample{
if self.enabled{
let sample = if self.timer.cycle(){
self.timer.update_cycles_to_tick(self.sample_producer.get_updated_frequency_ticks(self.frequency));
- self.sample_producer.produce()
+ self.sample_producer.produce() as Sample
}
else{
self.last_sample
@@ -60,13 +61,9 @@ impl Channel{
self.last_sample = sample;
- return self.convert_digtial_to_analog(self.last_sample);
+ return self.last_sample;
}
- return 0.0;
- }
-
- fn convert_digtial_to_analog(&self, sample:u8)->f32{
- (sample as f32 / 7.5 ) - 1.0
+ return DEFAULT_SAPMPLE;
}
}
diff --git a/lib_gb/src/apu/frame_sequencer.rs b/lib_gb/src/apu/frame_sequencer.rs
index c3ca4f6d..405cd26b 100644
--- a/lib_gb/src/apu/frame_sequencer.rs
+++ b/lib_gb/src/apu/frame_sequencer.rs
@@ -52,7 +52,7 @@ impl FrameSequencer{
// in a rare case where the length hasnt started the second iteration there might be a length step.
// This will happen only after if the channel is activated after being not active.
pub fn should_next_step_clock_length(&self)->bool{
- self.counter % 2 == 0
+ self.counter & 1 == 0
}
pub fn reset(&mut self){
diff --git a/lib_gb/src/apu/gb_apu.rs b/lib_gb/src/apu/gb_apu.rs
index 12cb1f7a..6233f458 100644
--- a/lib_gb/src/apu/gb_apu.rs
+++ b/lib_gb/src/apu/gb_apu.rs
@@ -9,12 +9,6 @@ use super::{
wave_sample_producer::WaveSampleProducer,
sound_utils::NUMBER_OF_CHANNELS
};
-use crate::{
- mmu::memory::UnprotectedMemory,
- utils::{bit_masks::set_bit_u8, memory_registers::{NR10_REGISTER_ADDRESS, NR52_REGISTER_ADDRESS}}
-};
-
-pub const AUDIO_BUFFER_SIZE:usize = 0x400;
pub struct GbApu{
pub wave_channel:Channel,
@@ -26,8 +20,8 @@ pub struct GbApu{
pub left_terminal:SoundTerminal,
pub enabled:bool,
- audio_buffer:[Sample;AUDIO_BUFFER_SIZE],
- current_t_cycle:u32,
+ audio_buffer:[StereoSample;BUFFER_SIZE],
+ current_m_cycle:u32,
device:Device,
last_enabled_state:bool
}
@@ -40,8 +34,8 @@ impl GbApu{
wave_channel:Channel::::new(WaveSampleProducer::default()),
tone_channel: Channel::::new(SquareSampleProducer::new()),
noise_channel: Channel::::new(NoiseSampleProducer::default()),
- audio_buffer:[Sample{left_sample:0.0, right_sample:0.0}; AUDIO_BUFFER_SIZE],
- current_t_cycle:0,
+ audio_buffer:crate::utils::create_array(StereoSample::const_defualt),
+ current_m_cycle:0,
device:device,
right_terminal: SoundTerminal::default(),
left_terminal: SoundTerminal::default(),
@@ -50,17 +44,14 @@ impl GbApu{
}
}
- pub fn cycle(&mut self, memory:&mut impl UnprotectedMemory, m_cycles_passed:u8){
- //converting m_cycles to t_cycles
- let t_cycles = m_cycles_passed * 4;
-
+ pub fn cycle(&mut self, m_cycles_passed:u8){
if self.enabled{
- for _ in 0..t_cycles{
+ for _ in 0..m_cycles_passed{
let tick = self.frame_sequencer.cycle();
self.update_channels_for_frame_squencer(tick);
- let mut samples:[f32;NUMBER_OF_CHANNELS] = [0.0;NUMBER_OF_CHANNELS];
+ let mut samples:[Sample;NUMBER_OF_CHANNELS] = [DEFAULT_SAPMPLE ; NUMBER_OF_CHANNELS];
samples[0] = self.sweep_tone_channel.get_audio_sample();
samples[1] = self.tone_channel.get_audio_sample();
samples[2] = self.wave_channel.get_audio_sample();
@@ -69,42 +60,37 @@ impl GbApu{
let left_sample = self.left_terminal.mix_terminal_samples(&samples);
let right_sample = self.right_terminal.mix_terminal_samples(&samples);
- self.audio_buffer[self.current_t_cycle as usize].left_sample = left_sample;
- self.audio_buffer[self.current_t_cycle as usize].right_sample = right_sample;
+ self.audio_buffer[self.current_m_cycle as usize].left_sample = left_sample;
+ self.audio_buffer[self.current_m_cycle as usize].right_sample = right_sample;
- self.current_t_cycle += 1;
+ self.current_m_cycle += 1;
self.push_buffer_if_full();
}
-
- self.update_registers(memory);
}
else{
- for _ in 0..t_cycles{
- self.audio_buffer[self.current_t_cycle as usize] = Sample{right_sample:0.0, left_sample:0.0};
- self.current_t_cycle += 1;
+ for _ in 0..m_cycles_passed{
+ self.audio_buffer[self.current_m_cycle as usize] = StereoSample::const_defualt();
+ self.current_m_cycle += 1;
self.push_buffer_if_full();
}
-
- //Reseting the apu state
- for i in NR10_REGISTER_ADDRESS..NR52_REGISTER_ADDRESS{
- memory.write_unprotected(i, 0);
- }
-
- self.tone_channel.reset();
- self.sweep_tone_channel.reset();
- self.wave_channel.reset();
- self.noise_channel.reset();
- self.frame_sequencer.reset();
}
self.last_enabled_state = self.enabled;
}
+ pub fn reset(&mut self){
+ self.tone_channel.reset();
+ self.sweep_tone_channel.reset();
+ self.wave_channel.reset();
+ self.noise_channel.reset();
+ self.frame_sequencer.reset();
+ }
+
fn push_buffer_if_full(&mut self){
- if self.current_t_cycle as usize >= AUDIO_BUFFER_SIZE{
- self.current_t_cycle = 0;
+ if self.current_m_cycle as usize >= BUFFER_SIZE{
+ self.current_m_cycle = 0;
self.device.push_buffer(&self.audio_buffer);
}
}
@@ -142,16 +128,6 @@ impl GbApu{
}
}
- fn update_registers(&mut self, memory:&mut impl UnprotectedMemory){
-
- let mut control_register = memory.read_unprotected(0xFF26);
- set_bit_u8(&mut control_register, 3, self.noise_channel.enabled && self.noise_channel.length_enable && self.noise_channel.sound_length != 0);
- set_bit_u8(&mut control_register, 2, self.wave_channel.enabled && self.wave_channel.length_enable && self.wave_channel.sound_length != 0);
- set_bit_u8(&mut control_register, 1, self.tone_channel.enabled && self.tone_channel.length_enable && self.tone_channel.sound_length != 0);
- set_bit_u8(&mut control_register, 0, self.sweep_tone_channel.enabled && self.sweep_tone_channel.length_enable && self.sweep_tone_channel.sound_length != 0);
-
- memory.write_unprotected(NR52_REGISTER_ADDRESS, control_register);
- }
pub fn update_sweep_frequency(channel:&mut Channel){
let sweep:&mut FreqSweep = &mut channel.sample_producer.sweep.as_mut().unwrap();
diff --git a/lib_gb/src/apu/mod.rs b/lib_gb/src/apu/mod.rs
index 8c418a4a..cf32280d 100644
--- a/lib_gb/src/apu/mod.rs
+++ b/lib_gb/src/apu/mod.rs
@@ -14,5 +14,5 @@ pub mod noise_sample_producer;
mod sound_utils;
mod apu_registers_updater;
-pub use apu_registers_updater::update_apu_registers;
+pub use apu_registers_updater::*;
diff --git a/lib_gb/src/apu/sound_terminal.rs b/lib_gb/src/apu/sound_terminal.rs
index 68b909b5..1da007bf 100644
--- a/lib_gb/src/apu/sound_terminal.rs
+++ b/lib_gb/src/apu/sound_terminal.rs
@@ -1,32 +1,43 @@
-use super::sound_utils::NUMBER_OF_CHANNELS;
+use super::{audio_device::{DEFAULT_SAPMPLE, Sample}, sound_utils::NUMBER_OF_CHANNELS};
+
+type ChannelMask = u16;
+
+const ENABLE_MASK:ChannelMask = 0xFFFF;
+const DISABLE_MASK:ChannelMask = 0x0;
pub struct SoundTerminal{
- pub enabled:bool,
pub volume:u8,
- pub channels:[bool;NUMBER_OF_CHANNELS]
+ channel_masks:[ChannelMask;NUMBER_OF_CHANNELS]
}
impl Default for SoundTerminal{
fn default() -> Self {
SoundTerminal{
- enabled:false,
- channels:[false;NUMBER_OF_CHANNELS],
+ channel_masks:[DISABLE_MASK;NUMBER_OF_CHANNELS],
volume:0
}
}
}
impl SoundTerminal{
- pub fn mix_terminal_samples(&self, samples:&[f32;NUMBER_OF_CHANNELS])->f32{
- let mut mixed_sample:f32 = 0.0;
+ pub fn set_channel_state(&mut self, channel:usize, state:bool){
+ self.channel_masks[channel] = state as u16 * ENABLE_MASK;
+ }
+
+ // For some reason this function is not inlined on release mode
+ #[inline]
+ pub fn mix_terminal_samples(&self, samples:&[Sample;NUMBER_OF_CHANNELS])->Sample{
+ let mut mixed_sample:Sample = DEFAULT_SAPMPLE;
for i in 0..NUMBER_OF_CHANNELS{
- if self.channels[i]{
- mixed_sample += samples[i];
- }
+ // This code should add the samples[i] only if channels[i] it true.
+ // After profiling this code is faster than if and since this is a hot spot in the code
+ // Im writing it like this.
+ mixed_sample += samples[i] & self.channel_masks[i] as Sample;
}
- mixed_sample /= NUMBER_OF_CHANNELS as f32;
+ mixed_sample >>= 2; // Divide by 4 in order to normal the sample
- return mixed_sample * (self.volume as f32 + 1.0);
+ // Adding +1 cause thats how to GB calculates the sound (0 still has volume)
+ return mixed_sample * ((self.volume + 1) as Sample);
}
}
\ No newline at end of file
diff --git a/lib_gb/src/apu/timer.rs b/lib_gb/src/apu/timer.rs
index 7604b2e2..ba05996e 100644
--- a/lib_gb/src/apu/timer.rs
+++ b/lib_gb/src/apu/timer.rs
@@ -3,29 +3,32 @@ pub struct Timer{
cycle_counter:u16
}
+// By deviding by 4 (shifting right 2) Im losing precison in favor of performance
impl Timer{
pub fn new(cycles_to_tick:u16)->Self{
Timer{
cycle_counter:0,
- cycles_to_tick:cycles_to_tick
+ cycles_to_tick:cycles_to_tick >> 2
}
}
+ // This function is a hot spot for the APU, almost every component uses the timer
+ #[inline]
pub fn cycle(&mut self)->bool{
if self.cycles_to_tick != 0{
+ // The calculation used to be this:
+ // self.cycle_counter = (self.cycle_counter + 1) % self.cycles_to_tick;
+ // After benching with a profiler I found that those 2 lines are much faster, probably cause there is no division here
self.cycle_counter += 1;
- if self.cycle_counter >= self.cycles_to_tick{
- self.cycle_counter = 0;
-
- return true;
- }
+ self.cycle_counter = (self.cycle_counter != self.cycles_to_tick) as u16 * self.cycle_counter;
+ return self.cycle_counter == 0;
}
return false;
}
pub fn update_cycles_to_tick(&mut self, cycles_to_tick:u16){
- self.cycles_to_tick = cycles_to_tick;
+ self.cycles_to_tick = cycles_to_tick >> 2;
self.cycle_counter = 0;
}
}
\ No newline at end of file
diff --git a/lib_gb/src/cpu/mod.rs b/lib_gb/src/cpu/mod.rs
index 3f39be16..296b0e63 100644
--- a/lib_gb/src/cpu/mod.rs
+++ b/lib_gb/src/cpu/mod.rs
@@ -1,4 +1,5 @@
pub mod gb_cpu;
pub mod register;
pub mod opcodes;
-pub mod flag;
\ No newline at end of file
+pub mod flag;
+pub mod opcode_runner;
\ No newline at end of file
diff --git a/lib_gb/src/cpu/opcode_runner.rs b/lib_gb/src/cpu/opcode_runner.rs
new file mode 100644
index 00000000..5ff6fede
--- /dev/null
+++ b/lib_gb/src/cpu/opcode_runner.rs
@@ -0,0 +1,211 @@
+use crate::mmu::memory::Memory;
+use super::{
+ gb_cpu::GbCpu,
+ opcodes::{
+ arithmetic_8bit_instructions::*,
+ rotate_shift_instructions::*,
+ cpu_control_instructions::*,
+ jump_instructions::*,
+ load_16bit_instructions::*,
+ arithmetic_16bit_instructions::*,
+ load_8bit_instructions::*,
+ single_bit_sintructions::*,
+ }
+};
+
+
+type U16MemoryOpcodeFunc = fn(&mut GbCpu,&mut T,u16)->u8;
+type U32MemoryOpcodeFunc = fn(&mut GbCpu,&mut T,u32)->u8;
+
+impl GbCpu{
+ pub fn run_opcode(&mut self, memory:&mut impl Memory)->u8{
+ let opcode = self.fetch_next_byte(memory);
+
+ match opcode{
+ //Stop
+ 0x10=>{
+ let next_byte = self.fetch_next_byte(memory);
+ if next_byte == 0{
+ stop(self, memory)
+ }
+ else{
+ std::panic!("Invalid stop opcode, second byte: {:#X}", next_byte);
+ }
+ }
+
+ //just cpu
+ 0x00=>1,
+ 0x07=>rlca(self),
+ 0x0F=>rrca(self),
+ 0x17=>rla(self),
+ 0x1F=>rra(self),
+ 0x2F=>cpl(self),
+ 0x27=>daa(self),
+ 0x37=>scf(self),
+ 0x3F=>ccf(self),
+ 0x76=>halt(self),
+ 0xE9=>jump_hl(self),
+ 0xF3=>di(self),
+ 0xF9=>load_sp_hl(self),
+ 0xFB=>ei(self),
+
+ //cpu and opcode
+ 0x03|0x13|0x23|0x33=>inc_rr(self, opcode),
+ 0x04|0x14|0x24|0x0C|0x1C|0x2C|0x3C=>inc_r(self, opcode),
+ 0x05|0x15|0x25|0x0D|0x1D|0x2D|0x3D=>dec_r(self, opcode),
+ 0x09|0x19|0x29|0x39=>add_hl_rr(self, opcode),
+ 0x0B|0x1B|0x2B|0x3B=>dec_rr(self, opcode),
+ 0x40..=0x45 | 0x47..=0x4D | 0x4F..=0x55 | 0x57..=0x5D |
+ 0x5F..=0x65 | 0x67..=0x6D | 0x6F | 0x78..=0x7D | 0x7F=>ld_r_r(self, opcode),
+ 0x80..=0x85 | 0x87=>add_a_r(self, opcode),
+ 0x88..=0x8D | 0x8F=>adc_a_r(self, opcode),
+ 0x90..=0x95 | 0x97=>sub_a_r(self, opcode),
+ 0x98..=0x9D | 0x9F=>sbc_a_r(self, opcode),
+ 0xA0..=0xA5 | 0xA7=>and_a_r(self, opcode),
+ 0xA8..=0xAD | 0xAF=>xor_a_r(self, opcode),
+ 0xB0..=0xB5 | 0xB7=>or_a_r(self, opcode),
+ 0xB8..=0xBD | 0xBF=>cp_a_r(self, opcode),
+
+ //u16 opcode
+ 0x06|0x0E|0x16|0x1E|0x26|0x2E|0x3E=>run_u16_opcode(self, memory, opcode, ld_r_n),
+ 0x18=>run_u16_opcode(self, memory, opcode, jump_r),
+ 0x20|0x28|0x30|0x38=>run_u16_opcode(self, memory, opcode, jump_r_cc),
+ 0xC6=>run_u16_opcode(self, memory, opcode, add_a_nn),
+ 0xCE=>run_u16_opcode(self, memory, opcode, adc_a_nn),
+ 0xD6=>run_u16_opcode(self, memory, opcode, sub_a_nn),
+ 0xDE=>run_u16_opcode(self, memory, opcode, sbc_a_nn),
+ 0xE6=>run_u16_opcode(self, memory, opcode, and_a_nn),
+ 0xE8=>run_u16_opcode(self, memory, opcode, add_sp_dd),
+ 0xEE=>run_u16_opcode(self, memory, opcode, xor_a_nn),
+ 0xF6=>run_u16_opcode(self, memory, opcode, or_a_nn),
+ 0xF8=>run_u16_opcode(self, memory, opcode, ld_hl_spdd),
+ 0xFE=>run_u16_opcode(self, memory, opcode, cp_a_nn),
+
+ //u32 opcodes
+ 0x01 | 0x11 | 0x21 | 0x31=>run_u32_opcode(self, memory, opcode, load_rr_nn),
+ 0xC2 | 0xD2 | 0xCA | 0xDA=>run_u32_opcode(self, memory, opcode,jump_cc),
+ 0xC3=>run_u32_opcode(self, memory, opcode,jump),
+
+ //Memory opcodes
+ 0x02=>ld_bc_a(self, memory),
+ 0x0A=>ld_a_bc(self, memory),
+ 0x12=>ld_de_a(self, memory),
+ 0x1A=>ld_a_de(self, memory),
+ 0x22=>ldi_hl_a(self, memory),
+ 0x2A=>ldi_a_hl(self, memory),
+ 0x32=>ldd_hl_a(self, memory),
+ 0x34=>inc_hl(self, memory),
+ 0x35=>dec_hl(self, memory),
+ 0x3A=>ldd_a_hl(self, memory),
+ 0x86=>add_a_hl(self, memory),
+ 0x8E=>adc_a_hl(self, memory),
+ 0x96=>sub_a_hl(self, memory),
+ 0x9E=>sbc_a_hl(self, memory),
+ 0xA6=>and_a_hl(self, memory),
+ 0xAE=>xor_a_hl(self, memory),
+ 0xB6=>or_a_hl(self, memory),
+ 0xBE=>cp_a_hl(self, memory),
+ 0xC9=>ret(self, memory),
+ 0xD9=>reti(self, memory),
+ 0xE2=>ld_ioport_c_a(self, memory),
+ 0xF2=>ld_a_ioport_c(self, memory),
+
+ //Memory u8 opcodes
+ 0x46|0x4E|0x56|0x5E|0x66|0x6E|0x7E=>ld_r_hl(self, memory, opcode),
+ 0x70..=0x75 | 0x77=>ld_hl_r(self, memory, opcode),
+ 0xC0|0xC8|0xD0|0xD8=>ret_cc(self, memory, opcode),
+ 0xC1|0xD1|0xE1|0xF1=>pop(self, memory, opcode),
+ 0xC5|0xD5|0xE5|0xF5=>push(self, memory, opcode),
+ 0xC7|0xCF|0xD7|0xDF|0xE7|0xEF|0xF7|0xFF=>rst(self, memory, opcode),
+
+ //Memory u16 opcodes
+ 0x36=>run_u16_memory_opcode(self, memory, opcode, ld_hl_n),
+ 0xE0=>run_u16_memory_opcode(self, memory, opcode, ld_ioport_n_a),
+ 0xF0=>run_u16_memory_opcode(self, memory, opcode, ld_a_ioport_n),
+
+ //Memory u32 opcodes
+
+ 0x08=>run_u32_memory_opcode(self, memory, opcode, ld_nn_sp),
+ 0xC4|0xCC|0xD4|0xDC=>run_u32_memory_opcode(self, memory, opcode, call_cc),
+ 0xCD=>run_u32_memory_opcode(self, memory, opcode, call),
+ 0xEA=>run_u32_memory_opcode(self, memory, opcode, ld_nn_a),
+ 0xFA=>run_u32_memory_opcode(self, memory, opcode, ld_a_nn),
+
+ //0xCB opcodes
+ 0xCB=>{
+ let next = self.fetch_next_byte(memory);
+ let u16_opcode = (opcode as u16) << 8 | next as u16;
+ match next{
+ 0x00..=0x05 | 0x07=>rlc_r(self, u16_opcode),
+ 0x08..=0x0D | 0x0F=>rrc_r(self, u16_opcode),
+ 0x10..=0x15 | 0x17=>rl_r(self, u16_opcode),
+ 0x18..=0x1D | 0x1F=>rr_r(self, u16_opcode),
+ 0x20..=0x25 | 0x27=>sla_r(self, u16_opcode),
+ 0x28..=0x2D | 0x2F=>sra_r(self, u16_opcode),
+ 0x30..=0x35 | 0x37=>swap_r(self, u16_opcode),
+ 0x38..=0x3D | 0x3F=>srl_r(self, u16_opcode),
+ 0x40..=0x45 | 0x47..=0x4D | 0x4F..=0x55 | 0x57..=0x5D |
+ 0x5F..=0x65 | 0x67..=0x6D | 0x6F..=0x75 | 0x77..=0x7D | 0x7F =>bit_r(self, u16_opcode),
+ 0x80..=0x85 | 0x87..=0x8D | 0x8F..=0x95 | 0x97..=0x9D |
+ 0x9F..=0xA5 | 0xA7..=0xAD | 0xAF..=0xB5 | 0xB7..=0xBD | 0xBF =>res_r(self, u16_opcode),
+ 0xC0..=0xC5 | 0xC7..=0xCD | 0xCF..=0xD5 | 0xD7..=0xDD |
+ 0xDF..=0xE5 | 0xE7..=0xED | 0xEF..=0xF5 | 0xF7..=0xFD | 0xFF =>set_r(self, u16_opcode),
+
+ 0x06=>rlc_hl(self, memory),
+ 0x0E=>rrc_hl(self, memory),
+ 0x16=>rl_hl(self, memory),
+ 0x1E=>rr_hl(self, memory),
+ 0x26=>sla_hl(self, memory),
+ 0x2E=>sra_hl(self, memory),
+ 0x36=>swap_hl(self, memory),
+ 0x3E=>srl_hl(self, memory),
+
+ 0x46|0x4E|0x56|0x5E|0x66|0x6E|0x76|0x7E=>bit_hl(self, memory, u16_opcode),
+ 0x86|0x8E|0x96|0x9E|0xA6|0xAE|0xB6|0xBE=>res_hl(self, memory, u16_opcode),
+ 0xC6|0xCE|0xD6|0xDE|0xE6|0xEE|0xF6|0xFE=>set_hl(self, memory, u16_opcode),
+ }
+ },
+
+ _=>std::panic!("Unsupported opcode:{:#X}", opcode)
+ }
+ }
+
+
+ fn fetch_next_byte(&mut self, memory: &impl Memory)->u8{
+ let byte:u8 = memory.read(self.program_counter);
+ self.program_counter+=1;
+ return byte;
+ }
+}
+
+
+
+fn run_u16_opcode(cpu: &mut GbCpu, memory: &impl Memory, opcode:u8, opcode_func:fn(&mut GbCpu, u16)->u8)->u8{
+ let u16_opcode = get_u16_opcode(cpu, memory, opcode);
+ opcode_func(cpu, u16_opcode)
+}
+
+fn run_u16_memory_opcode(cpu: &mut GbCpu, memory: &mut T, opcode:u8, opcode_func:U16MemoryOpcodeFunc)->u8{
+ let u16_opcode = get_u16_opcode(cpu, memory, opcode);
+ opcode_func(cpu, memory, u16_opcode)
+}
+
+fn run_u32_opcode(cpu: &mut GbCpu, memory: &impl Memory, opcode:u8, opcode_func:fn(&mut GbCpu, u32)->u8)->u8{
+ let mut u32_opcode:u32 = ((opcode as u32)<<8) | (cpu.fetch_next_byte(memory) as u32);
+ u32_opcode <<= 8;
+ u32_opcode |= cpu.fetch_next_byte(memory) as u32;
+
+ opcode_func(cpu, u32_opcode)
+}
+
+fn run_u32_memory_opcode(cpu: &mut GbCpu, memory: &mut T, opcode:u8, opcode_func:U32MemoryOpcodeFunc)->u8{
+ let mut u32_opcode:u32 = ((opcode as u32)<<8) | (cpu.fetch_next_byte(memory) as u32);
+ u32_opcode <<= 8;
+ u32_opcode |= cpu.fetch_next_byte(memory) as u32;
+
+ opcode_func(cpu, memory, u32_opcode)
+}
+
+fn get_u16_opcode(cpu:&mut GbCpu, memory:&impl Memory, opcode:u8)->u16{
+ (opcode as u16) << 8 | cpu.fetch_next_byte(memory) as u16
+}
\ No newline at end of file
diff --git a/lib_gb/src/cpu/opcodes/mod.rs b/lib_gb/src/cpu/opcodes/mod.rs
index 5fafc025..1a879b56 100644
--- a/lib_gb/src/cpu/opcodes/mod.rs
+++ b/lib_gb/src/cpu/opcodes/mod.rs
@@ -6,6 +6,4 @@ pub mod arithmetic_16bit_instructions;
pub mod jump_instructions;
pub mod cpu_control_instructions;
pub mod rotate_shift_instructions;
-pub mod single_bit_sintructions;
-pub mod opcodes_resolvers;
-pub mod opcode_resolver;
\ No newline at end of file
+pub mod single_bit_sintructions;
\ No newline at end of file
diff --git a/lib_gb/src/cpu/opcodes/opcode_resolver.rs b/lib_gb/src/cpu/opcodes/opcode_resolver.rs
deleted file mode 100644
index 1cd23b56..00000000
--- a/lib_gb/src/cpu/opcodes/opcode_resolver.rs
+++ /dev/null
@@ -1,103 +0,0 @@
-use super::opcodes_resolvers::*;
-use crate::mmu::memory::Memory;
-
-
-pub enum OpcodeFuncType{
- OpcodeFunc(OpcodeFunc),
- U8OpcodeFunc(U8OpcodeFunc),
- U16OpcodeFunc(U16OpcodeFunc),
- U32OpcodeFunc(U32OpcodeFunc),
- MemoryOpcodeFunc(MemoryOpcodeFunc),
- U8MemoryOpcodeFunc(U8MemoryOpcodeFunc),
- U16MemoryOpcodeFunc(U16MemoryOpcodeFunc),
- U32MemoryOpcodeFunc(U32MemoryOpcodeFunc)
-}
-
-pub struct OpcodeResolver{
- opcode_func_resolver:fn(u8)->Option,
- u8_opcode_func_resolver:fn(u8)->Option,
- u16_opcode_func_resolver:fn(u8,u8)->Option,
- u32_opcode_func_resolver:fn(u8)->Option,
- memory_opcode_func_resolver:fn(u8)->Option>,
- memory_opcode_func_2bytes_resolver:fn(u8,u8)->Option>,
- u8_memory_opcode_func_resolver:fn(u8)->Option>,
- u16_memory_opcode_func_resolver:fn(u8,u8)->Option>,
- u32_memory_opcode_func_resolver:fn(u8)->Option>
-}
-
-
-impl OpcodeResolver{
- pub fn get_opcode(&mut self, opcode:u8, memory:&impl Memory, program_counter:&mut u16)->OpcodeFuncType{
- let opcode_func = (self.opcode_func_resolver)(opcode);
- match opcode_func{
- Some(func)=> return OpcodeFuncType::OpcodeFunc(func),
- None=>{}
- }
- let memory_opcode_func = (self.memory_opcode_func_resolver)(opcode);
- match memory_opcode_func{
- Some(func)=> return OpcodeFuncType::MemoryOpcodeFunc(func),
- None=>{}
- }
- let u8_opcode_func=(self.u8_opcode_func_resolver)(opcode);
- match u8_opcode_func{
- Some(func)=> return OpcodeFuncType::U8OpcodeFunc(func),
- None=>{}
- }
- let u8_memory_func=(self.u8_memory_opcode_func_resolver)(opcode);
- match u8_memory_func{
- Some(func)=> return OpcodeFuncType::U8MemoryOpcodeFunc(func),
- None=>{}
- }
- let postfix:u8 = memory.read(*program_counter);
- let u16_opcode_func=(self.u16_opcode_func_resolver)(opcode, postfix);
- match u16_opcode_func{
- Some(func)=>return OpcodeFuncType::U16OpcodeFunc(func),
- None=>{}
- }
- let u32_opcode_func = (self.u32_opcode_func_resolver)(opcode);
- match u32_opcode_func{
- Some(func)=> return OpcodeFuncType::U32OpcodeFunc(func),
- None=>{}
- }
- let u32_memory_opcode_func=(self.u32_memory_opcode_func_resolver)(opcode);
- match u32_memory_opcode_func{
- Some(func)=> return OpcodeFuncType::U32MemoryOpcodeFunc(func),
- None=>{}
- }
- let u16_memory_opcode_func = (self.u16_memory_opcode_func_resolver)(opcode, postfix);
- match u16_memory_opcode_func{
- Some(func)=>return OpcodeFuncType::U16MemoryOpcodeFunc(func),
- None=>{}
- }
- let memory_opcode_func = (self.memory_opcode_func_2bytes_resolver)(opcode, postfix);
- match memory_opcode_func{
- Some(func)=>{
- //this is the only opcodes type that does not uses the postfix byte and therfore does not increment the program counter
- //so im incrementing is manually
- *program_counter+=1;
- return OpcodeFuncType::MemoryOpcodeFunc(func);
- },
- None=>{}
- }
-
- std::panic!("no opcode matching: {:#X?}, nextb{:#X?}, c_pc{:#X?}",opcode, postfix, program_counter);
- }
-}
-
-impl Default for OpcodeResolver{
- fn default()->OpcodeResolver{
- OpcodeResolver{
- opcode_func_resolver:get_opcode_func_resolver(),
- memory_opcode_func_resolver:get_memory_opcode_func_resolver(),
- memory_opcode_func_2bytes_resolver:get_memory_opcode_func_2bytes_resolver(),
- u8_opcode_func_resolver:get_u8_opcode_func_resolver(),
- u8_memory_opcode_func_resolver:get_u8_memory_opcode_func_resolver(),
- u16_memory_opcode_func_resolver:get_u16_memory_opcode_func_resolver(),
- u16_opcode_func_resolver:get_u16_opcode_func_resolver(),
- u32_opcode_func_resolver:get_u32_opcode_func_resolver(),
- u32_memory_opcode_func_resolver:get_u32_memory_opcode_func_resolver()
- }
- }
-}
-
-
diff --git a/lib_gb/src/cpu/opcodes/opcodes_resolvers.rs b/lib_gb/src/cpu/opcodes/opcodes_resolvers.rs
deleted file mode 100644
index 55428582..00000000
--- a/lib_gb/src/cpu/opcodes/opcodes_resolvers.rs
+++ /dev/null
@@ -1,216 +0,0 @@
-use crate::cpu::gb_cpu::GbCpu;
-use crate::mmu::memory::Memory;
-use std::option::Option;
-use super::{
- arithmetic_16bit_instructions::*,
- arithmetic_8bit_instructions::*,
- cpu_control_instructions::*,
- jump_instructions::*,
- load_16bit_instructions::*,
- load_8bit_instructions::*,
- rotate_shift_instructions::*,
- single_bit_sintructions::*
-};
-
-
-pub type OpcodeFunc = fn(&mut GbCpu)->u8;
-pub type U8OpcodeFunc = fn(&mut GbCpu,u8)->u8;
-pub type U16OpcodeFunc = fn(&mut GbCpu,u16)->u8;
-pub type U32OpcodeFunc = fn(&mut GbCpu,u32)->u8;
-pub type MemoryOpcodeFunc = fn(&mut GbCpu,&mut T)->u8;
-pub type U8MemoryOpcodeFunc = fn(&mut GbCpu,&mut T,u8)->u8;
-pub type U16MemoryOpcodeFunc = fn(&mut GbCpu,&mut T,u16)->u8;
-pub type U32MemoryOpcodeFunc = fn(&mut GbCpu,&mut T,u32)->u8;
-
-
-pub fn get_opcode_func_resolver()->fn(u8)->Option{
- |opcode:u8|->Option{
- match opcode{
- 0x00=>Some(|_|1),
- 0x07=>Some(rlca),
- 0x0F=>Some(rrca),
- 0x17=>Some(rla),
- 0x1F=>Some(rra),
- 0x2F=>Some(cpl),
- 0x27=>Some(daa),
- 0x37=>Some(scf),
- 0x3F=>Some(ccf),
- 0x76=>Some(halt),
- 0xE9=>Some(jump_hl),
- 0xF3=>Some(di),
- 0xF9=>Some(load_sp_hl),
- 0xFB=>Some(ei),
- _=>None
- }
- }
-}
-
-pub fn get_u8_opcode_func_resolver()->fn(u8)->Option{
- |opcode:u8|->Option{
- match opcode{
- 0x03|0x13|0x23|0x33=>Some(inc_rr),
- 0x04|0x14|0x24|0x0C|0x1C|0x2C|0x3C=>Some(inc_r),
- 0x05|0x15|0x25|0x0D|0x1D|0x2D|0x3D=>Some(dec_r),
- 0x09|0x19|0x29|0x39=>Some(add_hl_rr),
- 0x0B|0x1B|0x2B|0x3B=>Some(dec_rr),
- 0x40..=0x45|0x47..=0x4D|0x4F..=0x55|0x57..=0x5D|
- 0x5F..=0x65|0x67..=0x6D|0x6F|0x78..=0x7D|0x7F=>Some(ld_r_r),
- 0x80..=0x85 | 0x87=>Some(add_a_r),
- 0x88..=0x8D | 0x8F=>Some(adc_a_r),
- 0x90..=0x95 | 0x97=>Some(sub_a_r),
- 0x98..=0x9D | 0x9F=>Some(sbc_a_r),
- 0xA0..=0xA5 | 0xA7=>Some(and_a_r),
- 0xA8..=0xAD | 0xAF=>Some(xor_a_r),
- 0xB0..=0xB5 | 0xB7=>Some(or_a_r),
- 0xB8..=0xBD | 0xBF=>Some(cp_a_r),
- _=>None
- }
- }
-}
-
-pub fn get_u16_opcode_func_resolver()->fn(u8,u8)->Option{
- |opcode:u8, next:u8|->Option{
- match opcode{
- 0x06|0x0E|0x16|0x1E|0x26|0x2E|0x3E=>Some(ld_r_n),
- 0x18=>Some(jump_r),
- 0x20|0x28|0x30|0x38=>Some(jump_r_cc),
- 0xC6=>Some(add_a_nn),
- 0xCB=>match next{
- 0x00..=0x05 | 0x07=> Some(rlc_r),
- 0x08..=0x0D | 0x0F=>Some(rrc_r),
- 0x10..=0x15 | 0x17=>Some(rl_r),
- 0x18..=0x1D | 0x1F=>Some(rr_r),
- 0x20..=0x25 | 0x27=>Some(sla_r),
- 0x28..=0x2D | 0x2F=>Some(sra_r),
- 0x30..=0x35 | 0x37=>Some(swap_r),
- 0x38..=0x3D | 0x3F=>Some(srl_r),
- 0x40..=0x45 | 0x47..=0x4D | 0x4F..=0x55 | 0x57..=0x5D |
- 0x5F..=0x65 | 0x67..=0x6D | 0x6F..=0x75 | 0x77..=0x7D | 0x7F =>Some(bit_r),
- 0x80..=0x85 | 0x87..=0x8D | 0x8F..=0x95 | 0x97..=0x9D |
- 0x9F..=0xA5 | 0xA7..=0xAD | 0xAF..=0xB5 | 0xB7..=0xBD | 0xBF =>Some(res_r),
- 0xC0..=0xC5 | 0xC7..=0xCD | 0xCF..=0xD5 | 0xD7..=0xDD |
- 0xDF..=0xE5 | 0xE7..=0xED | 0xEF..=0xF5 | 0xF7..=0xFD | 0xFF =>Some(set_r),
- _=>None
- },
- 0xCE=>Some(adc_a_nn),
- 0xD6=>Some(sub_a_nn),
- 0xDE=>Some(sbc_a_nn),
- 0xE6=>Some(and_a_nn),
- 0xE8=>Some(add_sp_dd),
- 0xEE=>Some(xor_a_nn),
- 0xF6=>Some(or_a_nn),
- 0xF8=>Some(ld_hl_spdd),
- 0xFE=>Some(cp_a_nn),
- _=>None
- }
- }
-}
-
-pub fn get_u32_opcode_func_resolver()->fn(u8)->Option{
- |opcode:u8|->Option{
- match opcode{
- 0x01 | 0x11 | 0x21 | 0x31=>Some(load_rr_nn),
- 0xC2 | 0xD2 | 0xCA | 0xDA=>Some(jump_cc),
- 0xC3=>Some(jump),
- _=>None
- }
- }
-}
-
-pub fn get_memory_opcode_func_resolver()->fn(u8)->Option>{
- |opcode:u8|->Option>{
- match opcode{
- 0x02=>Some(ld_bc_a),
- 0x0A=>Some(ld_a_bc),
- 0x12=>Some(ld_de_a),
- 0x1A=>Some(ld_a_de),
- 0x22=>Some(ldi_hl_a),
- 0x2A=>Some(ldi_a_hl),
- 0x32=>Some(ldd_hl_a),
- 0x34=>Some(inc_hl),
- 0x35=>Some(dec_hl),
- 0x3A=>Some(ldd_a_hl),
- 0x86=>Some(add_a_hl),
- 0x8E=>Some(adc_a_hl),
- 0x96=>Some(sub_a_hl),
- 0x9E=>Some(sbc_a_hl),
- 0xA6=>Some(and_a_hl),
- 0xAE=>Some(xor_a_hl),
- 0xB6=>Some(or_a_hl),
- 0xBE=>Some(cp_a_hl),
- 0xC9=>Some(ret),
- 0xD9=>Some(reti),
- 0xE2=>Some(ld_ioport_c_a),
- 0xF2=>Some(ld_a_ioport_c),
- _=>None
- }
- }
-}
-
-pub fn get_memory_opcode_func_2bytes_resolver()->fn(u8,u8)->Option>{
- |opcode:u8,next_byte:u8|->Option>{
- if opcode == 0x10 && next_byte == 0{
- return Some(stop);
- }
-
- if opcode == 0xCB{
- return match next_byte{
- 0x06=>Some(rlc_hl),
- 0x0E=>Some(rrc_hl),
- 0x16=>Some(rl_hl),
- 0x1E=>Some(rr_hl),
- 0x26=>Some(sla_hl),
- 0x2E=>Some(sra_hl),
- 0x36=>Some(swap_hl),
- 0x3E=>Some(srl_hl),
- _=>None
- };
- }
-
- return None;
- }
-}
-
-pub fn get_u8_memory_opcode_func_resolver()->fn(u8)->Option>{
- |opcode:u8|->Option>{
- match opcode{
- 0x46|0x4E|0x56|0x5E|0x66|0x6E|0x7E=>Some(ld_r_hl),
- 0x70..=0x75 | 0x77=>Some(ld_hl_r),
- 0xC0|0xC8|0xD0|0xD8=>Some(ret_cc),
- 0xC1|0xD1|0xE1|0xF1=>Some(pop),
- 0xC5|0xD5|0xE5|0xF5=>Some(push),
- 0xC7|0xCF|0xD7|0xDF|0xE7|0xEF|0xF7|0xFF=>Some(rst),
- _=>None
- }
- }
-}
-
-pub fn get_u16_memory_opcode_func_resolver()->fn(u8,u8)->Option>{
- |opcode:u8, next_byte:u8|->Option>{
- match opcode{
- 0x36=>Some(ld_hl_n),
- 0xE0=>Some(ld_ioport_n_a),
- 0xF0=>Some(ld_a_ioport_n),
- 0xCB=>match next_byte{
- 0x46|0x4E|0x56|0x5E|0x66|0x6E|0x76|0x7E=>Some(bit_hl),
- 0x86|0x8E|0x96|0x9E|0xA6|0xAE|0xB6|0xBE=>Some(res_hl),
- 0xC6|0xCE|0xD6|0xDE|0xE6|0xEE|0xF6|0xFE=>Some(set_hl),
- _=>None
- },
- _=>None
- }
- }
-}
-
-pub fn get_u32_memory_opcode_func_resolver()->fn(u8)->Option>{
- |opcode:u8|->Option>{
- match opcode{
- 0x08=>Some(ld_nn_sp),
- 0xC4|0xCC|0xD4|0xDC=>Some(call_cc),
- 0xCD=>Some(call),
- 0xEA=>Some(ld_nn_a),
- 0xFA=>Some(ld_a_nn),
- _=>None
- }
- }
-}
\ No newline at end of file
diff --git a/lib_gb/src/keypad/joypad_provider.rs b/lib_gb/src/keypad/joypad_provider.rs
index 276411bc..1fe9f2e6 100644
--- a/lib_gb/src/keypad/joypad_provider.rs
+++ b/lib_gb/src/keypad/joypad_provider.rs
@@ -1,5 +1,5 @@
use super::joypad::Joypad;
pub trait JoypadProvider{
- fn provide(&self, joypad:&mut Joypad);
+ fn provide(&mut self, joypad:&mut Joypad);
}
\ No newline at end of file
diff --git a/lib_gb/src/lib.rs b/lib_gb/src/lib.rs
index f32f9f39..40a90b86 100644
--- a/lib_gb/src/lib.rs
+++ b/lib_gb/src/lib.rs
@@ -5,6 +5,6 @@ pub mod mmu;
pub mod keypad;
pub mod apu;
pub mod timer;
+pub mod utils;
-mod utils;
pub use utils::GB_FREQUENCY;
\ No newline at end of file
diff --git a/lib_gb/src/machine/gameboy.rs b/lib_gb/src/machine/gameboy.rs
index eff555fb..5ed41351 100644
--- a/lib_gb/src/machine/gameboy.rs
+++ b/lib_gb/src/machine/gameboy.rs
@@ -1,46 +1,36 @@
use crate::{
- apu::{self, audio_device::AudioDevice, gb_apu::GbApu},
- cpu::{gb_cpu::GbCpu, opcodes::opcode_resolver::*},
+ apu::{audio_device::AudioDevice, gb_apu::GbApu},
+ cpu::gb_cpu::GbCpu,
keypad::{joypad::Joypad, joypad_provider::JoypadProvider, joypad_register_updater},
- mmu::{carts::mbc::Mbc, gb_mmu::{GbMmu, BOOT_ROM_SIZE}, memory::Memory, mmu_register_updater, oam_dma_transferer::OamDmaTransferer},
- ppu::{gb_ppu::{CYCLES_PER_FRAME, GbPpu, SCREEN_HEIGHT, SCREEN_WIDTH}, ppu_register_updater}, timer::{gb_timer::GbTimer, timer_register_updater}
+ mmu::{carts::mbc::Mbc, gb_mmu::{GbMmu, BOOT_ROM_SIZE}, memory::Memory},
+ ppu::gfx_device::GfxDevice
};
use super::interrupts_handler::InterruptsHandler;
use std::boxed::Box;
use log::debug;
+//CPU frequrncy: 4,194,304 / 59.727~ / 4 == 70224 / 4
+pub const CYCLES_PER_FRAME:u32 = 17556;
-pub struct GameBoy<'a, JP: JoypadProvider, AD:AudioDevice> {
+pub struct GameBoy<'a, JP: JoypadProvider, AD:AudioDevice, GFX:GfxDevice> {
cpu: GbCpu,
- mmu: GbMmu::<'a>,
- opcode_resolver:OpcodeResolver::>,
- ppu:GbPpu,
- apu:GbApu,
+ mmu: GbMmu::<'a, AD, GFX>,
interrupts_handler:InterruptsHandler,
- cycles_counter:u32,
- joypad_provider: JP,
- timer: GbTimer,
- dma:OamDmaTransferer
+ joypad_provider: JP
}
-impl<'a, JP:JoypadProvider, AD:AudioDevice> GameBoy<'a, JP, AD>{
+impl<'a, JP:JoypadProvider, AD:AudioDevice, GFX:GfxDevice> GameBoy<'a, JP, AD, GFX>{
- pub fn new_with_bootrom(mbc:&'a mut Box,joypad_provider:JP, audio_device:AD, boot_rom:[u8;BOOT_ROM_SIZE])->GameBoy{
+ pub fn new_with_bootrom(mbc:&'a mut Box,joypad_provider:JP, audio_device:AD, gfx_device:GFX, boot_rom:[u8;BOOT_ROM_SIZE])->GameBoy{
GameBoy{
cpu:GbCpu::default(),
- mmu:GbMmu::new_with_bootrom(mbc, boot_rom),
- opcode_resolver:OpcodeResolver::default(),
- ppu:GbPpu::default(),
- apu:GbApu::new(audio_device),
+ mmu:GbMmu::new_with_bootrom(mbc, boot_rom, GbApu::new(audio_device), gfx_device),
interrupts_handler: InterruptsHandler::default(),
- cycles_counter:0,
- joypad_provider: joypad_provider,
- timer:GbTimer::default(),
- dma: OamDmaTransferer::default()
+ joypad_provider: joypad_provider
}
}
- pub fn new(mbc:&'a mut Box,joypad_provider:JP, audio_device:AD)->GameBoy{
+ pub fn new(mbc:&'a mut Box,joypad_provider:JP, audio_device:AD, gfx_device:GFX)->GameBoy{
let mut cpu = GbCpu::default();
//Values after the bootrom
*cpu.af.value() = 0x190;
@@ -52,24 +42,18 @@ impl<'a, JP:JoypadProvider, AD:AudioDevice> GameBoy<'a, JP, AD>{
GameBoy{
cpu:cpu,
- mmu:GbMmu::new(mbc),
- opcode_resolver:OpcodeResolver::default(),
- ppu:GbPpu::default(),
- apu:GbApu::new(audio_device),
+ mmu:GbMmu::new(mbc, GbApu::new(audio_device), gfx_device),
interrupts_handler: InterruptsHandler::default(),
- cycles_counter:0,
joypad_provider: joypad_provider,
- timer: GbTimer::default(),
- dma: OamDmaTransferer::default()
}
}
- pub fn cycle_frame(&mut self)->&[u32;SCREEN_HEIGHT*SCREEN_WIDTH]{
+ pub fn cycle_frame(&mut self){
let mut joypad = Joypad::default();
- let mut last_ppu_power_state:bool = self.ppu.screen_enable;
+ let mut cycles_counter = 0;
- while self.cycles_counter < CYCLES_PER_FRAME{
+ while cycles_counter < CYCLES_PER_FRAME{
self.joypad_provider.provide(&mut joypad);
joypad_register_updater::update_joypad_registers(&joypad, &mut self.mmu);
@@ -78,75 +62,24 @@ impl<'a, JP:JoypadProvider, AD:AudioDevice> GameBoy<'a, JP, AD>{
if !self.cpu.halt{
cpu_cycles_passed = self.execute_opcode();
}
-
- //For the DMA controller
- mmu_register_updater::update_mmu_registers(&mut self.mmu, &mut self.dma);
-
- timer_register_updater::update_timer_registers(&mut self.timer, &mut self.mmu.io_ports);
- self.timer.cycle(&mut self.mmu, cpu_cycles_passed);
- self.dma.cycle(&mut self.mmu, cpu_cycles_passed as u8);
- //For the PPU
- mmu_register_updater::update_mmu_registers(&mut self.mmu, &mut self.dma);
-
- ppu_register_updater::update_ppu_regsiters(&mut self.mmu, &mut self.ppu);
- self.ppu.update_gb_screen(&mut self.mmu, cpu_cycles_passed as u32);
- mmu_register_updater::update_mmu_registers(&mut self.mmu, &mut self.dma);
+ self.mmu.cycle(cpu_cycles_passed);
//interrupts
- let interrupt_cycles = self.interrupts_handler.handle_interrupts(&mut self.cpu, &mut self.ppu, &mut self.mmu);
- if interrupt_cycles != 0{
- self.dma.cycle(&mut self.mmu, interrupt_cycles as u8);
- timer_register_updater::update_timer_registers(&mut self.timer, &mut self.mmu.io_ports);
- self.timer.cycle(&mut self.mmu, interrupt_cycles as u8);
- mmu_register_updater::update_mmu_registers(&mut self.mmu, &mut self.dma);
-
- //PPU
- ppu_register_updater::update_ppu_regsiters(&mut self.mmu, &mut self.ppu);
- self.ppu.update_gb_screen(&mut self.mmu, interrupt_cycles as u32);
- mmu_register_updater::update_mmu_registers(&mut self.mmu, &mut self.dma);
- }
-
-
- let iter_total_cycles= cpu_cycles_passed as u32 + interrupt_cycles as u32;
-
-
- //APU
- apu::update_apu_registers(&mut self.mmu, &mut self.apu);
- self.apu.cycle(&mut self.mmu, iter_total_cycles as u8);
-
- //clears io ports
- self.mmu.io_ports.clear_io_ports_triggers();
-
- //In case the ppu just turned I want to keep it sync with the actual screen and thats why Im reseting the loop to finish
- //the frame when the ppu finishes the frame
- if !last_ppu_power_state && self.ppu.screen_enable{
- self.cycles_counter = 0;
+ let interrupt_cycles = self.interrupts_handler.handle_interrupts(&mut self.cpu, &mut self.mmu);
+ if interrupt_cycles != 0{
+ self.mmu.cycle(interrupt_cycles);
}
- self.cycles_counter += iter_total_cycles;
- last_ppu_power_state = self.ppu.screen_enable;
+ cycles_counter += cpu_cycles_passed as u32 + interrupt_cycles as u32;
}
-
- if self.cycles_counter >= CYCLES_PER_FRAME{
- self.cycles_counter -= CYCLES_PER_FRAME;
- }
-
- return self.ppu.get_frame_buffer();
- }
-
- fn fetch_next_byte(&mut self)->u8{
- let byte:u8 = self.mmu.read(self.cpu.program_counter);
- self.cpu.program_counter+=1;
- return byte;
}
fn execute_opcode(&mut self)->u8{
let pc = self.cpu.program_counter;
- let opcode:u8 = self.fetch_next_byte();
//debug
- if self.mmu.finished_boot{
+ if self.mmu.io_components.finished_boot{
let a = *self.cpu.af.high();
let b = *self.cpu.bc.high();
let c = *self.cpu.bc.low();
@@ -159,35 +92,7 @@ impl<'a, JP:JoypadProvider, AD:AudioDevice> GameBoy<'a, JP, AD>{
a,f,b,c,d,e,h,l, self.cpu.stack_pointer, pc, self.mmu.read(pc), self.mmu.read(pc+1), self.mmu.read(pc+2), self.mmu.read(pc+3));
}
-
-
- let opcode_func:OpcodeFuncType = self.opcode_resolver.get_opcode(opcode, &self.mmu, &mut self.cpu.program_counter);
- match opcode_func{
- OpcodeFuncType::OpcodeFunc(func)=>func(&mut self.cpu),
- OpcodeFuncType::MemoryOpcodeFunc(func)=>func(&mut self.cpu, &mut self.mmu),
- OpcodeFuncType::U8OpcodeFunc(func)=>func(&mut self.cpu, opcode),
- OpcodeFuncType::U8MemoryOpcodeFunc(func)=>func(&mut self.cpu, &mut self.mmu, opcode),
- OpcodeFuncType::U16OpcodeFunc(func)=>{
- let u16_opcode:u16 = ((opcode as u16)<<8) | (self.fetch_next_byte() as u16);
- func(&mut self.cpu, u16_opcode)
- },
- OpcodeFuncType::U16MemoryOpcodeFunc(func)=>{
- let u16_opcode:u16 = ((opcode as u16)<<8) | (self.fetch_next_byte() as u16);
- func(&mut self.cpu, &mut self.mmu, u16_opcode)
- },
- OpcodeFuncType::U32OpcodeFunc(func)=>{
- let mut u32_opcode:u32 = ((opcode as u32)<<8) | (self.fetch_next_byte() as u32);
- u32_opcode <<= 8;
- u32_opcode |= self.fetch_next_byte() as u32;
- func(&mut self.cpu, u32_opcode)
- },
- OpcodeFuncType::U32MemoryOpcodeFunc(func)=>{
- let mut u32_opcode:u32 = ((opcode as u32)<<8) | (self.fetch_next_byte() as u32);
- u32_opcode <<= 8;
- u32_opcode |= self.fetch_next_byte() as u32;
- func(&mut self.cpu, &mut self.mmu, u32_opcode)
- }
- }
+ self.cpu.run_opcode(&mut self.mmu)
}
}
diff --git a/lib_gb/src/machine/interrupts_handler.rs b/lib_gb/src/machine/interrupts_handler.rs
index 72c7db65..89a53174 100644
--- a/lib_gb/src/machine/interrupts_handler.rs
+++ b/lib_gb/src/machine/interrupts_handler.rs
@@ -1,8 +1,11 @@
-use crate::{cpu::gb_cpu::GbCpu, ppu::gb_ppu::GbPpu, utils::memory_registers::IE_REGISTER_ADDRESS};
-use crate::utils::{
+use crate::{cpu::gb_cpu::GbCpu, utils::{
bit_masks::*,
- memory_registers::IF_REGISTER_ADDRESS
-};
+ memory_registers::{
+ IE_REGISTER_ADDRESS,
+ IF_REGISTER_ADDRESS,
+ STAT_REGISTER_ADDRESS
+ }
+}};
use crate::cpu::opcodes::opcodes_utils::push;
use crate::mmu::memory::Memory;
@@ -26,18 +29,19 @@ impl Default for InterruptsHandler{
impl InterruptsHandler{
- pub fn handle_interrupts(&mut self, cpu:&mut GbCpu,ppu:&mut GbPpu, memory:&mut impl Memory)->u8{
+ pub fn handle_interrupts(&mut self, cpu:&mut GbCpu, memory:&mut impl Memory)->u8{
//this is delayed by one instruction cause there is this delay since EI opcode is called untill the interrupt could happen
let mut interupt_flag = memory.read(IF_REGISTER_ADDRESS);
let interupt_enable = memory.read(IE_REGISTER_ADDRESS);
+ let stat_register = memory.read(STAT_REGISTER_ADDRESS);
if cpu.mie && self.ei_triggered{
if interupt_flag & BIT_0_MASK != 0 && interupt_enable & BIT_0_MASK != 0{
return Self::prepare_for_interut(cpu, BIT_0_MASK, V_BLANK_INTERRUPT_ADDERESS, memory, &mut interupt_flag);
}
- if interupt_flag & BIT_1_MASK != 0 && interupt_enable & BIT_1_MASK != 0 &&
- (ppu.v_blank_interrupt_request || ppu.oam_search_interrupt_request || ppu.h_blank_interrupt_request || ppu.coincidence_interrupt_request){
+ // Checking those STAT register bits for the STAT interrupts requests
+ if interupt_flag & BIT_1_MASK != 0 && interupt_enable & BIT_1_MASK != 0 && (stat_register & 0b111_1000) != 0{
return Self::prepare_for_interut(cpu, BIT_1_MASK, LCD_STAT_INTERRUPT_ADDERESS, memory, &mut interupt_flag);
}
if interupt_flag & BIT_2_MASK != 0 && interupt_enable & BIT_2_MASK != 0{
diff --git a/lib_gb/src/machine/mbc_initializer.rs b/lib_gb/src/machine/mbc_initializer.rs
index d2dd9a51..22c9fce8 100644
--- a/lib_gb/src/machine/mbc_initializer.rs
+++ b/lib_gb/src/machine/mbc_initializer.rs
@@ -1,6 +1,11 @@
use crate::mmu::carts::*;
-pub fn initialize_mbc(mbc_type:u8, program:Vec, save_data:Option>)->Box{
+const CARTRIDGE_TYPE_ADDRESS:usize = 0x147;
+
+pub fn initialize_mbc(program:Vec, save_data:Option>)->Box{
+ let mbc_type = program[CARTRIDGE_TYPE_ADDRESS];
+ log::info!("initializing cartridge of type: {:#X}", mbc_type);
+
match mbc_type{
0x0|0x8=>Box::new(Rom::new(program,false, None)),
0x9=>Box::new(Rom::new(program, true, save_data)),
diff --git a/lib_gb/src/mmu/gb_mmu.rs b/lib_gb/src/mmu/gb_mmu.rs
index 1993a67f..8c434add 100644
--- a/lib_gb/src/mmu/gb_mmu.rs
+++ b/lib_gb/src/mmu/gb_mmu.rs
@@ -1,40 +1,33 @@
-use super::memory::*;
-use super::ram::Ram;
-use super::vram::VRam;
-use super::io_ports::IoPorts;
+use super::{io_components::IoComponents, memory::*};
use super::access_bus::AccessBus;
-use crate::utils::memory_registers::BOOT_REGISTER_ADDRESS;
+use crate::ppu::gfx_device::GfxDevice;
+use crate::{apu::{audio_device::AudioDevice, gb_apu::GbApu}, utils::memory_registers::BOOT_REGISTER_ADDRESS};
use super::carts::mbc::Mbc;
use crate::ppu::ppu_state::PpuState;
use std::boxed::Box;
pub const BOOT_ROM_SIZE:usize = 0x100;
const HRAM_SIZE:usize = 0x7F;
-const SPRITE_ATTRIBUTE_TABLE_SIZE:usize = 0xA0;
+const DMA_SIZE:u16 = 0xA0;
+const DMA_DEST:u16 = 0xFE00;
const BAD_READ_VALUE:u8 = 0xFF;
-pub struct GbMmu<'a>{
- pub ram: Ram,
- pub vram: VRam,
- pub finished_boot:bool,
- pub io_ports: IoPorts,
+pub struct GbMmu<'a, D:AudioDevice, G:GfxDevice>{
+ pub io_components: IoComponents,
boot_rom:[u8;BOOT_ROM_SIZE],
mbc: &'a mut Box,
- sprite_attribute_table:[u8;SPRITE_ATTRIBUTE_TABLE_SIZE],
hram: [u8;HRAM_SIZE],
- interupt_enable_register:u8,
- pub dma_state:Option,
- pub ppu_state:PpuState
+ interupt_enable_register:u8
}
//DMA only locks the used bus. there 2 possible used buses: extrnal (wram, rom, sram) and video (vram)
-impl<'a> Memory for GbMmu<'a>{
+impl<'a, D:AudioDevice, G:GfxDevice> Memory for GbMmu<'a, D, G>{
fn read(&self, address:u16)->u8{
- if let Some (bus) = &self.dma_state{
+ if let Some (bus) = &self.io_components.dma.enable{
return match address{
- 0xFF00..=0xFF7F => self.io_ports.read(address - 0xFF00),
+ 0xFF00..=0xFF7F => self.io_components.read(address - 0xFF00),
0xFEA0..=0xFEFF | 0xFF80..=0xFFFE | 0xFFFF=>self.read_unprotected(address),
0x8000..=0x9FFF => if let AccessBus::External = bus {self.read_unprotected(address)} else{Self::bad_dma_read(address)},
0..=0x7FFF | 0xA000..=0xFDFF => if let AccessBus::Video = bus {self.read_unprotected(address)} else{Self::bad_dma_read(address)},
@@ -44,7 +37,7 @@ impl<'a> Memory for GbMmu<'a>{
return match address{
0x8000..=0x9FFF=>{
if self.is_vram_ready_for_io(){
- return self.vram.read_current_bank(address-0x8000);
+ return self.io_components.ppu.vram.read_current_bank(address-0x8000);
}
else{
log::warn!("bad vram read");
@@ -53,22 +46,22 @@ impl<'a> Memory for GbMmu<'a>{
},
0xFE00..=0xFE9F=>{
if self.is_oam_ready_for_io(){
- return self.sprite_attribute_table[(address-0xFE00) as usize];
+ return self.io_components.ppu.oam[(address-0xFE00) as usize];
}
else{
log::warn!("bad oam read");
return BAD_READ_VALUE;
}
},
- 0xFF00..=0xFF7F => self.io_ports.read(address - 0xFF00),
+ 0xFF00..=0xFF7F => self.io_components.read(address - 0xFF00),
_=>self.read_unprotected(address)
};
}
fn write(&mut self, address:u16, value:u8){
- if let Some(bus) = &self.dma_state{
+ if let Some(bus) = &self.io_components.dma.enable{
match address{
- 0xFF00..=0xFF7F => self.io_ports.write(address- 0xFF00, value),
+ 0xFF00..=0xFF7F => self.io_components.write(address- 0xFF00, value),
0xFF80..=0xFFFE | 0xFFFF=>self.write_unprotected(address, value),
0x8000..=0x9FFF => if let AccessBus::External = bus {self.write_unprotected(address, value)} else{Self::bad_dma_write(address)},
0..=0x7FFF | 0xA000..=0xFDFF => if let AccessBus::Video = bus {self.write_unprotected(address, value)} else{Self::bad_dma_write(address)},
@@ -79,7 +72,7 @@ impl<'a> Memory for GbMmu<'a>{
match address{
0x8000..=0x9FFF=>{
if self.is_vram_ready_for_io(){
- self.vram.write_current_bank(address-0x8000, value);
+ self.io_components.ppu.vram.write_current_bank(address-0x8000, value);
}
else{
log::warn!("bad vram write")
@@ -87,24 +80,24 @@ impl<'a> Memory for GbMmu<'a>{
},
0xFE00..=0xFE9F=>{
if self.is_oam_ready_for_io(){
- self.sprite_attribute_table[(address-0xFE00) as usize] = value;
+ self.io_components.ppu.oam[(address-0xFE00) as usize] = value;
}
else{
log::warn!("bad oam write")
}
},
- 0xFF00..=0xFF7F=>self.io_ports.write(address - 0xFF00, value),
+ 0xFF00..=0xFF7F=>self.io_components.write(address - 0xFF00, value),
_=>self.write_unprotected(address, value)
}
}
}
}
-impl<'a> UnprotectedMemory for GbMmu<'a>{
+impl<'a, D:AudioDevice, G:GfxDevice> UnprotectedMemory for GbMmu<'a, D, G>{
fn read_unprotected(&self, address:u16) ->u8 {
return match address{
0x0..=0xFF=>{
- if self.finished_boot{
+ if self.io_components.finished_boot{
return self.mbc.read_bank0(address);
}
@@ -112,14 +105,14 @@ impl<'a> UnprotectedMemory for GbMmu<'a>{
},
0x100..=0x3FFF=>self.mbc.read_bank0(address),
0x4000..=0x7FFF=>self.mbc.read_current_bank(address-0x4000),
- 0x8000..=0x9FFF=>self.vram.read_current_bank(address-0x8000),
+ 0x8000..=0x9FFF=>self.io_components.ppu.vram.read_current_bank(address-0x8000),
0xA000..=0xBFFF=>self.mbc.read_external_ram(address-0xA000),
- 0xC000..=0xCFFF =>self.ram.read_bank0(address - 0xC000),
- 0xD000..=0xDFFF=>self.ram.read_current_bank(address-0xD000),
- 0xE000..=0xFDFF=>self.ram.read_bank0(address - 0xE000),
- 0xFE00..=0xFE9F=>self.sprite_attribute_table[(address-0xFE00) as usize],
+ 0xC000..=0xCFFF =>self.io_components.ram.read_bank0(address - 0xC000),
+ 0xD000..=0xDFFF=>self.io_components.ram.read_current_bank(address-0xD000),
+ 0xE000..=0xFDFF=>self.io_components.ram.read_bank0(address - 0xE000),
+ 0xFE00..=0xFE9F=>self.io_components.ppu.oam[(address-0xFE00) as usize],
0xFEA0..=0xFEFF=>0x0,
- 0xFF00..=0xFF7F=>self.io_ports.read_unprotected(address - 0xFF00),
+ 0xFF00..=0xFF7F=>self.io_components.read_unprotected(address - 0xFF00),
0xFF80..=0xFFFE=>self.hram[(address-0xFF80) as usize],
0xFFFF=>self.interupt_enable_register
};
@@ -128,65 +121,73 @@ impl<'a> UnprotectedMemory for GbMmu<'a>{
fn write_unprotected(&mut self, address:u16, value:u8) {
match address{
0x0..=0x7FFF=>self.mbc.write_rom(address, value),
- 0x8000..=0x9FFF=>self.vram.write_current_bank(address-0x8000, value),
+ 0x8000..=0x9FFF=>self.io_components.ppu.vram.write_current_bank(address-0x8000, value),
0xA000..=0xBFFF=>self.mbc.write_external_ram(address-0xA000,value),
- 0xC000..=0xCFFF =>self.ram.write_bank0(address - 0xC000,value),
- 0xE000..=0xFDFF=>self.ram.write_bank0(address - 0xE000,value),
- 0xD000..=0xDFFF=>self.ram.write_current_bank(address-0xD000,value),
- 0xFE00..=0xFE9F=>self.sprite_attribute_table[(address-0xFE00) as usize] = value,
+ 0xC000..=0xCFFF =>self.io_components.ram.write_bank0(address - 0xC000,value),
+ 0xE000..=0xFDFF=>self.io_components.ram.write_bank0(address - 0xE000,value),
+ 0xD000..=0xDFFF=>self.io_components.ram.write_current_bank(address-0xD000,value),
+ 0xFE00..=0xFE9F=>self.io_components.ppu.oam[(address-0xFE00) as usize] = value,
0xFEA0..=0xFEFF=>{},
- 0xFF00..=0xFF7F=>self.io_ports.write_unprotected(address - 0xFF00, value),
+ 0xFF00..=0xFF7F=>self.io_components.write_unprotected(address - 0xFF00, value),
0xFF80..=0xFFFE=>self.hram[(address-0xFF80) as usize] = value,
0xFFFF=>self.interupt_enable_register = value
}
}
}
-impl<'a> GbMmu<'a>{
- pub fn new_with_bootrom(mbc:&'a mut Box, boot_rom:[u8;BOOT_ROM_SIZE])->Self{
+impl<'a, D:AudioDevice, G:GfxDevice> GbMmu<'a, D, G>{
+ pub fn new_with_bootrom(mbc:&'a mut Box, boot_rom:[u8;BOOT_ROM_SIZE], apu:GbApu, gfx_device:G)->Self{
GbMmu{
- ram:Ram::default(),
- io_ports:IoPorts::default(),
+ io_components:IoComponents::new(apu, gfx_device),
mbc:mbc,
- vram:VRam::default(),
- sprite_attribute_table:[0;SPRITE_ATTRIBUTE_TABLE_SIZE],
hram:[0;HRAM_SIZE],
interupt_enable_register:0,
boot_rom:boot_rom,
- finished_boot:false,
- ppu_state:PpuState::OamSearch,
- dma_state: None
}
}
- pub fn new(mbc:&'a mut Box)->Self{
+ pub fn new(mbc:&'a mut Box, apu:GbApu, gfx_device: G)->Self{
let mut mmu = GbMmu{
- ram:Ram::default(),
- io_ports:IoPorts::default(),
+ io_components:IoComponents::new(apu, gfx_device),
mbc:mbc,
- vram:VRam::default(),
- sprite_attribute_table:[0;SPRITE_ATTRIBUTE_TABLE_SIZE],
hram:[0;HRAM_SIZE],
interupt_enable_register:0,
boot_rom:[0;BOOT_ROM_SIZE],
- finished_boot:true,
- ppu_state:PpuState::OamSearch,
- dma_state:None
};
//Setting the bootrom register to be set (the boot sequence has over)
- mmu.io_ports.write_unprotected(BOOT_REGISTER_ADDRESS - 0xFF00, 1);
+ mmu.write(BOOT_REGISTER_ADDRESS, 1);
mmu
}
+ pub fn cycle(&mut self, cycles:u8){
+ self.handle_dma_trasnfer(cycles);
+ self.io_components.cycle(cycles as u32);
+ }
+
+ fn handle_dma_trasnfer(&mut self, cycles: u8) {
+ if self.io_components.dma.enable.is_some(){
+ let cycles_to_run = std::cmp::min(self.io_components.dma.dma_cycle_counter + cycles as u16, DMA_SIZE);
+ for i in self.io_components.dma.dma_cycle_counter..cycles_to_run as u16{
+ self.write_unprotected(DMA_DEST + i, self.read_unprotected(self.io_components.dma.soure_address + i));
+ }
+
+ self.io_components.dma.dma_cycle_counter += cycles as u16;
+ if self.io_components.dma.dma_cycle_counter >= DMA_SIZE{
+ self.io_components.dma.dma_cycle_counter = 0;
+ self.io_components.dma.enable = Option::None;
+ }
+ }
+ }
+
fn is_oam_ready_for_io(&self)->bool{
- let ppu_state = self.ppu_state as u8;
+ let ppu_state = self.io_components.ppu.state as u8;
return ppu_state != PpuState::OamSearch as u8 && ppu_state != PpuState::PixelTransfer as u8
}
fn is_vram_ready_for_io(&self)->bool{
- return self.ppu_state as u8 != PpuState::PixelTransfer as u8;
+ return self.io_components.ppu.state as u8 != PpuState::PixelTransfer as u8;
}
fn bad_dma_read(address:u16)->u8{
diff --git a/lib_gb/src/mmu/io_components.rs b/lib_gb/src/mmu/io_components.rs
new file mode 100644
index 00000000..fc18efc4
--- /dev/null
+++ b/lib_gb/src/mmu/io_components.rs
@@ -0,0 +1,176 @@
+use crate::{apu::{*,audio_device::AudioDevice, gb_apu::GbApu},
+ ppu::{gb_ppu::GbPpu, ppu_register_updater::*, gfx_device::GfxDevice},
+ timer::timer_register_updater::*,
+ utils::memory_registers::*
+};
+use crate::timer::gb_timer::GbTimer;
+use super::{access_bus::AccessBus, memory::*, oam_dma_transfer::OamDmaTransfer, ram::Ram};
+use super::io_ports::*;
+
+pub const IO_PORTS_SIZE:usize = 0x80;
+const WAVE_RAM_START_INDEX:u16 = 0x30;
+const WAVE_RAM_END_INDEX:u16 = 0x3F;
+
+pub struct IoComponents{
+ pub ram: Ram,
+ pub apu: GbApu,
+ pub timer: GbTimer,
+ pub ppu:GbPpu,
+ ports:[u8;IO_PORTS_SIZE],
+ pub dma:OamDmaTransfer,
+ pub finished_boot:bool,
+}
+
+io_port_index!(LCDC_REGISTER_INDEX, LCDC_REGISTER_ADDRESS);
+io_port_index!(STAT_REGISTER_INDEX, STAT_REGISTER_ADDRESS);
+io_port_index!(SCY_REGISTER_INDEX, SCY_REGISTER_ADDRESS);
+io_port_index!(SCX_REGISTER_INDEX, SCX_REGISTER_ADDRESS);
+io_port_index!(LY_REGISTER_INDEX, LY_REGISTER_ADDRESS);
+io_port_index!(LYC_REGISTER_INDEX, LYC_REGISTER_ADDRESS);
+io_port_index!(DMA_REGISTER_INDEX, DMA_REGISTER_ADDRESS);
+io_port_index!(WY_REGISTER_INDEX, WY_REGISTER_ADDRESS);
+io_port_index!(WX_REGISTER_INDEX, WX_REGISTER_ADDRESS);
+io_port_index!(BOOT_REGISTER_INDEX, BOOT_REGISTER_ADDRESS);
+io_port_index!(BGP_REGISTER_INDEX, BGP_REGISTER_ADDRESS);
+io_port_index!(OBP0_REGISTER_INDEX, OBP0_REGISTER_ADDRESS);
+io_port_index!(OBP1_REGISTER_INDEX, OBP1_REGISTER_ADDRESS);
+io_port_index!(IF_REGISTER_INDEX, IF_REGISTER_ADDRESS);
+
+impl Memory for IoComponents{
+ fn read(&self, address:u16)->u8 {
+ let mut value = self.ports[address as usize];
+ return match address {
+ //Timer
+ TAC_REGISTER_INDEX=> value & 0b111,
+ DIV_REGISTER_INDEX=> get_div(&self.timer),
+ TIMA_REGISTER_INDEX=> self.timer.tima_register,
+ //APU
+ NR10_REGISTER_INDEX=>value | 0b1000_0000,
+ NR11_REGISTER_INDEX=> value | 0b0011_1111,
+ NR13_REGISTER_INDEX=> 0xFF,
+ NR14_REGISTER_INDEX=> value | 0b1011_1111,
+ 0x15 => 0xFF, //Not used
+ NR21_REGISTER_INDEX=> value | 0b0011_1111,
+ NR23_REGISTER_INDEX=> 0xFF,
+ NR24_REGISTER_INDEX=> value | 0b1011_1111,
+ NR30_REGISTER_INDEX=> value | 0b0111_1111,
+ NR31_REGISTER_INDEX=> value | 0xFF,
+ NR32_REGISTER_INDEX=> value | 0b1001_1111,
+ NR33_REGISTER_INDEX=> value | 0xFF,
+ NR34_REGISTER_INDEX=> value | 0b1011_1111,
+ 0x1F => 0xFF, //Not used
+ NR41_REGISTER_INDEX=> 0xFF,
+ NR44_REGISTER_INDEX=> value | 0b1011_1111,
+ NR52_REGISTER_INDEX=> {
+ get_nr52(&self.apu, &mut value);
+ value
+ }
+ 0x27..=0x2F => 0xFF, //Not used
+ WAVE_RAM_START_INDEX..=WAVE_RAM_END_INDEX => get_wave_ram(&self.apu.wave_channel, address),
+ //PPU
+ STAT_REGISTER_INDEX=> get_stat(&self.ppu),
+ LY_REGISTER_INDEX=> get_ly(&self.ppu),
+ //Joypad
+ JOYP_REGISTER_INDEX => {
+ let joypad_value = self.ports[JOYP_REGISTER_INDEX as usize];
+ (joypad_value & 0xF) | (value & 0xF0)
+ }
+ _=>value
+ };
+ }
+
+ fn write(&mut self, address:u16, mut value:u8) {
+ match address{
+ //timer
+ DIV_REGISTER_INDEX=> {
+ reset_div(&mut self.timer);
+ value = 0;
+ }
+ TIMA_REGISTER_INDEX=> set_tima(&mut self.timer, value),
+ TMA_REGISTER_INDEX=> set_tma(&mut self.timer, value),
+ TAC_REGISTER_INDEX=> {
+ set_tac(&mut self.timer, value);
+ value &= 0b111;
+ }
+ //APU
+ NR10_REGISTER_INDEX=> set_nr10(&mut self.apu.sweep_tone_channel, value),
+ NR11_REGISTER_INDEX=> set_nr11(&mut self.apu.sweep_tone_channel, value),
+ NR12_REGISTER_INDEX=> set_nr12(&mut self.apu.sweep_tone_channel, value),
+ NR13_REGISTER_INDEX=> set_nr13(&mut self.apu.sweep_tone_channel, value),
+ NR14_REGISTER_INDEX=> set_nr14(&mut self.apu.sweep_tone_channel, &self.apu.frame_sequencer, value),
+ NR21_REGISTER_INDEX=> set_nr11(&mut self.apu.tone_channel, value),
+ NR22_REGISTER_INDEX=> set_nr12(&mut self.apu.tone_channel, value),
+ NR23_REGISTER_INDEX=> set_nr13(&mut self.apu.tone_channel, value),
+ NR24_REGISTER_INDEX=> set_nr24(&mut self.apu.tone_channel, &self.apu.frame_sequencer, value),
+ NR30_REGISTER_INDEX=> set_nr30(&mut self.apu.wave_channel, value),
+ NR31_REGISTER_INDEX=> set_nr31(&mut self.apu.wave_channel, value),
+ NR32_REGISTER_INDEX=> set_nr32(&mut self.apu.wave_channel, value),
+ NR33_REGISTER_INDEX=> set_nr33(&mut self.apu.wave_channel, value),
+ NR34_REGISTER_INDEX=> set_nr34(&mut self.apu.wave_channel, &self.apu.frame_sequencer, self.ports[NR30_REGISTER_INDEX as usize],value),
+ NR41_REGISTER_INDEX=> set_nr41(&mut self.apu.noise_channel, value),
+ NR42_REGISTER_INDEX=> set_nr42(&mut self.apu.noise_channel, value),
+ NR43_REGISTER_INDEX=> set_nr43(&mut self.apu.noise_channel, value),
+ NR44_REGISTER_INDEX=> set_nr44(&mut self.apu.noise_channel, &self.apu.frame_sequencer, value),
+ NR50_REGISTER_INDEX=> set_nr50(&mut self.apu, value),
+ NR51_REGISTER_INDEX=> set_nr51(&mut self.apu, value),
+ NR52_REGISTER_INDEX=> set_nr52(&mut self.apu, &mut self.ports,value),
+ WAVE_RAM_START_INDEX..=WAVE_RAM_END_INDEX => set_wave_ram(&mut self.apu.wave_channel, address, value),
+ //PPU
+ LCDC_REGISTER_INDEX=> handle_lcdcontrol_register(value, &mut self.ppu),
+ STAT_REGISTER_INDEX=> {
+ update_stat_register(value, &mut self.ppu);
+ value = (value >> 2) << 2;
+ },
+ SCY_REGISTER_INDEX=> set_scy(&mut self.ppu, value),
+ SCX_REGISTER_INDEX=> set_scx(&mut self.ppu, value),
+ LYC_REGISTER_INDEX=> set_lyc(&mut self.ppu, value),
+ DMA_REGISTER_INDEX=>{
+ let address = (value as u16) << 8;
+ self.dma.soure_address = address;
+ self.dma.enable = match value{
+ 0..=0x7F=>Some(AccessBus::External),
+ 0x80..=0x9F=>Some(AccessBus::Video),
+ 0xA0..=0xFF=>Some(AccessBus::External)
+ }
+ }
+ BGP_REGISTER_INDEX=> handle_bg_pallet_register(value,&mut self.ppu.bg_color_mapping),
+ OBP0_REGISTER_INDEX=> handle_obp_pallet_register(value,&mut self.ppu.obj_color_mapping0),
+ OBP1_REGISTER_INDEX=> handle_obp_pallet_register(value,&mut self.ppu.obj_color_mapping1),
+ WY_REGISTER_INDEX=> handle_wy_register(value, &mut self.ppu),
+ WX_REGISTER_INDEX=> handle_wx_register(value, &mut self.ppu),
+ BOOT_REGISTER_INDEX=> self.finished_boot = value != 0,
+ JOYP_REGISTER_INDEX => {
+ let joypad_value = self.ports[JOYP_REGISTER_INDEX as usize];
+ value = (joypad_value & 0xF) | (value & 0xF0);
+ }
+ // TODO: handle gbc registers (expecailly ram and vram)
+ _=>{}
+ }
+
+ self.ports[address as usize] = value;
+ }
+}
+
+impl UnprotectedMemory for IoComponents{
+ fn read_unprotected(&self, address:u16)->u8 {
+ self.ports[address as usize]
+ }
+
+ fn write_unprotected(&mut self, address:u16, value:u8) {
+ self.ports[address as usize] = value;
+ }
+}
+
+impl IoComponents{
+ pub fn new(apu:GbApu, gfx_device:GFX)->Self{
+ Self{apu, ports:[0;IO_PORTS_SIZE], timer:GbTimer::default(), ppu:GbPpu::new(gfx_device), dma:OamDmaTransfer::default(),finished_boot:false, ram:Ram::default()}
+ }
+
+ pub fn cycle(&mut self, cycles:u32){
+ let mut if_register = self.ports[IF_REGISTER_INDEX as usize];
+ self.timer.cycle(&mut if_register, cycles as u8);
+ self.apu.cycle(cycles as u8);
+ self.ppu.cycle( cycles, &mut if_register);
+ self.ports[IF_REGISTER_INDEX as usize] = if_register;
+ }
+}
\ No newline at end of file
diff --git a/lib_gb/src/mmu/io_ports.rs b/lib_gb/src/mmu/io_ports.rs
index 10c799e1..58dc253f 100644
--- a/lib_gb/src/mmu/io_ports.rs
+++ b/lib_gb/src/mmu/io_ports.rs
@@ -1,11 +1,9 @@
use crate::utils::memory_registers::*;
-use super::memory::{UnprotectedMemory, Memory};
pub const IO_PORTS_SIZE:usize = 0x80;
pub const IO_PORTS_MEMORY_OFFSET:u16 = 0xFF00;
-#[macro_use]
macro_rules! io_port_index{
($name:ident, $reg_address:expr) => {
const $name:u16 = $reg_address - IO_PORTS_MEMORY_OFFSET;
@@ -18,10 +16,11 @@ macro_rules! pub_io_port_index{
}
pub_io_port_index!(DIV_REGISTER_INDEX, DIV_REGISTER_ADDRESS);
+pub_io_port_index!(TAC_REGISTER_INDEX, TAC_REGISTER_ADDRESS);
+pub_io_port_index!(TIMA_REGISTER_INDEX, TIMA_REGISTER_ADDRESS);
+pub_io_port_index!(TMA_REGISTER_INDEX, TMA_REGISTER_ADDRESS);
-io_port_index!(TAC_REGISTER_INDEX, TAC_REGISTER_ADDRESS);
-io_port_index!(STAT_REGISTER_INDEX, STAT_REGISTER_ADDRESS);
-io_port_index!(JOYP_REGISTER_INDEX, JOYP_REGISTER_ADDRESS);
+pub_io_port_index!(JOYP_REGISTER_INDEX, JOYP_REGISTER_ADDRESS);
pub_io_port_index!(NR10_REGISTER_INDEX, NR10_REGISTER_ADDRESS);
pub_io_port_index!(NR11_REGISTER_INDEX, NR11_REGISTER_ADDRESS);
pub_io_port_index!(NR12_REGISTER_INDEX, NR12_REGISTER_ADDRESS);
@@ -40,92 +39,6 @@ pub_io_port_index!(NR41_REGISTER_INDEX, NR41_REGISTER_ADDRESS);
pub_io_port_index!(NR42_REGISTER_INDEX, NR42_REGISTER_ADDRESS);
pub_io_port_index!(NR43_REGISTER_INDEX, NR43_REGISTER_ADDRESS);
pub_io_port_index!(NR44_REGISTER_INDEX, NR44_REGISTER_ADDRESS);
-pub_io_port_index!(NR52_REGISTER_INDEX, NR52_REGISTER_ADDRESS);
-
-pub struct IoPorts{
- ports:[u8;IO_PORTS_SIZE],
- ports_cycle_trigger:[bool; IO_PORTS_SIZE]
-}
-
-impl Memory for IoPorts{
- fn read(&self, address:u16)->u8{
- let value = self.ports[address as usize];
- match address{
- NR10_REGISTER_INDEX=> value | 0b1000_0000,
- NR11_REGISTER_INDEX=> value | 0b0011_1111,
- NR13_REGISTER_INDEX=> 0xFF,
- NR14_REGISTER_INDEX=> value | 0b1011_1111,
- 0x15 => 0xFF, //Not used
- NR21_REGISTER_INDEX=> value | 0b0011_1111,
- NR23_REGISTER_INDEX=> 0xFF,
- NR24_REGISTER_INDEX=> value | 0b1011_1111,
- NR30_REGISTER_INDEX=> value | 0b0111_1111,
- NR31_REGISTER_INDEX=> value | 0xFF,
- NR32_REGISTER_INDEX=> value | 0b1001_1111,
- NR33_REGISTER_INDEX=> value | 0xFF,
- NR34_REGISTER_INDEX=> value | 0b1011_1111,
- 0x1F => 0xFF, //Not used
- NR41_REGISTER_INDEX=> 0xFF,
- NR44_REGISTER_INDEX=> value | 0b1011_1111,
- NR52_REGISTER_INDEX=> value | 0b0111_0000,
- 0x27..=0x2F => 0xFF, //Not used
- TAC_REGISTER_INDEX=> value & 0b111,
- JOYP_REGISTER_INDEX => {
- let joypad_value = self.ports[JOYP_REGISTER_INDEX as usize];
- (joypad_value & 0xF) | (value & 0xF0)
- }
- _=>value
- }
- }
-
- fn write(&mut self, address:u16, mut value:u8){
- match address{
- DIV_REGISTER_INDEX=> value = 0,
- TAC_REGISTER_INDEX=> value &= 0b111,
- STAT_REGISTER_INDEX => value = (value >> 2) << 2,
- JOYP_REGISTER_INDEX => {
- let joypad_value = self.ports[JOYP_REGISTER_INDEX as usize];
- value = (joypad_value & 0xF) | (value & 0xF0);
- }
- _=>{}
- }
-
- self.ports_cycle_trigger[address as usize] = true;
-
- self.ports[address as usize] = value;
- }
-}
-
-impl UnprotectedMemory for IoPorts{
- fn write_unprotected(&mut self, address:u16, value:u8){
- self.ports[address as usize] = value;
- }
-
- fn read_unprotected(&self, address:u16) ->u8 {
- self.ports[address as usize]
- }
-}
-
-impl IoPorts{
- pub fn get_ports_cycle_trigger(&mut self)->&mut [bool; IO_PORTS_SIZE]{
- return &mut self.ports_cycle_trigger;
- }
-
- pub fn clear_io_ports_triggers(&mut self){
- unsafe{std::ptr::write_bytes::(self.ports_cycle_trigger.as_mut_ptr(), 0, IO_PORTS_SIZE);}
- }
-}
-
-impl Default for IoPorts{
- fn default()->Self{
- let mut io_ports = IoPorts{
- ports:[0;IO_PORTS_SIZE],
- ports_cycle_trigger:[false;IO_PORTS_SIZE]
- };
-
- //joypad register initiall value
- io_ports.ports[JOYP_REGISTER_INDEX as usize] = 0xFF;
-
- io_ports
- }
-}
\ No newline at end of file
+pub_io_port_index!(NR50_REGISTER_INDEX, NR50_REGISTER_ADDRESS);
+pub_io_port_index!(NR51_REGISTER_INDEX, NR51_REGISTER_ADDRESS);
+pub_io_port_index!(NR52_REGISTER_INDEX, NR52_REGISTER_ADDRESS);
\ No newline at end of file
diff --git a/lib_gb/src/mmu/mmu_register_updater.rs b/lib_gb/src/mmu/mmu_register_updater.rs
deleted file mode 100644
index 69cbe119..00000000
--- a/lib_gb/src/mmu/mmu_register_updater.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-use crate::{ppu::ppu_state::PpuState, utils::memory_registers::*};
-use super::{access_bus::AccessBus, gb_mmu::GbMmu, io_ports::IO_PORTS_MEMORY_OFFSET, memory::UnprotectedMemory, oam_dma_transferer::OamDmaTransferer};
-
-const DMA_REGISTER_INDEX:usize = (DMA_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize;
-
-pub fn update_mmu_registers(memory: &mut GbMmu,dma:&mut OamDmaTransferer){
-
- handle_ppu_state(memory, memory.read_unprotected(STAT_REGISTER_ADDRESS));
- handle_wram_register(memory, memory.read_unprotected(SVBK_REGISTER_ADDRESS));
- handle_bootrom_register(memory, memory.read_unprotected(BOOT_REGISTER_ADDRESS));
- let ports = memory.io_ports.get_ports_cycle_trigger();
- if ports[DMA_REGISTER_INDEX]{
- ports[DMA_REGISTER_INDEX] = false;
- handle_dma_transfer_register(memory.read_unprotected(DMA_REGISTER_ADDRESS), dma, memory);
- }
- else{
- memory.dma_state = dma.enable;
- }
-}
-
-fn handle_ppu_state(memory:&mut GbMmu, stat:u8){
- memory.ppu_state = PpuState::from_u8(stat & 0b0000_0011);
-}
-
-fn handle_wram_register(memory: &mut GbMmu, register:u8){
- let bank:u8 = register & 0b00000111;
- memory.ram.set_bank(bank);
-}
-
-fn handle_bootrom_register(memory: &mut GbMmu, register:u8){
- memory.finished_boot = register == 1;
-}
-
-fn handle_dma_transfer_register(register:u8, dma: &mut OamDmaTransferer, mmu:&mut GbMmu){
- dma.soure_address = (register as u16) << 8;
- dma.enable = match register{
- 0..=0x7F=>Some(AccessBus::External),
- 0x80..=0x9F=>Some(AccessBus::Video),
- 0xA0..=0xFF=>Some(AccessBus::External)
- };
-
- mmu.dma_state = dma.enable;
-}
\ No newline at end of file
diff --git a/lib_gb/src/mmu/mod.rs b/lib_gb/src/mmu/mod.rs
index 890e7c99..886978cb 100644
--- a/lib_gb/src/mmu/mod.rs
+++ b/lib_gb/src/mmu/mod.rs
@@ -5,6 +5,6 @@ pub mod vram;
#[macro_use]
pub mod io_ports;
pub mod carts;
-pub mod access_bus;
-pub mod mmu_register_updater;
-pub mod oam_dma_transferer;
\ No newline at end of file
+pub mod access_bus;
+pub mod oam_dma_transfer;
+pub mod io_components;
\ No newline at end of file
diff --git a/lib_gb/src/mmu/oam_dma_transfer.rs b/lib_gb/src/mmu/oam_dma_transfer.rs
new file mode 100644
index 00000000..703d74d1
--- /dev/null
+++ b/lib_gb/src/mmu/oam_dma_transfer.rs
@@ -0,0 +1,13 @@
+use super::access_bus::AccessBus;
+
+pub struct OamDmaTransfer{
+ pub soure_address:u16,
+ pub enable:Option,
+ pub dma_cycle_counter:u16
+}
+
+impl Default for OamDmaTransfer{
+ fn default() -> Self {
+ OamDmaTransfer{dma_cycle_counter:0, enable:None, soure_address:0}
+ }
+}
\ No newline at end of file
diff --git a/lib_gb/src/mmu/oam_dma_transferer.rs b/lib_gb/src/mmu/oam_dma_transferer.rs
deleted file mode 100644
index a24034e6..00000000
--- a/lib_gb/src/mmu/oam_dma_transferer.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-use super::{access_bus::AccessBus, memory::UnprotectedMemory};
-
-const DMA_SIZE:u16 = 0xA0;
-const DMA_DEST:u16 = 0xFE00;
-
-pub struct OamDmaTransferer{
- pub soure_address:u16,
- pub enable:Option,
- dma_cycle_counter:u16
-}
-
-impl Default for OamDmaTransferer{
- fn default() -> Self {
- OamDmaTransferer{dma_cycle_counter:0, enable:None, soure_address:0}
- }
-}
-
-impl OamDmaTransferer{
- pub fn cycle(&mut self,memory:&mut impl UnprotectedMemory, m_cycles:u8){
- if self.enable.is_some(){
- let cycles_to_run = std::cmp::min(self.dma_cycle_counter + m_cycles as u16, DMA_SIZE);
- for i in self.dma_cycle_counter..cycles_to_run as u16{
- memory.write_unprotected(DMA_DEST + i, memory.read_unprotected(self.soure_address + i));
- }
-
- self.dma_cycle_counter += m_cycles as u16;
-
- if self.dma_cycle_counter >= DMA_SIZE{
- self.enable = None;
- self.dma_cycle_counter = 0;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/lib_gb/src/mmu/vram.rs b/lib_gb/src/mmu/vram.rs
index 5395140a..d1ba629d 100644
--- a/lib_gb/src/mmu/vram.rs
+++ b/lib_gb/src/mmu/vram.rs
@@ -6,8 +6,7 @@ pub struct VRam{
}
impl VRam{
- pub fn set_bank(&mut self, bank:u8)
- {
+ pub fn set_bank(&mut self, bank:u8){
self.current_bank_register = bank;
}
diff --git a/lib_gb/src/ppu/color.rs b/lib_gb/src/ppu/color.rs
index a0806109..26c8fb8c 100644
--- a/lib_gb/src/ppu/color.rs
+++ b/lib_gb/src/ppu/color.rs
@@ -33,3 +33,9 @@ impl PartialEq for Color{
self.r == color.r
}
}
+
+impl From for u32{
+ fn from(color: Color) -> Self {
+ ((color.r as u32) << 16) | ((color.g as u32) << 8) | (color.b as u32)
+ }
+}
\ No newline at end of file
diff --git a/lib_gb/src/ppu/extended_sprite.rs b/lib_gb/src/ppu/extended_sprite.rs
deleted file mode 100644
index 09a92d96..00000000
--- a/lib_gb/src/ppu/extended_sprite.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-use super::sprite::*;
-
-pub struct ExtendedSprite{
- pub pixels:[u8;128]
-}
-
-impl ExtendedSprite {
- const SIZE:u8 = 16;
-
- pub fn new() -> ExtendedSprite {
- ExtendedSprite { pixels: [0; 128] }
- }
-}
-
-impl Clone for ExtendedSprite{
- fn clone(&self)->Self{
- ExtendedSprite{
- pixels:self.pixels
- }
- }
-}
-
-impl Sprite for ExtendedSprite{
- fn size(&self)->u8 {Self::SIZE}
-
- fn get_pixel(&self, pos: u8)->u8{
- self.pixels[pos as usize]
- }
-
- fn set_pixel(&mut self, pos: u8, pixel: u8){
- self.pixels[pos as usize] = pixel;
- }
-
- fn flip_x(&mut self){
- let mut fliiped = ExtendedSprite::new();
-
- for y in 0..16{
- let line = &self.pixels[y*8 .. (y+1)*8];
- for x in 0..4{
- fliiped.pixels[y*8 + x] = line[7-x];
- fliiped.pixels[y*8 + (7-x)] = line[x];
- }
- }
-
- *self = fliiped;
- }
-
- fn flip_y(&mut self){
- let mut flipped = ExtendedSprite::new();
- for y in 0..8{
- let upper_line = &self.pixels[y*8..(y+1)*8];
- let opposite_index = 15-y;
- let lower_line = &self.pixels[opposite_index*8..(opposite_index+1)*8];
-
- copy_pixels(&mut flipped,y as u8, lower_line);
- copy_pixels(&mut flipped,opposite_index as u8, upper_line);
- }
-
- *self = flipped;
- }
-}
\ No newline at end of file
diff --git a/lib_gb/src/ppu/fifo/background_fetcher.rs b/lib_gb/src/ppu/fifo/background_fetcher.rs
new file mode 100644
index 00000000..42e633c6
--- /dev/null
+++ b/lib_gb/src/ppu/fifo/background_fetcher.rs
@@ -0,0 +1,127 @@
+use crate::{mmu::vram::VRam, utils::{bit_masks::*, fixed_size_queue::FixedSizeQueue, vec2::Vec2}};
+use super::{FIFO_SIZE, SPRITE_WIDTH, fetcher_state_machine::FetcherStateMachine, fetching_state::*};
+
+pub struct BackgroundFetcher{
+ pub fifo:FixedSizeQueue,
+ pub window_line_counter:u8,
+ pub has_wy_reached_ly:bool,
+
+ current_x_pos:u8,
+ rendering_window:bool,
+ fetcher_state_machine:FetcherStateMachine,
+}
+
+impl BackgroundFetcher{
+ pub fn new()->Self{
+ let state_machine = [FetchingState::Sleep, FetchingState::FetchTileNumber, FetchingState::Sleep, FetchingState::FetchLowTile, FetchingState::Sleep, FetchingState::FetchHighTile, FetchingState::Sleep, FetchingState::Push];
+ BackgroundFetcher{
+ fetcher_state_machine:FetcherStateMachine::new(state_machine),
+ current_x_pos:0,
+ fifo:FixedSizeQueue::::new(),
+ window_line_counter:0,
+ rendering_window:false,
+ has_wy_reached_ly:false,
+ }
+ }
+
+ pub fn reset(&mut self){
+ self.fifo.clear();
+ self.current_x_pos = 0;
+ self.fetcher_state_machine.reset();
+ self.rendering_window = false;
+ }
+
+ pub fn pause(&mut self){
+ self.fetcher_state_machine.reset();
+ }
+
+ pub fn try_increment_window_counter(&mut self, ly_register:u8, wy_register:u8){
+ if self.rendering_window && ly_register >= wy_register{
+ self.window_line_counter += 1;
+ }
+ }
+
+ pub fn fetch_pixels(&mut self, vram:&VRam, lcd_control:u8, ly_register:u8, window_pos:&Vec2, bg_pos:&Vec2){
+ self.has_wy_reached_ly = self.has_wy_reached_ly || ly_register == window_pos.y;
+ let last_rendering_status = self.rendering_window;
+ self.rendering_window = self.is_rendering_wnd(lcd_control, window_pos);
+
+ // In case I was rendering a background pixel need to reset the state of the fetcher
+ // (and maybe clear the fifo but right now Im not doing it since im not sure what about the current_x_pos var)
+ if self.rendering_window && !last_rendering_status{
+ self.fetcher_state_machine.reset();
+ }
+
+ match self.fetcher_state_machine.current_state(){
+ FetchingState::FetchTileNumber=>{
+ let tile_num = if self.rendering_window{
+ let tile_map_address:u16 = if (lcd_control & BIT_6_MASK) == 0 {0x1800} else {0x1C00};
+ vram.read_current_bank(tile_map_address + (32 * (self.window_line_counter as u16 / SPRITE_WIDTH as u16)) + ((self.current_x_pos - window_pos.x) as u16 / SPRITE_WIDTH as u16))
+ }
+ else{
+ let tile_map_address = if (lcd_control & BIT_3_MASK) == 0 {0x1800} else {0x1C00};
+ let scx_offset = ((bg_pos.x as u16 + self.current_x_pos as u16) / SPRITE_WIDTH as u16 ) & 31;
+ let scy_offset = ((bg_pos.y as u16 + ly_register as u16) & 0xFF) / SPRITE_WIDTH as u16;
+
+ vram.read_current_bank(tile_map_address + ((32 * scy_offset) + scx_offset))
+ };
+
+ self.fetcher_state_machine.data.reset();
+ self.fetcher_state_machine.data.tile_data = Some(tile_num);
+ }
+ FetchingState::FetchLowTile=>{
+ let tile_num = self.fetcher_state_machine.data.tile_data.expect("State machine is corrupted, No Tile data on FetchLowTIle");
+ let address = self.get_tila_data_address(lcd_control, bg_pos, ly_register, tile_num);
+ let low_data = vram.read_current_bank(address);
+
+ self.fetcher_state_machine.data.low_tile_data = Some(low_data);
+ }
+ FetchingState::FetchHighTile=>{
+ let tile_num= self.fetcher_state_machine.data.tile_data.expect("State machine is corrupted, No Tile data on FetchHighTIle");
+ let address = self.get_tila_data_address(lcd_control, bg_pos, ly_register, tile_num);
+ let high_data = vram.read_current_bank(address + 1);
+
+ self.fetcher_state_machine.data.high_tile_data = Some(high_data);
+ }
+ FetchingState::Push=>{
+ let low_data = self.fetcher_state_machine.data.low_tile_data.expect("State machine is corrupted, No Low data on Push");
+ let high_data = self.fetcher_state_machine.data.high_tile_data.expect("State machine is corrupted, No High data on Push");
+ if self.fifo.len() == 0{
+ if lcd_control & BIT_0_MASK == 0{
+ for _ in 0..SPRITE_WIDTH{
+ //When the baclkground is off pushes 0
+ self.fifo.push(0);
+ self.current_x_pos += 1;
+ }
+ }
+ else{
+ for i in (0..SPRITE_WIDTH).rev(){
+ let mask = 1 << i;
+ let mut pixel = (low_data & mask) >> i;
+ pixel |= ((high_data & mask) >> i) << 1;
+ self.fifo.push(pixel);
+ self.current_x_pos += 1;
+ }
+ }
+ }
+ }
+ FetchingState::Sleep=>{}
+ }
+
+ self.fetcher_state_machine.advance();
+ }
+
+ fn get_tila_data_address(&self, lcd_control:u8, bg_pos:&Vec2, ly_register:u8, tile_num:u8)->u16{
+ let current_tile_base_data_address = if (lcd_control & BIT_4_MASK) == 0 && (tile_num & BIT_7_MASK) == 0 {0x1000} else {0};
+ let current_tile_data_address = current_tile_base_data_address + (tile_num as u16 * 16);
+ return if self.rendering_window{
+ current_tile_data_address + (2 * (self.window_line_counter % SPRITE_WIDTH)) as u16
+ } else{
+ current_tile_data_address + (2 * ((bg_pos.y as u16 + ly_register as u16) % SPRITE_WIDTH as u16))
+ };
+ }
+
+ fn is_rendering_wnd(&self, lcd_control:u8, window_pos:&Vec2)->bool{
+ window_pos.x <= self.current_x_pos && self.has_wy_reached_ly && (lcd_control & BIT_5_MASK) != 0
+ }
+}
\ No newline at end of file
diff --git a/lib_gb/src/ppu/fifo/fetcher_state_machine.rs b/lib_gb/src/ppu/fifo/fetcher_state_machine.rs
new file mode 100644
index 00000000..e051c30b
--- /dev/null
+++ b/lib_gb/src/ppu/fifo/fetcher_state_machine.rs
@@ -0,0 +1,30 @@
+use super::fetching_state::*;
+
+pub struct FetcherStateMachine{
+ pub data:FetchingStateData,
+ state:usize,
+ state_machine:[FetchingState;8]
+}
+
+impl FetcherStateMachine{
+ pub fn advance(&mut self){
+ self.state = (self.state + 1) % 8;
+ }
+
+ pub fn new(state_machine:[FetchingState;8])->Self{
+ Self{
+ data:FetchingStateData{high_tile_data:None, low_tile_data:None, tile_data:None},
+ state:0,
+ state_machine
+ }
+ }
+
+ pub fn reset(&mut self){
+ self.state = 0;
+ self.data.reset();
+ }
+
+ pub fn current_state(&self)->&FetchingState{
+ &self.state_machine[self.state]
+ }
+}
\ No newline at end of file
diff --git a/lib_gb/src/ppu/fifo/fetching_state.rs b/lib_gb/src/ppu/fifo/fetching_state.rs
new file mode 100644
index 00000000..68b6c777
--- /dev/null
+++ b/lib_gb/src/ppu/fifo/fetching_state.rs
@@ -0,0 +1,21 @@
+pub enum FetchingState{
+ FetchTileNumber,
+ FetchLowTile,
+ FetchHighTile,
+ Push,
+ Sleep
+}
+
+pub struct FetchingStateData{
+ pub tile_data:Option,
+ pub low_tile_data:Option,
+ pub high_tile_data:Option,
+}
+
+impl FetchingStateData{
+ pub fn reset(&mut self){
+ self.high_tile_data = None;
+ self.low_tile_data = None;
+ self.tile_data = None;
+ }
+}
\ No newline at end of file
diff --git a/lib_gb/src/ppu/fifo/mod.rs b/lib_gb/src/ppu/fifo/mod.rs
new file mode 100644
index 00000000..cd122215
--- /dev/null
+++ b/lib_gb/src/ppu/fifo/mod.rs
@@ -0,0 +1,7 @@
+pub mod background_fetcher;
+pub mod sprite_fetcher;
+mod fetching_state;
+mod fetcher_state_machine;
+
+pub const FIFO_SIZE:usize = 8;
+pub const SPRITE_WIDTH:u8 = 8;
\ No newline at end of file
diff --git a/lib_gb/src/ppu/fifo/sprite_fetcher.rs b/lib_gb/src/ppu/fifo/sprite_fetcher.rs
new file mode 100644
index 00000000..fe6a3625
--- /dev/null
+++ b/lib_gb/src/ppu/fifo/sprite_fetcher.rs
@@ -0,0 +1,140 @@
+use crate::{mmu::vram::VRam, ppu::sprite_attribute::SpriteAttribute, utils::{self, bit_masks::{BIT_0_MASK, BIT_2_MASK}, fixed_size_queue::FixedSizeQueue}};
+use super::{FIFO_SIZE, SPRITE_WIDTH, fetcher_state_machine::FetcherStateMachine, fetching_state::*};
+
+pub const NORMAL_SPRITE_HIGHT:u8 = 8;
+pub const EXTENDED_SPRITE_HIGHT:u8 = 16;
+pub const MAX_SPRITES_PER_LINE:usize = 10;
+
+pub struct SpriteFetcher{
+ pub fifo:FixedSizeQueue<(u8, u8), FIFO_SIZE>,
+ pub oam_entries:[SpriteAttribute; 10],
+ pub oam_entries_len:u8,
+ pub rendering:bool,
+
+ fetcher_state_machine:FetcherStateMachine,
+ current_oam_entry:u8,
+}
+
+impl SpriteFetcher{
+ pub fn new()->Self{
+ let oam_entries:[SpriteAttribute; MAX_SPRITES_PER_LINE] = utils::create_array(|| SpriteAttribute::new(0,0,0,0));
+ let state_machine:[FetchingState;8] = [FetchingState::FetchTileNumber, FetchingState::FetchTileNumber, FetchingState::Sleep, FetchingState::FetchLowTile, FetchingState::Sleep, FetchingState::FetchHighTile, FetchingState::Sleep, FetchingState::Push];
+
+ SpriteFetcher{
+ fetcher_state_machine:FetcherStateMachine::new(state_machine),
+ current_oam_entry:0,
+ oam_entries_len:0,
+ oam_entries,
+ fifo:FixedSizeQueue::<(u8,u8), 8>::new(),
+ rendering:false,
+ }
+ }
+
+ pub fn reset(&mut self){
+ self.current_oam_entry = 0;
+ self.oam_entries_len = 0;
+ self.fetcher_state_machine.reset();
+ self.fifo.clear();
+ self.rendering = false;
+ }
+
+ pub fn fetch_pixels(&mut self, vram:&VRam, lcd_control:u8, ly_register:u8, current_x_pos:u8){
+ let sprite_size = if lcd_control & BIT_2_MASK == 0 {NORMAL_SPRITE_HIGHT} else{EXTENDED_SPRITE_HIGHT};
+
+ match self.fetcher_state_machine.current_state(){
+ FetchingState::FetchTileNumber=>{
+ self.try_fetch_tile_number(current_x_pos, lcd_control);
+ }
+ FetchingState::FetchLowTile=>{
+ let tile_num = self.fetcher_state_machine.data.tile_data.expect("State machine is corrupted, No Tile data on FetchLowTIle");
+ let oam_attribute = &self.oam_entries[self.current_oam_entry as usize];
+ let current_tile_data_address = Self::get_current_tile_data_address(ly_register, oam_attribute, sprite_size, tile_num);
+ let low_data = vram.read_current_bank(current_tile_data_address);
+ self.fetcher_state_machine.data.low_tile_data = Some(low_data);
+ self.fetcher_state_machine.advance();
+ }
+ FetchingState::FetchHighTile=>{
+ let tile_num= self.fetcher_state_machine.data.tile_data.expect("State machine is corrupted, No Tile data on FetchHighTIle");
+ let oam_attribute = &self.oam_entries[self.current_oam_entry as usize];
+ let current_tile_data_address = Self::get_current_tile_data_address(ly_register, oam_attribute, sprite_size, tile_num);
+ let high_data = vram.read_current_bank(current_tile_data_address + 1);
+ self.fetcher_state_machine.data.high_tile_data = Some(high_data);
+ self.fetcher_state_machine.advance();
+ }
+ FetchingState::Push=>{
+ let low_data = self.fetcher_state_machine.data.low_tile_data.expect("State machine is corrupted, No Low data on Push");
+ let high_data = self.fetcher_state_machine.data.high_tile_data.expect("State machine is corrupted, No High data on Push");
+ let oam_attribute = &self.oam_entries[self.current_oam_entry as usize];
+ let start_x = self.fifo.len();
+ let skip_x = 8 - (oam_attribute.x - current_x_pos) as usize;
+
+ if oam_attribute.flip_x{
+ for i in (0 + skip_x)..SPRITE_WIDTH as usize{
+ let pixel = Self::get_decoded_pixel(i, low_data, high_data);
+ if i + skip_x >= start_x {
+ self.fifo.push((pixel, self.current_oam_entry));
+ }
+ else if self.fifo[i + skip_x].0 == 0{
+ self.fifo[i+ skip_x] = (pixel, self.current_oam_entry);
+ }
+ }
+ }
+ else{
+ let fifo_max_index = FIFO_SIZE as usize - 1;
+ for i in (0..(SPRITE_WIDTH as usize - skip_x)).rev(){
+ let pixel = Self::get_decoded_pixel(i, low_data, high_data);
+ if fifo_max_index - skip_x - i >= start_x {
+ self.fifo.push((pixel, self.current_oam_entry));
+ }
+ else if self.fifo[fifo_max_index - skip_x - i].0 == 0{
+ self.fifo[fifo_max_index - skip_x - i] = (pixel, self.current_oam_entry);
+ }
+ }
+ }
+
+ self.current_oam_entry += 1;
+ self.fetcher_state_machine.advance();
+ }
+ FetchingState::Sleep=>self.fetcher_state_machine.advance()
+ }
+ }
+
+ //This is a function on order to abort if rendering
+ fn try_fetch_tile_number(&mut self, current_x_pos: u8, lcd_control: u8) {
+ if self.oam_entries_len > self.current_oam_entry{
+ let oam_entry = &self.oam_entries[self.current_oam_entry as usize];
+ if oam_entry.x <= current_x_pos + SPRITE_WIDTH && current_x_pos < oam_entry.x{
+ let mut tile_number = oam_entry.tile_number;
+ if lcd_control & BIT_2_MASK != 0{
+ tile_number &= !BIT_0_MASK
+ }
+ self.rendering = true;
+ self.fetcher_state_machine.data.reset();
+ self.fetcher_state_machine.data.tile_data = Some(tile_number);
+ self.fetcher_state_machine.advance();
+ return;
+ }
+ }
+ self.rendering = false;
+ }
+
+ // Receiving the tile_num since in case of extended sprite this could change (the first bit is reset)
+ fn get_current_tile_data_address(ly_register:u8, sprite_attrib:&SpriteAttribute, sprite_size:u8, tile_num:u8)->u16{
+ return if sprite_attrib.flip_y{
+ // Since Im flipping but dont know for what rect (8X8 or 8X16) I need sub this from the size (minus 1 casue im starting to count from 0 in the screen lines).
+ tile_num as u16 * 16 + (2 * (sprite_size - 1 - (16 - (sprite_attrib.y - ly_register)))) as u16
+ }
+ else{
+ // Since the sprite attribute y pos is the right most dot of the rect
+ // Im subtracting this from 16 (since the rects are 8X16)
+ tile_num as u16 * 16 + (2 * (16 - (sprite_attrib.y - ly_register))) as u16
+ };
+ }
+
+ fn get_decoded_pixel(index: usize, low_data: u8, high_data: u8) -> u8 {
+ let mask = 1 << index;
+ let mut pixel = (low_data & mask) >> index;
+ pixel |= ((high_data & mask) >> index) << 1;
+ pixel
+ }
+}
\ No newline at end of file
diff --git a/lib_gb/src/ppu/gb_ppu.rs b/lib_gb/src/ppu/gb_ppu.rs
index 5fa54ee7..4a5b2dd6 100644
--- a/lib_gb/src/ppu/gb_ppu.rs
+++ b/lib_gb/src/ppu/gb_ppu.rs
@@ -1,59 +1,37 @@
-use crate::mmu::memory::UnprotectedMemory;
-use super::ppu_state::PpuState;
-use super::color::Color;
-use super::colors::*;
-use crate::utils::vec2::Vec2;
-use super::colors::WHITE;
-use super::normal_sprite::NormalSprite;
-use super::extended_sprite::ExtendedSprite;
-use super::sprite::Sprite;
-use super::sprite_attribute::SpriteAttribute;
-use crate::utils::{
- memory_registers::*,
- bit_masks::*
-};
-use std::cmp;
+use crate::utils::{vec2::Vec2, bit_masks::*};
+use crate::mmu::vram::VRam;
+use crate::ppu::color::*;
+use crate::ppu::colors::*;
+use crate::ppu::gfx_device::GfxDevice;
+use crate::ppu::{ppu_state::PpuState, sprite_attribute::SpriteAttribute};
+
+use super::fifo::background_fetcher::BackgroundFetcher;
+use super::fifo::{FIFO_SIZE, sprite_fetcher::*};
pub const SCREEN_HEIGHT: usize = 144;
pub const SCREEN_WIDTH: usize = 160;
+pub const BUFFERS_NUMBER:usize = 2;
-//CPU frequrncy: 4,194,304 / 59.727~ / 4 == 70224 / 4
-pub const CYCLES_PER_FRAME:u32 = 17556;
-
-const OAM_CLOCKS:u8 = 20;
-const PIXEL_TRANSFER_CLOCKS:u8 = 43;
-const H_BLANK_CLOCKS:u8 = 51;
-const DRAWING_CYCLE_CLOCKS: u8 = OAM_CLOCKS + H_BLANK_CLOCKS + PIXEL_TRANSFER_CLOCKS;
-const LY_MAX_VALUE:u8 = 153;
-const OAM_ADDRESS:u16 = 0xFE00;
-const OAM_SIZE:u16 = 0xA0;
-const OBJ_PER_LINE:usize = 10;
-const SPRITE_WIDTH:u8 = 8;
-const NORMAL_SPRITE_HIEGHT:u8 = 8;
-const SPRITE_MAX_HEIGHT:u8 = 16;
-const BG_SPRITES_PER_LINE:u16 = 32;
-const SPRITE_SIZE_IN_MEMORY:u16 = 16;
+const OAM_ENTRY_SIZE:u16 = 4;
+const OAM_MEMORY_SIZE:usize = 0xA0;
-const BLANK_SCREEN_BUFFER:[u32; SCREEN_HEIGHT * SCREEN_WIDTH] = [GbPpu::color_as_uint(&WHITE);SCREEN_HEIGHT * SCREEN_WIDTH];
+const OAM_SEARCH_T_CYCLES_LENGTH: u16 = 80;
+const HBLANK_T_CYCLES_LENGTH: u16 = 456;
+const VBLANK_T_CYCLES_LENGTH: u16 = 4560;
-pub struct GbPpu {
- pub screen_buffer: [u32; SCREEN_HEIGHT*SCREEN_WIDTH],
- pub screen_enable: bool,
- pub window_enable: bool,
- pub sprite_extended: bool,
- pub background_enabled: bool,
- pub gbc_mode: bool,
- pub sprite_enable: bool,
- pub window_tile_map_address: bool,
- pub window_tile_background_map_data_address: bool,
- pub background_tile_map_address: bool,
- pub background_scroll: Vec2,
- pub window_scroll: Vec2,
+pub struct GbPpu{
+ pub vram: VRam,
+ pub oam:[u8;OAM_MEMORY_SIZE],
+ pub state:PpuState,
+ pub lcd_control:u8,
+ pub stat_register:u8,
+ pub lyc_register:u8,
+ pub ly_register:u8,
+ pub window_pos:Vec2,
+ pub bg_pos:Vec2,
pub bg_color_mapping: [Color; 4],
pub obj_color_mapping0: [Option;4],
pub obj_color_mapping1: [Option;4],
- pub current_line_drawn: u8,
- pub state:PpuState,
//interrupts
pub v_blank_interrupt_request:bool,
@@ -61,449 +39,270 @@ pub struct GbPpu {
pub oam_search_interrupt_request:bool,
pub coincidence_interrupt_request:bool,
- window_active:bool,
- window_line_counter:u8,
- line_rendered:bool,
- current_cycle:u32,
- last_screen_state:bool,
- v_blank_triggered:bool,
- stat_triggered:bool
+ gfx_device: GFX,
+ t_cycles_passed:u16,
+ screen_buffers: [[u32; SCREEN_HEIGHT * SCREEN_WIDTH];BUFFERS_NUMBER],
+ current_screen_buffer_index:usize,
+ push_lcd_buffer:Vec,
+ screen_buffer_index:usize,
+ pixel_x_pos:u8,
+ scanline_started:bool,
+ bg_fetcher:BackgroundFetcher,
+ sprite_fetcher:SpriteFetcher,
+ stat_triggered:bool,
+ trigger_stat_interrupt:bool,
}
-impl Default for GbPpu {
- fn default() -> Self {
- GbPpu {
- background_enabled: false,
- background_scroll: Vec2:: { x: 0, y: 0 },
- window_scroll: Vec2:: { x: 0, y: 0 },
- background_tile_map_address: false,
- gbc_mode: false,
- screen_buffer: [0; SCREEN_HEIGHT*SCREEN_WIDTH],
- screen_enable: false,
- sprite_enable: false,
- sprite_extended: false,
- window_enable: false,
- window_tile_background_map_data_address: false,
- window_tile_map_address: false,
- bg_color_mapping: [WHITE, LIGHT_GRAY, DARK_GRAY, BLACK],
+impl GbPpu{
+ pub fn new(device:GFX) -> Self {
+ Self{
+ gfx_device: device,
+ vram: VRam::default(),
+ oam: [0;OAM_MEMORY_SIZE],
+ stat_register: 0,
+ lyc_register: 0,
+ lcd_control: 0,
+ bg_pos: Vec2::{x:0, y:0},
+ window_pos: Vec2::{x:0,y:0},
+ screen_buffers:[[0;SCREEN_HEIGHT * SCREEN_WIDTH];BUFFERS_NUMBER],
+ current_screen_buffer_index:0,
+ bg_color_mapping:[WHITE, LIGHT_GRAY, DARK_GRAY, BLACK],
obj_color_mapping0: [None, Some(LIGHT_GRAY), Some(DARK_GRAY), Some(BLACK)],
obj_color_mapping1: [None, Some(LIGHT_GRAY), Some(DARK_GRAY), Some(BLACK)],
- current_line_drawn:0,
- state:PpuState::OamSearch,
- line_rendered:false,
- window_line_counter:0,
- window_active:false,
- current_cycle:0,
- last_screen_state:true,
- v_blank_triggered:false,
- stat_triggered:false,
+ ly_register:0,
+ state: PpuState::Hblank,
//interrupts
- v_blank_interrupt_request:false,
+ v_blank_interrupt_request:false,
h_blank_interrupt_request:false,
- oam_search_interrupt_request:false,
- coincidence_interrupt_request:false
+ oam_search_interrupt_request:false,
+ coincidence_interrupt_request:false,
+ screen_buffer_index:0,
+ t_cycles_passed:0,
+ stat_triggered:false,
+ trigger_stat_interrupt:false,
+ bg_fetcher:BackgroundFetcher::new(),
+ sprite_fetcher:SpriteFetcher::new(),
+ push_lcd_buffer:Vec::