commit 12f97b8d5bec289f25db2d0976038ab4d6ec44a5 Author: EliseZeroTwo Date: Wed Nov 24 12:01:25 2021 +0100 feat: basic ppu diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..335122a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/roms/ +config.toml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ccc7b85 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1913 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "andrew" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4afb09dd642feec8408e33f92f3ffc4052946f6b20f32fb99c1f58cd4fa7cf" +dependencies = [ + "bitflags", + "rusttype", + "walkdir", + "xdg", + "xml-rs", +] + +[[package]] +name = "argh" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f023c76cd7975f9969f8e29f0e461decbdc7f51048ce43427107a3d192f1c9bf" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ad219abc0c06ca788aface2e3a1970587e3413ab70acd20e54b6ec524c1f8f" +dependencies = [ + "argh_shared", + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "argh_shared" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38de00daab4eac7d753e97697066238d67ce9d7e2d823ab4f72fe14af29f3f33" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "ash" +version = "0.33.3+1.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4f1d82f164f838ae413296d1131aa6fa79b917d25bebaa7033d25620c09219" +dependencies = [ + "libloading 0.7.2", +] + +[[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 = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + +[[package]] +name = "bytemuck" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "calloop" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b036167e76041694579972c28cf4877b4f92da222560ddb49008937b6a6727c" +dependencies = [ + "log", + "nix 0.18.0", +] + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + +[[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 = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[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 0.2.14", + "time", + "winapi", +] + +[[package]] +name = "cocoa" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation 0.9.2", + "core-graphics 0.22.3", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation 0.9.2", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "config" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" +dependencies = [ + "lazy_static", + "nom 5.1.2", + "rust-ini", + "serde 1.0.130", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +dependencies = [ + "core-foundation-sys 0.8.3", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation 0.9.2", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation 0.9.2", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "objc", +] + +[[package]] +name = "crossbeam" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[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-queue" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[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 = "d3d12" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c" +dependencies = [ + "bitflags", + "libloading 0.7.2", + "winapi", +] + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deemgee" +version = "0.1.0" +dependencies = [ + "argh", + "chrono", + "config", + "env_logger", + "log", + "paste", + "pixels", + "serde 1.0.130", + "sha1", + "thiserror", + "winit", + "winit_input_helper", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76" +dependencies = [ + "libloading 0.6.7", +] + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading 0.7.2", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[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 = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[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 = "glow" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f04649123493bc2483cbef4daddb45d40bbdae5adb221a63a23efdb0cc99520" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gpu-alloc" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e64cbb8d36508d3e19da95e56e196a84f674fc190881f2cc010000798838aa6" +dependencies = [ + "bitflags", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "inplace_it" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[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 = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading 0.7.2", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec 0.5.2", + "bitflags", + "cfg-if 1.0.0", + "ryu", + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" + +[[package]] +name = "libloading" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[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 = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memmap2" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b70ca2a6103ac8b665dc150b142ef0e4e89df640c9e6cf295d189c3caebe5a" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0514f491f4cc03632ab399ee01e2c1c1b12d3e1cf2d667c1ff5f87d6dcd2084" +dependencies = [ + "bitflags", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "mio-misc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ddf05411bb159cdb5801bb10002afb66cb4572be656044315e363460ce69dc2" +dependencies = [ + "crossbeam", + "crossbeam-queue", + "log", + "mio", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "naga" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda66d09f712e1f0a6ab436137da4fac312f78301f6d4ac7cb8bfe96e988734f" +dependencies = [ + "bit-set", + "bitflags", + "codespan-reporting", + "fxhash", + "hexf-parse", + "indexmap", + "log", + "num-traits 0.2.14", + "spirv", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8794322172319b972f528bf90c6b467be0079f1fa82780ffb431088e741a73ab" +dependencies = [ + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-glue" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5caf0c24d51ac1c905c27d4eda4fa0635bbe0de596b8f79235e0b17a4d29385" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" +dependencies = [ + "darling", + "proc-macro-crate 0.1.5", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" + +[[package]] +name = "nix" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055" +dependencies = [ + "bitflags", + "cc", + "cfg-if 0.1.10", + "libc", +] + +[[package]] +name = "nix" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + +[[package]] +name = "nom" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + +[[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 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", +] + +[[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_enum" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" +dependencies = [ + "derivative", + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" +dependencies = [ + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "owned_ttf_parser" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pixels" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ab1e297051c39cc7b7511e7e2b3ab151f14aff6a44e73bdde652b1e2190950" +dependencies = [ + "bytemuck", + "pollster", + "raw-window-handle", + "thiserror", + "ultraviolet", + "wgpu", +] + +[[package]] +name = "pkg-config" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" + +[[package]] +name = "pollster" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb20dcc30536a1508e75d47dd0e399bb2fe7354dcf35cda9127f2bf1ed92e30e" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "profiling" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9926767b8b8244d7b6b64546585121d193c3d0b4856ccd656b7bfa9deb91ab6a" + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "range-alloc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" + +[[package]] +name = "raw-window-handle" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" +dependencies = [ + "libc", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[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-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + +[[package]] +name = "rusttype" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "safe_arch" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + +[[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.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" +dependencies = [ + "itoa", + "ryu", + "serde 1.0.130", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" + +[[package]] +name = "smithay-client-toolkit" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4750c76fd5d3ac95fa3ed80fe667d6a3d8590a960e5b575b98eea93339a80b80" +dependencies = [ + "andrew", + "bitflags", + "calloop", + "dlib 0.4.2", + "lazy_static", + "log", + "memmap2", + "nix 0.18.0", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "spirv" +version = "0.2.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags", + "num-traits 0.2.14", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +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 = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde 1.0.130", +] + +[[package]] +name = "ttf-parser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" + +[[package]] +name = "ultraviolet" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b9e3507eba17043af05c8a72fce3ec2c24b58945f45732e71dbc6646d904a7" +dependencies = [ + "wide", +] + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[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 = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[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 = "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", + "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 = "wayland-client" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ab332350e502f159382201394a78e3cc12d0f04db863429260164ea40e0355" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.20.0", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21817947c7011bbd0a27e11b17b337bfd022e8544b071a2641232047966fbda" +dependencies = [ + "nix 0.20.0", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be610084edd1586d45e7bdd275fe345c7c1873598caa464c4fb835dee70fa65a" +dependencies = [ + "nix 0.20.0", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "286620ea4d803bacf61fa087a4242ee316693099ee5a140796aaba02b29f861f" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce923eb2deb61de332d1f356ec7b6bf37094dc5573952e1c8936db03b54c03f1" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d841fca9aed7febf9bed2e9796c49bf58d4152ceda8ac949ebe00868d8f0feb8" +dependencies = [ + "dlib 0.5.0", + "lazy_static", + "pkg-config", +] + +[[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 = "wgpu" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1577ecc4f6992b9e965878ac594efb24eed2bdf089c11f45b3d1c5f216e2e30" +dependencies = [ + "arrayvec 0.7.2", + "js-sys", + "log", + "parking_lot", + "raw-window-handle", + "smallvec", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bdcbfa4885b32c2b1feb2faeb8b6a76065b752b8f08751b82f994e937687f46" +dependencies = [ + "arrayvec 0.7.2", + "bitflags", + "cfg_aliases", + "copyless", + "fxhash", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "thiserror", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e493835d9edb153d5c8a9d8d016e1811dbe32ddb707a110be1453c7b051d3ec" +dependencies = [ + "arrayvec 0.7.2", + "ash", + "bit-set", + "bitflags", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "js-sys", + "khronos-egl", + "libloading 0.7.2", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e15e44ba88ec415466e18e91881319e7c9e96cb905dc623305168aea65b85ccc" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wide" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bbe7c604a27ca0b05c5503221e76da628225b568e6f1280b42dbad3b72d89b" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[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 = "winit" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79610794594d5e86be473ef7763f604f2159cbac8c94debd00df8fb41e86c2f8" +dependencies = [ + "bitflags", + "cocoa", + "core-foundation 0.9.2", + "core-graphics 0.22.3", + "core-video-sys", + "dispatch", + "instant", + "lazy_static", + "libc", + "log", + "mio", + "mio-misc", + "ndk", + "ndk-glue", + "ndk-sys", + "objc", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "scopeguard", + "serde 1.0.130", + "smithay-client-toolkit", + "wayland-client", + "winapi", + "x11-dl", +] + +[[package]] +name = "winit_input_helper" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8af691f04e6d8a892e80a2176221b2c13d5832a8929d8c0fed1e3e3d4fe831" +dependencies = [ + "winit", +] + +[[package]] +name = "x11-dl" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom 7.1.0", +] + +[[package]] +name = "xdg" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a23fe958c70412687039c86f578938b4a0bb50ec788e96bce4d6ab00ddd5803" +dependencies = [ + "dirs", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9fa813f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "deemgee" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +argh = "0.1.6" +chrono = "0.4.19" +config = "0.11.0" +env_logger = "0.9.0" +log = "0.4.14" +paste = "1.0.6" +pixels = "0.7.0" +serde = { version = "1.0.130", features = ["derive"] } +sha1 = { version = "0.6.0", features = ["std"] } +thiserror = "1.0.30" +winit = { version = "0.25.0", features = ["serde"] } +winit_input_helper = "0.10.0" diff --git a/config.example.toml b/config.example.toml new file mode 100644 index 0000000..c86f3db --- /dev/null +++ b/config.example.toml @@ -0,0 +1,11 @@ +[bindings] +a = "A" +b = "S" +select = "Q" +start = "W" +up = "Up" +down = "Down" +left = "Left" +right = "Right" +pause = "P" +exit = "Escape" \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..11a295d --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,7 @@ +hard_tabs=true +max_width = 100 +comment_width = 80 +wrap_comments = true +imports_granularity = "Crate" +use_small_heuristics = "Max" +group_imports = "StdExternalCrate" diff --git a/src/gameboy.rs b/src/gameboy.rs new file mode 100644 index 0000000..25c26e1 --- /dev/null +++ b/src/gameboy.rs @@ -0,0 +1,232 @@ +mod interrupts; +mod joypad; +mod mapper; +mod memory; +mod ppu; +mod timer; + +use interrupts::Interrupts; +use joypad::Joypad; +use mapper::Mapper; +use memory::Memory; +use ppu::Ppu; +use timer::Timer; + +pub struct DmaState { + pub base: u8, + pub remaining_cycles: u8, + pub remaining_delay: u8, +} + +impl DmaState { + pub fn new() -> Self { + Self { base: 0, remaining_cycles: 0, remaining_delay: 0 } + } + + pub fn init_request(&mut self, base: u8) { + self.base = base; + self.remaining_cycles = 0xA0; + self.remaining_delay = 2; + } +} + +pub struct Gameboy { + pub ppu: Ppu, + memory: Memory, + cartridge: Option>, + interrupts: Interrupts, + timer: Timer, + pub joypad: Joypad, + pub dma: DmaState, +} + +impl Gameboy { + pub fn new(bootrom: [u8; 0x100]) -> Self { + Self { + memory: Memory::new(bootrom), + cartridge: None, + interrupts: Interrupts::new(), + timer: Timer::new(), + joypad: Joypad::new(), + dma: DmaState::new(), + ppu: Ppu::new(), + } + } + + pub fn tick(&mut self) -> bool { + if self.timer.tick() { + self.interrupts.write_if_timer(true); + } + + self.tick_cpu(); + let redraw_requested = self.ppu.tick(&mut self.interrupts); + self.tick_dma(); + redraw_requested + } + + fn tick_cpu(&mut self) {} + + fn tick_dma(&mut self) { + if self.dma.remaining_delay > 0 { + self.dma.remaining_delay -= 1; + } else if self.dma.remaining_cycles > 0 { + let offset = 0xA0 - self.dma.remaining_cycles; + + let value = if self.dma.base <= 0x7F { + match self.cartridge.as_ref() { + Some(cart) => cart.read_rom_u8((self.dma.base as u16) << 8 | offset as u16), + None => 0xFF, + } + } else if self.dma.base <= 0x9F { + self.ppu.dma_read_vram(offset) + } else { + 0xFF + }; + + self.ppu.dma_write_oam(offset, value); + self.dma.remaining_cycles -= 1; + } + } + + fn cpu_read_io(&self, address: u16) -> u8 { + match address { + 0xFF00 => self.joypad.cpu_read(), + 0xFF01..=0xFF02 => unimplemented!("Serial"), + 0xFF03 => 0, // Unused + 0xFF04 => self.timer.div, + 0xFF05 => self.timer.tima, + 0xFF06 => self.timer.tma, + 0xFF07 => self.timer.read_tac(), + 0xFF08..=0xFF0E => 0, // Unused + 0xFF0F => self.interrupts.interrupt_enable, + 0xFF10..=0xFF3F => unimplemented!("Sound IO"), + 0xFF40 => self.ppu.lcdc, + 0xFF41 => self.ppu.stat, + 0xFF42 => self.ppu.scy, + 0xFF43 => self.ppu.scx, + 0xFF44 => self.ppu.ly, + 0xFF45 => self.ppu.lyc, + 0xFF46 => self.dma.base, + 0xFF47..=0xFF49 => 0, + 0xFF4A => self.ppu.wy, + 0xFF4B => self.ppu.wx, + 0xFF4C..=0xFF4E => 0, // Unused + 0xFF4F => 0, // CGB VRAM Bank Select + 0xFF50 => self.memory.bootrom_disabled as u8, + 0xFF51..=0xFF55 => 0, // CGB VRAM DMA + 0xFF56..=0xFF67 => 0, // Unused + 0xFF68..=0xFF69 => 0, // BJ/OBJ Palettes + 0xFF6A..=0xFF6F => 0, // Unused + 0xFF70 => 0, // CGB WRAM Bank Select + 0xFF71..=0xFF7F => 0, // Unused + _ => unreachable!("IO Read Invalid"), + } + } + + fn cpu_write_io(&mut self, address: u16, value: u8) { + match address { + 0xFF00 => self.joypad.cpu_write(value), + 0xFF01..=0xFF02 => unimplemented!("Serial"), + 0xFF03 => {} // Unused + 0xFF04 => self.timer.div = value, + 0xFF05 => self.timer.tima = value, + 0xFF06 => self.timer.tma = value, + 0xFF07 => self.timer.write_tac(value), + 0xFF08..=0xFF0E => {} // Unused + 0xFF0F => self.interrupts.interrupt_enable = value & 0b1_1111, + 0xFF10..=0xFF3F => unimplemented!("Sound IO"), + 0xFF40 => self.ppu.lcdc = value, + 0xFF41 => self.ppu.cpu_write_stat(value), + 0xFF42 => self.ppu.scy = value, + 0xFF43 => self.ppu.scx = value, + 0xFF44 => {} // LY is read only + 0xFF45 => self.ppu.lyc = value, + 0xFF46 => { + if self.dma.remaining_cycles == 0 { + self.dma.init_request(value); + } + } + 0xFF47..=0xFF49 => {} + 0xFF4A => self.ppu.wy = value, + 0xFF4B => self.ppu.wx = value, + 0xFF4C..=0xFF4E => {} // Unused + 0xFF4F => {} // CGB VRAM Bank Select + 0xFF50 => { + if value & 0b1 == 1 { + self.memory.bootrom_disabled = true; + } + } + 0xFF51..=0xFF55 => {} // CGB VRAM DMA + 0xFF56..=0xFF67 => {} // Unused + 0xFF68..=0xFF69 => {} // CGB BG/OBJ Palettes + 0xFF6A..=0xFF6F => {} // Unused + 0xFF70 => {} // CGB WRAM Bank Select + 0xFF71..=0xFF7F => {} // Unused + _ => unreachable!("IO Read Invalid"), + } + } + + pub fn cpu_read_u8(&self, address: u16) -> u8 { + if self.dma.remaining_cycles == 0 { + match address { + 0..=0xFF if !self.memory.bootrom_disabled => self.memory.bootrom[address as usize], + 0..=0x7FFF => match self.cartridge.as_ref() { + Some(mapper) => mapper.read_rom_u8(address), + None => 0, + }, + 0x8000..=0x9FFF => self.ppu.cpu_read_vram(address), + 0xA000..=0xBFFF => match self.cartridge.as_ref() { + Some(mapper) => mapper.read_eram_u8(address), + None => 0, + }, + 0xC000..=0xDFFF => self.memory.wram[address as usize - 0xC000], + 0xE000..=0xFDFF => self.memory.wram[address as usize - 0xE000], + 0xFE00..=0xFE9F => self.ppu.cpu_read_oam(address), + 0xFEA0..=0xFEFF => 0, + 0xFF00..=0xFF7F => self.cpu_read_io(address), + 0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80], + 0xFFFF => self.interrupts.interrupt_enable, + } + } else { + match address { + 0..=0xFEFF => 0, + 0xFF00..=0xFF7F => self.cpu_read_io(address), + 0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80], + 0xFFFF => self.interrupts.interrupt_enable, + } + } + } + + pub fn cpu_write_u8(&mut self, address: u16, value: u8) { + if self.dma.remaining_cycles == 0 { + match address { + 0..=0xFF if !self.memory.bootrom_disabled => {} + 0..=0x7FFF => { + if let Some(mapper) = self.cartridge.as_mut() { + mapper.write_rom_u8(address, value) + } + } + 0x8000..=0x9FFF => self.ppu.cpu_write_vram(address, value), + 0xA000..=0xBFFF => { + if let Some(mapper) = self.cartridge.as_mut() { + mapper.write_eram_u8(address, value) + } + } + 0xC000..=0xDFFF => self.memory.wram[address as usize - 0xC000] = value, + 0xE000..=0xFDFF => self.memory.wram[address as usize - 0xE000] = value, + 0xFE00..=0xFE9F => self.ppu.cpu_write_oam(address, value), + 0xFEA0..=0xFEFF => {} + 0xFF00..=0xFF7F => self.cpu_write_io(address, value), + 0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80] = value, + 0xFFFF => self.interrupts.interrupt_enable = value & 0b1_1111, + } + } else { + match address { + 0..=0xFEFF => {} + 0xFF00..=0xFF7F => self.cpu_write_io(address, value), + 0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80] = value, + 0xFFFF => self.interrupts.interrupt_enable = value & 0b1_1111, + } + } + } +} diff --git a/src/gameboy/interrupts.rs b/src/gameboy/interrupts.rs new file mode 100644 index 0000000..ebe996a --- /dev/null +++ b/src/gameboy/interrupts.rs @@ -0,0 +1,38 @@ +macro_rules! define_bitfield_u8_gs { + ($name:ident, $offset:literal, $loc:ident) => { + paste::paste! { + pub fn [](&self) -> bool { + ((self.$loc >> $offset) & 0b1) == 1 + } + + pub fn [](&mut self, value: bool) { + log::debug!(std::concat!("Setting ", std::stringify!($name), " to {}"), value); + self.$loc &= !(0b1 << $offset); + self.$loc |= (value as u8) << $offset; + } + } + }; +} + +pub struct Interrupts { + pub ime: bool, + pub interrupt_enable: u8, + pub interrupt_flag: u8, +} + +impl Interrupts { + pub fn new() -> Self { + Self { ime: true, interrupt_enable: 0b1_1111, interrupt_flag: 0b0_0000 } + } + + define_bitfield_u8_gs!(ie_vblank, 0, interrupt_enable); + define_bitfield_u8_gs!(ie_lcd_stat, 1, interrupt_enable); + define_bitfield_u8_gs!(ie_timer, 2, interrupt_enable); + define_bitfield_u8_gs!(ie_serial, 3, interrupt_enable); + define_bitfield_u8_gs!(ie_joypad, 4, interrupt_enable); + define_bitfield_u8_gs!(if_vblank, 0, interrupt_flag); + define_bitfield_u8_gs!(if_lcd_stat, 1, interrupt_flag); + define_bitfield_u8_gs!(if_timer, 2, interrupt_flag); + define_bitfield_u8_gs!(if_serial, 3, interrupt_flag); + define_bitfield_u8_gs!(if_joypad, 4, interrupt_flag); +} diff --git a/src/gameboy/joypad.rs b/src/gameboy/joypad.rs new file mode 100644 index 0000000..e55d4ce --- /dev/null +++ b/src/gameboy/joypad.rs @@ -0,0 +1,62 @@ +#[derive(Debug, PartialEq, Eq)] +pub enum JoypadMode { + Action, + Direction, +} + +pub struct Joypad { + mode: JoypadMode, + pub down: bool, + pub up: bool, + pub left: bool, + pub right: bool, + pub start: bool, + pub select: bool, + pub b: bool, + pub a: bool, +} + +impl Joypad { + pub fn new() -> Self { + Self { + mode: JoypadMode::Action, + down: false, + up: false, + left: false, + right: false, + start: false, + select: false, + b: false, + a: false, + } + } + + pub fn cpu_read(&self) -> u8 { + match self.mode { + JoypadMode::Action => { + (1 << 4) + | ((!self.start as u8) << 3) + | ((!self.select as u8) << 2) + | ((!self.b as u8) << 1) + | (!self.a as u8) + } + JoypadMode::Direction => { + (1 << 5) + | ((!self.down as u8) << 3) + | ((!self.up as u8) << 2) + | ((!self.left as u8) << 1) + | (!self.right as u8) + } + } + } + + pub fn cpu_write(&mut self, content: u8) { + if (content >> 5) & 0b1 == 0 { + self.mode = JoypadMode::Action; + } + + if (content >> 4) & 0b1 == 0 { + self.mode = JoypadMode::Direction; + } + } +} diff --git a/src/gameboy/mapper.rs b/src/gameboy/mapper.rs new file mode 100644 index 0000000..59e8dea --- /dev/null +++ b/src/gameboy/mapper.rs @@ -0,0 +1,37 @@ +pub trait Mapper { + fn read_rom_u8(&self, address: u16) -> u8; + fn write_rom_u8(&mut self, address: u16, value: u8); + + fn read_eram_u8(&self, address: u16) -> u8; + fn write_eram_u8(&mut self, address: u16, value: u8); +} + +pub struct NoMBC { + rom: [u8; 0x8000], + ram: Option<[u8; 0x2000]>, +} + +impl Mapper for NoMBC { + fn read_rom_u8(&self, address: u16) -> u8 { + self.rom[address as usize] + } + + fn write_rom_u8(&mut self, address: u16, value: u8) { + self.rom[address as usize] = value + } + + fn read_eram_u8(&self, address: u16) -> u8 { + let decoded_address = address - 0xA000; + match &self.ram { + Some(ram) => ram[decoded_address as usize], + None => 0, + } + } + + fn write_eram_u8(&mut self, address: u16, value: u8) { + let decoded_address = address - 0xA000; + if let Some(ram) = &mut self.ram { + ram[decoded_address as usize] = value; + } + } +} diff --git a/src/gameboy/memory.rs b/src/gameboy/memory.rs new file mode 100644 index 0000000..4a4b772 --- /dev/null +++ b/src/gameboy/memory.rs @@ -0,0 +1,13 @@ +pub struct Memory { + pub wram: [u8; 0x2000], + pub hram: [u8; 0xAF], + + pub bootrom_disabled: bool, + pub bootrom: [u8; 0x100], +} + +impl Memory { + pub fn new(bootrom: [u8; 0x100]) -> Self { + Self { wram: [0; 0x2000], hram: [0; 0xAF], bootrom, bootrom_disabled: false } + } +} diff --git a/src/gameboy/ppu.rs b/src/gameboy/ppu.rs new file mode 100644 index 0000000..e89851a --- /dev/null +++ b/src/gameboy/ppu.rs @@ -0,0 +1,365 @@ +use std::ops::{Index, IndexMut}; + +use crate::{gameboy::Interrupts, window::FB_WIDTH}; + +pub struct WrappedBuffer([u8; SIZE]); + +impl Index for WrappedBuffer { + type Output = u8; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index % SIZE] + } +} + +impl IndexMut for WrappedBuffer { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index % SIZE] + } +} + +impl WrappedBuffer { + pub fn empty() -> Self { + Self([0; SIZE]) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum PPUMode { + HBlank, + VBlank, + SearchingOAM, + TransferringData, +} + +#[derive(Debug, Clone, Copy)] +pub enum Color { + White, + LGray, + DGray, + Black, + Transparent, +} + +impl Color { + pub fn rgba(self) -> &'static [u8; 4] { + match self { + Color::White => &[0x7F, 0x86, 0x0F, 0xFF], + Color::LGray => &[0x57, 0x7c, 0x44, 0xFF], + Color::DGray => &[0x36, 0x5d, 0x48, 0xFF], + Color::Black => &[0x2a, 0x45, 0x3b, 0xFF], + Color::Transparent => &[0x00, 0x00, 0x00, 0x00], + } + } + + pub fn parse_bgp_color(color: u8) -> Self { + match color & 0b11 { + 0 => Self::White, + 1 => Self::LGray, + 2 => Self::DGray, + 3 => Self::Black, + _ => unreachable!(), + } + } + + pub fn parse_obp_color(color: u8) -> Self { + match color & 0b11 { + 0 => Self::Transparent, + 1 => Self::LGray, + 2 => Self::DGray, + 3 => Self::Black, + _ => unreachable!(), + } + } + + pub fn parse_bgp(mut bgp: u8) -> [Self; 4] { + let mut out = [Self::White, Self::White, Self::White, Self::White]; + for color in &mut out { + *color = Self::parse_bgp_color(bgp); + bgp >>= 2; + } + out.reverse(); + out + } + + pub fn parse_obp(mut obp: u8) -> [Self; 4] { + let mut out = [Self::Transparent, Self::Transparent, Self::Transparent, Self::Transparent]; + for color in &mut out { + *color = Self::parse_obp_color(obp); + obp >>= 2; + } + out.reverse(); + out + } +} + +impl PPUMode { + pub fn mode_flag(&self) -> u8 { + match self { + PPUMode::HBlank => 0, + PPUMode::VBlank => 1, + PPUMode::SearchingOAM => 2, + PPUMode::TransferringData => 3, + } + } + + pub fn from_mode_flag(value: u8) -> Self { + match value & 0b11 { + 0 => Self::HBlank, + 1 => Self::VBlank, + 2 => Self::SearchingOAM, + 3 => Self::TransferringData, + _ => unreachable!(), + } + } +} + +pub struct Ppu { + pub lcdc: u8, + pub stat: u8, + pub scy: u8, + pub scx: u8, + pub ly: u8, + pub lyc: u8, + pub wy: u8, + pub wx: u8, + pub vram: [u8; 0x2000], + pub oam: [u8; 0xA0], + pub cycle_counter: u16, + pub bgp: u8, + pub obp: u8, + + pub framebuffer: WrappedBuffer<{ 160 * 144 * 4 }>, + pub sprite_framebuffer: WrappedBuffer<{ 160 * 144 * 4 }>, +} + +impl Ppu { + pub fn new() -> Self { + Self { + lcdc: 0b1000_0000, + stat: 0b0000_0010, + scy: 0, + scx: 0, + ly: 0, + lyc: 0, + wy: 0, + wx: 0, + vram: [0; 0x2000], + oam: [0; 0xA0], + cycle_counter: 0, + framebuffer: WrappedBuffer::empty(), + sprite_framebuffer: WrappedBuffer::empty(), + bgp: 0, + obp: 0, + } + } + + fn set_scanline(&mut self, interrupts: &mut Interrupts, scanline: u8) { + self.ly = scanline; + + self.stat &= !(1 << 2); + if self.ly == self.lyc { + self.stat |= 1 << 2; + + if (self.stat >> 6) & 0b1 == 1 { + interrupts.write_if_lcd_stat(true); + } + } + } + + fn draw_line(&mut self) { + for pixel_idx in 0..FB_WIDTH as u8 { + let scrolled_x = pixel_idx.overflowing_add(self.scx).0 as usize; + let scrolled_y = self.ly.overflowing_add(self.scy).0 as usize; + let tilemap_idx = scrolled_x / 8 + ((scrolled_y as usize / 8) * 32); + let tilemap_value = self.read_tile_map()[tilemap_idx]; + + let color = Self::parse_tile_color( + self.read_tile(tilemap_value), + scrolled_x % 8, + scrolled_y % 8, + ); + let dest_idx_base = ((self.scy as usize * FB_WIDTH as usize) + pixel_idx as usize) * 4; + for (idx, byte) in color.rgba().iter().enumerate() { + self.framebuffer[dest_idx_base + idx] = *byte; + } + } + } + + fn parse_tile_color(tile: &[u8], x: usize, y: usize) -> Color { + assert!(x < 8); + if x < 4 { + let bitshift = 6 - x * 2; + Color::parse_bgp_color(tile[y * 2] >> bitshift) + } else { + let x = x - 4; + let bitshift = 6 - x * 2; + Color::parse_bgp_color(tile[(y * 2) + 1] >> bitshift) + } + } + + fn set_mode(&mut self, interrupts: &mut Interrupts, mode: PPUMode) { + log::debug!("PPU switching mode to {:?} @ {}", mode, self.cycle_counter); + self.stat &= !0b11; + self.stat |= mode.mode_flag(); + self.cycle_counter = 0; + + let offset = match mode { + PPUMode::HBlank => 3, + PPUMode::VBlank => 4, + PPUMode::SearchingOAM => 5, + _ => return, + }; + + if (self.stat >> offset) & 0b1 == 1 { + interrupts.write_if_lcd_stat(true); + } + + if mode == PPUMode::VBlank { + interrupts.write_if_vblank(true); + } + } + + pub fn write_fb(&self) -> Vec { + let mut out = self.framebuffer.0.to_vec(); + + for x in 0..(160 * 144) { + let idx = x * 4; + + let (r, g, b, a) = ( + self.sprite_framebuffer[idx], + self.sprite_framebuffer[idx + 1], + self.sprite_framebuffer[idx + 2], + self.sprite_framebuffer[idx + 3], + ); + + if r != 0 || g != 0 || b != 0 || a != 0 { + out[idx] = r; + out[idx + 1] = g; + out[idx + 2] = b; + out[idx + 3] = a; + } + } + + out + } + + pub fn tick(&mut self, interrupts: &mut Interrupts) -> bool { + let res = match self.mode() { + PPUMode::HBlank => { + if self.cycle_counter >= 120 { + self.set_scanline(interrupts, self.ly + 1); + + let next_mode = match self.ly > 143 { + true => PPUMode::VBlank, + false => PPUMode::SearchingOAM, + }; + self.set_mode(interrupts, next_mode); + } + false + } + PPUMode::VBlank => { + if self.cycle_counter % 506 == 0 { + if self.ly >= 153 { + self.set_scanline(interrupts, 0); + self.set_mode(interrupts, PPUMode::SearchingOAM); + true + } else { + self.set_scanline(interrupts, self.ly + 1); + false + } + } else { + false + } + } + PPUMode::SearchingOAM => { + if self.cycle_counter >= 80 { + self.set_mode(interrupts, PPUMode::TransferringData); + } + false + } + PPUMode::TransferringData => { + if self.cycle_counter >= 170 { + self.draw_line(); + self.set_mode(interrupts, PPUMode::HBlank); + } + false + } + }; + + self.cycle_counter += 1; + + res + } + + pub fn mode(&self) -> PPUMode { + PPUMode::from_mode_flag(self.stat) + } + + pub fn cpu_read_oam(&self, address: u16) -> u8 { + let decoded_address = address - 0xFE00; + match self.mode() { + PPUMode::HBlank | PPUMode::VBlank => self.oam[decoded_address as usize], + PPUMode::SearchingOAM | PPUMode::TransferringData => 0xFF, + } + } + + pub fn dma_write_oam(&mut self, offset: u8, value: u8) { + self.oam[offset as usize] = value; + } + + pub fn cpu_write_oam(&mut self, address: u16, value: u8) { + let decoded_address = address - 0xFE00; + match self.mode() { + PPUMode::HBlank | PPUMode::VBlank => self.oam[decoded_address as usize] = value, + _ => {} + } + } + + pub fn dma_read_vram(&mut self, offset: u8) -> u8 { + self.vram[offset as usize] + } + + pub fn cpu_read_vram(&self, address: u16) -> u8 { + let decoded_address = address - 0x8000; + match self.mode() { + PPUMode::HBlank | PPUMode::VBlank | PPUMode::SearchingOAM => { + self.vram[decoded_address as usize] + } + PPUMode::TransferringData => 0xFF, + } + } + + pub fn cpu_write_vram(&mut self, address: u16, value: u8) { + let decoded_address = address - 0x8000; + match self.mode() { + PPUMode::HBlank | PPUMode::VBlank | PPUMode::SearchingOAM => { + self.vram[decoded_address as usize] = value + } + _ => {} + } + } + + pub fn cpu_write_stat(&mut self, value: u8) { + self.stat = value & 0b0111_1000; + } + + pub fn read_tile(&self, obj: u8) -> &[u8] { + if (self.lcdc >> 4) & 0b1 == 1 { + &self.vram[obj as usize * 16..((obj as usize + 1) * 16)] + } else if obj < 128 { + &self.vram[0x1000 + (obj as usize * 16)..0x1000 + ((obj as usize + 1) * 16)] + } else { + let adjusted_obj = obj - 128; + &self.vram + [0x800 + (adjusted_obj as usize * 16)..0x800 + ((adjusted_obj as usize + 1) * 16)] + } + } + + pub fn read_tile_map(&self) -> &[u8] { + match (self.lcdc >> 3) & 0b1 == 1 { + true => &self.vram[0x1C00..=0x1FFF], + false => &self.vram[0x1800..=0x1BFF], + } + } +} diff --git a/src/gameboy/timer.rs b/src/gameboy/timer.rs new file mode 100644 index 0000000..3310ce8 --- /dev/null +++ b/src/gameboy/timer.rs @@ -0,0 +1,87 @@ +pub struct Timer { + pub enable: bool, + pub clock: TimerClock, + pub div: u8, + pub div_counter: u8, + pub tima: u8, + pub tima_counter: u16, + pub tma: u8, +} + +impl Timer { + pub fn new() -> Self { + Self { + enable: false, + clock: TimerClock::C1024, + tima: 0, + tma: 0, + div: 0, + div_counter: 0, + tima_counter: 0, + } + } + + pub fn tick(&mut self) -> bool { + self.div_counter = self.div_counter.overflowing_add(1).0; + if self.div_counter == 0 { + self.div = self.div.overflowing_add(1).0; + } + + if self.enable { + self.tima_counter = self.tima_counter.overflowing_add(1).0; + if self.tima_counter >= self.clock.cycles() { + self.tima += self.tima.overflowing_add(1).0; + + return self.tima == 0; + } + } + false + } + + pub fn read_tac(&self) -> u8 { + ((self.enable as u8) << 2) | self.clock.tac_clock() + } + + pub fn write_tac(&mut self, value: u8) { + self.enable = (value >> 2) & 0b1 == 1; + self.clock = TimerClock::from_tac_clock(value); + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum TimerClock { + C16, + C64, + C256, + C1024, +} + +impl TimerClock { + pub fn cycles(&self) -> u16 { + match self { + Self::C16 => 16, + Self::C64 => 64, + Self::C256 => 256, + Self::C1024 => 1024, + } + } + + pub fn tac_clock(&self) -> u8 { + match self { + Self::C16 => 1, + Self::C64 => 2, + Self::C256 => 3, + Self::C1024 => 0, + } + } + + pub fn from_tac_clock(value: u8) -> Self { + match value & 0b11 { + 1 => Self::C16, + 2 => Self::C64, + 3 => Self::C256, + 4 => Self::C1024, + _ => unreachable!(), + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..16ff297 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,125 @@ +mod gameboy; +mod settings; +mod window; + +use std::{ + path::PathBuf, + sync::mpsc::{channel, Receiver, Sender}, +}; + +use argh::FromArgs; +use gameboy::Gameboy; +use settings::DeemgeeConfig; +use window::WindowEvent; + +use crate::window::GameboyEvent; + +#[derive(Debug, FromArgs)] +/// DMG Emulator +pub struct CliArgs { + /// bootrom path + #[argh(positional)] + pub bootrom: PathBuf, + /// game path + #[argh(positional)] + pub rom: Option, +} + +#[derive(Debug, thiserror::Error)] +pub enum DmgError { + #[error("Bootrom Not Found")] + BootromNotFound, + #[error("Bootrom Incorrect Size (expected 256 bytes, found {0} bytes)")] + BootromInvalidSize(u64), + #[error("Bootrom SHA1 failed (expected 4ed31ec6b0b175bb109c0eb5fd3d193da823339f)")] + BootromInvalidHash, + #[error("Game Not Found")] + GameNotFound, + #[error("IO Error: {0}")] + IO(#[from] std::io::Error), +} + +fn main() -> Result<(), DmgError> { + env_logger::init(); + + let args: CliArgs = argh::from_env(); + let config = DeemgeeConfig::from_file(); + + let (window_side_tx, gb_side_rx) = channel::(); + let (gb_side_tx, window_side_rx) = channel::(); + + let jh = std::thread::spawn(move || run_gameboy(config, args, gb_side_rx, gb_side_tx)); + + window::run_window(config, window_side_rx, window_side_tx); + + jh.join().unwrap() +} + +pub fn run_gameboy( + _config: DeemgeeConfig, + args: CliArgs, + rx: Receiver, + tx: Sender, +) -> Result<(), DmgError> { + if !args.bootrom.is_file() { + return Err(DmgError::BootromNotFound); + } + + let brom_md = std::fs::metadata(args.bootrom.as_path())?; + + if brom_md.len() != 256 { + return Err(DmgError::BootromInvalidSize(brom_md.len())); + } + + let bootrom = std::fs::read(args.bootrom)?; + + if bootrom.len() != 256 { + return Err(DmgError::BootromInvalidSize(bootrom.len() as u64)); + } + + if sha1::Sha1::from(bootrom.as_slice()).hexdigest().as_str() + != "4ed31ec6b0b175bb109c0eb5fd3d193da823339f" + { + return Err(DmgError::BootromInvalidHash); + } + + let mut gameboy = Gameboy::new(bootrom.as_slice().try_into().unwrap()); + let mut last = chrono::Local::now(); + let mut paused = false; + let mut frame_counter = 0; + + 'outer: loop { + while let Ok(event) = rx.try_recv() { + match event { + window::WindowEvent::AToggle => gameboy.joypad.a = !gameboy.joypad.a, + window::WindowEvent::BToggle => gameboy.joypad.b = !gameboy.joypad.b, + window::WindowEvent::SelectToggle => gameboy.joypad.select = !gameboy.joypad.select, + window::WindowEvent::StartToggle => gameboy.joypad.start = !gameboy.joypad.start, + window::WindowEvent::UpToggle => gameboy.joypad.up = !gameboy.joypad.up, + window::WindowEvent::DownToggle => gameboy.joypad.down = !gameboy.joypad.down, + window::WindowEvent::LeftToggle => gameboy.joypad.left = !gameboy.joypad.left, + window::WindowEvent::RightToggle => gameboy.joypad.right = !gameboy.joypad.right, + window::WindowEvent::PauseToggle => paused = !paused, + window::WindowEvent::Exit => break 'outer, + } + } + + if !paused { + let redraw_needed = gameboy.tick(); + if redraw_needed { + frame_counter += 1; + + if frame_counter == 60 { + let now = chrono::Local::now(); + log::info!("Rendered 60 frames in {}", now - last); + last = now; + frame_counter = 0; + + tx.send(GameboyEvent::Framebuffer(gameboy.ppu.write_fb())).unwrap(); + } + } + } + } + + Ok(()) +} diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..00faa43 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,29 @@ +use winit::event::VirtualKeyCode; + +#[derive(Debug, serde::Deserialize, Clone, Copy)] +pub struct DeemgeeConfig { + pub bindings: Bindings, +} + +impl DeemgeeConfig { + pub fn from_file() -> Self { + let mut settings = config::Config::default(); + settings.merge(config::File::with_name("config")).unwrap(); + settings.try_into().expect("Config Error") + } +} + +#[derive(Debug, serde::Deserialize, Clone, Copy)] +pub struct Bindings { + pub a: VirtualKeyCode, + pub b: VirtualKeyCode, + pub select: VirtualKeyCode, + pub start: VirtualKeyCode, + pub up: VirtualKeyCode, + pub down: VirtualKeyCode, + pub left: VirtualKeyCode, + pub right: VirtualKeyCode, + + pub pause: VirtualKeyCode, + pub exit: VirtualKeyCode, +} diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..4b3d8b4 --- /dev/null +++ b/src/window.rs @@ -0,0 +1,171 @@ +use std::sync::mpsc::{Receiver, Sender}; + +use pixels::{Pixels, SurfaceTexture}; +use winit::{ + event::{Event, VirtualKeyCode}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; +use winit_input_helper::WinitInputHelper; + +use crate::settings::DeemgeeConfig; + +macro_rules! define_keypress { + ($input:ident, $config:ident, $keymap:ident, $tx:ident, $key:ident, $event:ident) => { + if $input.key_pressed($config.bindings.$key) + && !*$keymap.idx(&$config, $config.bindings.$key) + { + $tx.send(WindowEvent::$event).unwrap(); + *$keymap.idx(&$config, $config.bindings.$key) = true; + } + + if $input.key_released($config.bindings.$key) + && *$keymap.idx(&$config, $config.bindings.$key) + { + $tx.send(WindowEvent::$event).unwrap(); + *$keymap.idx(&$config, $config.bindings.$key) = false; + } + }; +} + +#[derive(Debug, Clone, Copy)] +pub enum WindowEvent { + AToggle, + BToggle, + SelectToggle, + StartToggle, + UpToggle, + DownToggle, + LeftToggle, + RightToggle, + PauseToggle, + Exit, +} + +#[derive(Debug)] +pub enum GameboyEvent { + Framebuffer(Vec), +} + +pub const FB_HEIGHT: u32 = 144; +pub const FB_WIDTH: u32 = 160; + +#[derive(Debug, Default)] +pub struct Keymap { + pub down: bool, + pub up: bool, + pub left: bool, + pub right: bool, + pub start: bool, + pub select: bool, + pub b: bool, + pub a: bool, + pub pause: bool, +} + +impl Keymap { + pub fn idx(&mut self, config: &DeemgeeConfig, kc: VirtualKeyCode) -> &mut bool { + if kc == config.bindings.a { + &mut self.a + } else if kc == config.bindings.b { + &mut self.b + } else if kc == config.bindings.start { + &mut self.start + } else if kc == config.bindings.select { + &mut self.select + } else if kc == config.bindings.up { + &mut self.up + } else if kc == config.bindings.down { + &mut self.down + } else if kc == config.bindings.left { + &mut self.left + } else if kc == config.bindings.right { + &mut self.right + } else if kc == config.bindings.pause { + &mut self.pause + } else { + unreachable!(); + } + } +} + +pub fn run_window(config: DeemgeeConfig, rx: Receiver, tx: Sender) { + let event_loop = EventLoop::new(); + let mut input = WinitInputHelper::new(); + + let window = { WindowBuilder::new().with_title("OwO").build(&event_loop).unwrap() }; + + let mut pixels = { + let window_size = window.inner_size(); + let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); + Pixels::new(FB_WIDTH, FB_HEIGHT, surface_texture).unwrap() + }; + + let mut redraw_happened = true; + let mut fb: Option> = None; + + let mut keymap = Keymap::default(); + + event_loop.run(move |event, _, control_flow| { + if let Event::RedrawRequested(_) = event { + let frame = pixels.get_frame(); + + match fb.as_ref() { + Some(fb) => { + redraw_happened = true; + frame.copy_from_slice(fb.as_slice()); + } + None => { + let x = vec![0xff; frame.len()]; + frame.copy_from_slice(x.as_slice()) + } + } + if let Err(why) = pixels.render() { + log::error!("Pixels Error: {}", why); + *control_flow = ControlFlow::Exit; + tx.send(WindowEvent::Exit).unwrap(); + return; + } + } + + if input.update(&event) { + if input.key_pressed(config.bindings.exit) || input.quit() { + *control_flow = ControlFlow::Exit; + tx.send(WindowEvent::Exit).unwrap(); + return; + } + + if input.key_pressed(config.bindings.pause) { + tx.send(WindowEvent::PauseToggle).unwrap(); + } + + define_keypress!(input, config, keymap, tx, a, AToggle); + define_keypress!(input, config, keymap, tx, b, BToggle); + define_keypress!(input, config, keymap, tx, start, StartToggle); + define_keypress!(input, config, keymap, tx, select, SelectToggle); + define_keypress!(input, config, keymap, tx, up, UpToggle); + define_keypress!(input, config, keymap, tx, down, DownToggle); + define_keypress!(input, config, keymap, tx, left, LeftToggle); + define_keypress!(input, config, keymap, tx, right, RightToggle); + } + + if let Some(size) = input.window_resized() { + pixels.resize_surface(size.width, size.height); + window.request_redraw(); + redraw_happened = false; + } + + while let Ok(event) = rx.try_recv() { + match event { + GameboyEvent::Framebuffer(buf) => { + fb = Some(buf); + + if redraw_happened { + window.request_redraw(); + redraw_happened = false; + } + } + } + } + }); +}