From d97a8559ec6b56d9ed508a50c9cdd89f176eb0d7 Mon Sep 17 00:00:00 2001 From: Matthew Pomes Date: Sun, 7 Dec 2025 11:34:37 -0600 Subject: [PATCH] Initial commit --- .gitignore | 3 + Cargo.lock | 4560 ++++++++++++++++++++++++++++++++++ Cargo.toml | 13 + build.rs | 45 + src/apu.rs | 111 + src/controllers.rs | 14 + src/header_menu.rs | 944 +++++++ src/hex_view.rs | 57 + src/lib.rs | 1574 ++++++++++++ src/main.rs | 310 +++ src/mem.rs | 163 ++ src/ppu.rs | 495 ++++ src/test_roms/Sprites.pcx | Bin 0 -> 5479 bytes src/test_roms/Tiles.pcx | Bin 0 -> 9631 bytes src/test_roms/basic-cpu.asm | 26 + src/test_roms/mod.rs | 41 + src/test_roms/read_write.asm | 31 + 17 files changed, 8387 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 src/apu.rs create mode 100644 src/controllers.rs create mode 100644 src/header_menu.rs create mode 100644 src/hex_view.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/mem.rs create mode 100644 src/ppu.rs create mode 100644 src/test_roms/Sprites.pcx create mode 100644 src/test_roms/Tiles.pcx create mode 100644 src/test_roms/basic-cpu.asm create mode 100644 src/test_roms/mod.rs create mode 100644 src/test_roms/read_write.asm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f41442 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +*.nes +*.zip diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4473520 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4560 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.10.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.2", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.2", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.2", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +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 = "bitfield" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf79f42d21f18b5926a959280215903e659760da994835d27c3a0c5ff4f898f" +dependencies = [ + "bitfield-macros", +] + +[[package]] +name = "bitfield-macros" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6115af052c7914c0cbb97195e5c72cb61c511527250074f5c041d1048b0d8b16" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.10.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e" +dependencies = [ + "bitflags 2.10.0", + "polling", + "rustix 1.1.2", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.3", + "rustix 1.1.2", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "clipboard_macos" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "clipboard_wayland" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003f886bc4e2987729d10c1db3424e7f80809f3fc22dbc16c685738887cb37b8" +dependencies = [ + "smithay-clipboard", +] + +[[package]] +name = "clipboard_x11" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4274ea815e013e0f9f04a2633423e14194e408a0576c943ce3d14ca56c50031c" +dependencies = [ + "thiserror 1.0.69", + "x11rb", +] + +[[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 = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "cosmic-text" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2" +dependencies = [ + "bitflags 2.10.0", + "fontdb", + "log", + "rangemap", + "rayon", + "rustc-hash 1.1.0", + "rustybuzz", + "self_cell", + "swash", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctor-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "d3d12" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" +dependencies = [ + "bitflags 2.10.0", + "libloading 0.8.9", + "winapi", +] + +[[package]] +name = "dark-light" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a76fa97167fa740dcdbfe18e8895601e1bc36525f09b044e00916e717c03a3c" +dependencies = [ + "dconf_rs", + "detect-desktop-environment", + "dirs", + "objc", + "rust-ini", + "web-sys", + "winreg", + "zbus", +] + +[[package]] +name = "dconf_rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7046468a81e6a002061c01e6a7c83139daf91b11c30e66795b13217c2d885c8b" + +[[package]] +name = "detect-desktop-environment" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d8ad60dd5b13a4ee6bd8fa2d5d88965c597c67bce32b5fc49c94f55cb50810" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +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.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.9", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "drm" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "rustix 0.38.44", +] + +[[package]] +name = "drm-ffi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" +dependencies = [ + "drm-sys", + "rustix 0.38.44", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" +dependencies = [ + "libc", + "linux-raw-sys 0.6.5", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "font-types" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.20.0", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.2", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.10.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "winapi", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +dependencies = [ + "bitflags 2.10.0", + "gpu-descriptor-types", + "hashbrown 0.14.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.12", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.10.0", + "com", + "libc", + "libloading 0.8.9", + "thiserror 1.0.69", + "widestring", + "winapi", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "iced" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88acfabc84ec077eaf9ede3457ffa3a104626d79022a9bf7f296093b1d60c73f" +dependencies = [ + "iced_core", + "iced_futures", + "iced_renderer", + "iced_widget", + "iced_winit", + "thiserror 1.0.69", +] + +[[package]] +name = "iced_core" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0013a238275494641bf8f1732a23a808196540dc67b22ff97099c044ae4c8a1c" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "dark-light", + "glam", + "log", + "num-traits", + "once_cell", + "palette", + "rustc-hash 2.1.1", + "smol_str", + "thiserror 1.0.69", + "web-time", +] + +[[package]] +name = "iced_futures" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c04a6745ba2e80f32cf01e034fd00d853aa4f4cd8b91888099cb7aaee0d5d7c" +dependencies = [ + "futures", + "iced_core", + "log", + "rustc-hash 2.1.1", + "tokio", + "wasm-bindgen-futures", + "wasm-timer", +] + +[[package]] +name = "iced_glyphon" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c3bb56f1820ca252bc1d0994ece33d233a55657c0c263ea7cb16895adbde82" +dependencies = [ + "cosmic-text", + "etagere", + "lru", + "rustc-hash 2.1.1", + "wgpu", +] + +[[package]] +name = "iced_graphics" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba25a18cfa6d5cc160aca7e1b34f73ccdff21680fa8702168c09739767b6c66f" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "cosmic-text", + "half", + "iced_core", + "iced_futures", + "image", + "kamadak-exif", + "log", + "lyon_path", + "once_cell", + "raw-window-handle", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "unicode-segmentation", +] + +[[package]] +name = "iced_renderer" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73558208059f9e622df2bf434e044ee2f838ce75201a023cf0ca3e1244f46c2a" +dependencies = [ + "iced_graphics", + "iced_tiny_skia", + "iced_wgpu", + "log", + "thiserror 1.0.69", +] + +[[package]] +name = "iced_runtime" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348b5b2c61c934d88ca3b0ed1ed913291e923d086a66fa288ce9669da9ef62b5" +dependencies = [ + "bytes", + "iced_core", + "iced_futures", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "iced_tiny_skia" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c625d368284fcc43b0b36b176f76eff1abebe7959dd58bd8ce6897d641962a50" +dependencies = [ + "bytemuck", + "cosmic-text", + "iced_graphics", + "kurbo", + "log", + "rustc-hash 2.1.1", + "softbuffer", + "tiny-skia", +] + +[[package]] +name = "iced_wgpu" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15708887133671d2bcc6c1d01d1f176f43a64d6cdc3b2bf893396c3ee498295f" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "futures", + "glam", + "guillotiere", + "iced_glyphon", + "iced_graphics", + "log", + "lyon", + "once_cell", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "wgpu", +] + +[[package]] +name = "iced_widget" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81429e1b950b0e4bca65be4c4278fea6678ea782030a411778f26fa9f8983e1d" +dependencies = [ + "iced_renderer", + "iced_runtime", + "num-traits", + "once_cell", + "ouroboros", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "unicode-segmentation", +] + +[[package]] +name = "iced_winit" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44cd4e1c594b6334f409282937bf972ba14d31fedf03c23aa595d982a2fda28" +dependencies = [ + "iced_futures", + "iced_graphics", + "iced_runtime", + "log", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "tracing", + "wasm-bindgen-futures", + "web-sys", + "winapi", + "window_clipboard", + "winit", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", +] + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kamadak-exif" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077" +dependencies = [ + "mutate_once", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.9", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" +dependencies = [ + "arrayvec", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.10.0", + "libc", + "redox_syscall 0.5.18", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" + +[[package]] +name = "lyon" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcb7d54d54c8937364c9d41902d066656817dce1e03a44e5533afebd1ef4352" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c0829e28c4f336396f250d850c3987e16ce6db057ffe047ce0dd54aab6b647" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e16770d760c7848b0c1c2d209101e408207a65168109509f8483837a36cf2e7" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aeca86bcfd632a15984ba029b539ffb811e0a70bf55e814ef8b0f54f506fdeb" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f586142e1280335b1bc89539f7c97dd80f08fc43e9ab1b74ef0a42b04aa353" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +dependencies = [ + "bitflags 2.10.0", + "block", + "core-graphics-types 0.1.3", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mutate_once" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" + +[[package]] +name = "naga" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" +dependencies = [ + "bit-set", + "bitflags 2.10.0", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash 1.1.0", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.10.0", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nes-emu" +version = "0.1.0" +dependencies = [ + "bitfield", + "iced", + "iced_graphics", + "iced_widget", + "thiserror 2.0.17", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", + "memoffset", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[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-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.10.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.10.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[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.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "orbclient" +version = "0.3.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "247ad146e19b9437f8604c21f8652423595cf710ad108af40e77d3ae6e96b827" +dependencies = [ + "libredox", +] + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser 0.25.1", +] + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[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 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.12", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", + "version_check", + "yansi", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + +[[package]] +name = "rangemap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.22.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "libm", + "smallvec", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit 0.19.2", + "tiny-skia", +] + +[[package]] +name = "self_cell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "skrifa" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.10.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.10.0", + "calloop 0.14.3", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.2", + "thiserror 2.0.17", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "as-raw-xcb-connection", + "bytemuck", + "cfg_aliases 0.2.1", + "core-graphics 0.24.0", + "drm", + "fastrand", + "foreign-types", + "js-sys", + "log", + "memmap2", + "objc2", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall 0.5.18", + "rustix 0.38.44", + "tiny-xlib", + "wasm-bindgen", + "wayland-backend", + "wayland-client", + "wayland-sys", + "web-sys", + "windows-sys 0.59.0", + "x11rb", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "swash" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tiny-xlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" +dependencies = [ + "as-raw-xcb-connection", + "ctor-lite", + "libloading 0.8.9", + "pkg-config", + "tracing", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "chrono", + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.108", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.2", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +dependencies = [ + "bitflags 2.10.0", + "rustix 1.1.2", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.10.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" +dependencies = [ + "rustix 1.1.2", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfe33d551eb8bffd03ff067a8b44bb963919157841a99957151299a6307d19c" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" +dependencies = [ + "arrayvec", + "cfg-if", + "cfg_aliases 0.1.1", + "js-sys", + "log", + "naga", + "parking_lot 0.12.5", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.10.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot 0.12.5", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfabcfc55fd86611a855816326b2d54c3b2fd7972c27ce414291562650552703" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.10.0", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types 0.1.3", + "d3d12", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.9", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot 0.12.5", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +dependencies = [ + "bitflags 2.10.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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 = "window_clipboard" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d692d46038c433f9daee7ad8757e002a4248c20b0a3fbc991d99521d3bcb6d" +dependencies = [ + "clipboard-win", + "clipboard_macos", + "clipboard_wayland", + "clipboard_x11", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +dependencies = [ + "ahash 0.8.12", + "android-activity", + "atomic-waker", + "bitflags 2.10.0", + "block2", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit 0.19.2", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading 0.8.9", + "once_cell", + "rustix 1.1.2", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.10.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.108", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.108", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..dfca184 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nes-emu" +version = "0.1.0" +edition = "2024" + +[dependencies] +bitfield = "0.19.3" +iced = { version = "0.13.1", features = ["debug", "canvas", "tokio", "lazy"] } +iced_graphics = { version = "0.13.0", features = ["geometry", "image"] } +iced_widget = { version = "0.13.4", features = ["canvas", "image"] } +thiserror = "2.0.17" +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.20", features = ["ansi", "chrono", "env-filter", "json", "serde"] } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..5c83657 --- /dev/null +++ b/build.rs @@ -0,0 +1,45 @@ +use std::{io::Read, process::Stdio}; + +fn main() { + println!("cargo::rerun-if-changed=src/test_roms/"); + println!("cargo::rerun-if-changed=build.rs"); + + let name = format!( + "{}/test-roms", + std::env::var("OUT_DIR").expect("Need OUT_DIR env var") + ); + std::fs::create_dir_all(&name).expect("Failed to create rom output dir"); + println!("cargo::rustc-env=ROM_DIR={name}"); + + for file in std::fs::read_dir("./src/test_roms").expect("Failed to read directory") { + let file = file.expect("test"); + let file_name = file.file_name(); + let file_name = file_name.to_str().unwrap(); + if let Some(file_name) = file_name.strip_suffix(".asm") { + let mut proc = std::process::Command::new("/home/matthew/asm6f/asm6f") + .arg(file.file_name()) + .arg(format!("{name}/{file_name}.nes")) + .current_dir("src/test_roms") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to start process"); + let rc = proc.wait(); + if rc.is_err() || rc.is_ok_and(|s| !s.success()) { + let mut stdout = String::new(); + proc.stdout + .unwrap() + .read_to_string(&mut stdout) + .expect("Failed to read stdout"); + let mut stderr = String::new(); + proc.stderr + .unwrap() + .read_to_string(&mut stderr) + .expect("Failed to read stderr"); + panic!( + "Failed to compile {file_name}\n=== STDOUT ===\n{stdout}\n=== STDERR ===\n{stderr}" + ); + } + } + } +} diff --git a/src/apu.rs b/src/apu.rs new file mode 100644 index 0000000..b622241 --- /dev/null +++ b/src/apu.rs @@ -0,0 +1,111 @@ +use tracing::debug; + +bitfield::bitfield! { + pub struct DutyVol(u8); + impl Debug; + duty, set_duty: 7, 6; + r#loop, set_loop: 5; + const_vol, set_const_vol: 4; + volume, set_volume: 3, 0; +} + +bitfield::bitfield! { + pub struct Sweep(u8); + impl Debug; + enable, set_enable: 7; + period, set_period: 6, 4; + negate, set_negate: 3; + shift, set_shift: 2, 0; +} + +bitfield::bitfield! { + pub struct LengthTimerHigh(u8); + impl Debug; + length, set_length: 7, 3; + timer_high, set_timer_high: 2, 0; +} + +struct PulseChannel { + enabled: bool, +} + +bitfield::bitfield! { + pub struct CounterLoad(u8); + impl Debug; + halt, set_halt: 7; + value, set_value: 6, 0; +} + +struct TriangleChannel { + enabled: bool, +} +struct NoiseChannel { + enabled: bool, +} +struct DeltaChannel { + enabled: bool, +} + +pub struct APU { + pulse_1: PulseChannel, + pulse_2: PulseChannel, + triangle: TriangleChannel, + noise: NoiseChannel, + dmc: DeltaChannel, + frame_counter: u8, +} + +impl std::fmt::Debug for APU { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // write!( + // f, + // "PPU: f {}, s {}, p {}", + // self.frame_count, self.scanline, self.pixel + // ) + Ok(()) + } +} + +impl APU { + pub fn init() -> Self { + Self { + pulse_1: PulseChannel { enabled: false }, + pulse_2: PulseChannel { enabled: false }, + triangle: TriangleChannel { enabled: false }, + noise: NoiseChannel { enabled: false }, + dmc: DeltaChannel { enabled: false }, + frame_counter: 0, + } + } + pub fn read_reg(&mut self, offset: u16) -> u8 { + match offset { + _ => panic!("No register at {:X}", offset), + } + } + pub fn write_reg(&mut self, offset: u16, val: u8) { + match offset { + 0x15 => { + self.dmc.enabled = val & 0b0001_0000 != 0; + self.noise.enabled = val & 0b0000_1000 != 0; + self.triangle.enabled = val & 0b0000_0100 != 0; + self.pulse_2.enabled = val & 0b0000_0010 != 0; + self.pulse_1.enabled = val & 0b0000_0001 != 0; + } + 0x11 => { + // TODO: load dmc counter with (val & 7F) + } + _ => panic!("No register at {:X}", offset), + } + } + + pub fn run_one_clock_cycle(&mut self) -> bool { + false + } + pub fn nmi_waiting(&mut self) -> bool { + false + } + pub fn irq_waiting(&mut self) -> bool { + // TODO: implement logic + false + } +} diff --git a/src/controllers.rs b/src/controllers.rs new file mode 100644 index 0000000..e4b1390 --- /dev/null +++ b/src/controllers.rs @@ -0,0 +1,14 @@ +pub struct Controllers {} + +impl Controllers { + pub fn init() -> Self { + Self {} + } + pub fn read_joy1(&mut self) -> u8 { + 0 + } + pub fn read_joy2(&mut self) -> u8 { + 0 + } + pub fn write_joy_strobe(&mut self, val: u8) { } +} diff --git a/src/header_menu.rs b/src/header_menu.rs new file mode 100644 index 0000000..8fcf958 --- /dev/null +++ b/src/header_menu.rs @@ -0,0 +1,944 @@ +//! Pick lists display a dropdown list of selectable options. +//! +//! # Example +//! ```no_run +//! # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; } +//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +//! # +//! use iced::widget::pick_list; +//! +//! struct State { +//! favorite: Option, +//! } +//! +//! #[derive(Debug, Clone, Copy, PartialEq, Eq)] +//! enum Fruit { +//! Apple, +//! Orange, +//! Strawberry, +//! Tomato, +//! } +//! +//! #[derive(Debug, Clone)] +//! enum Message { +//! FruitSelected(Fruit), +//! } +//! +//! fn view(state: &State) -> Element<'_, Message> { +//! let fruits = [ +//! Fruit::Apple, +//! Fruit::Orange, +//! Fruit::Strawberry, +//! Fruit::Tomato, +//! ]; +//! +//! pick_list( +//! fruits, +//! state.favorite, +//! Message::FruitSelected, +//! ) +//! .placeholder("Select your favorite fruit...") +//! .into() +//! } +//! +//! fn update(state: &mut State, message: Message) { +//! match message { +//! Message::FruitSelected(fruit) => { +//! state.favorite = Some(fruit); +//! } +//! } +//! } +//! +//! impl std::fmt::Display for Fruit { +//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +//! f.write_str(match self { +//! Self::Apple => "Apple", +//! Self::Orange => "Orange", +//! Self::Strawberry => "Strawberry", +//! Self::Tomato => "Tomato", +//! }) +//! } +//! } +//! ``` +use iced::alignment; +use iced::event::{self, Event}; +use iced::keyboard; +// use iced::layout; +use iced::mouse; +use iced::overlay; +// use iced::renderer; +// use iced::text::paragraph; +// use iced::text::{self, Text}; +use iced::touch; +// use iced::widget::tree::{self, Tree}; +use iced::overlay::menu::{self, Menu}; +use iced::{ + Background, Border, Color, Element, Length, Padding, Pixels, Point, Rectangle, Size, Theme, + Vector, +}; +use iced_graphics::core::text::paragraph; +use iced_graphics::core::widget::{Tree, tree}; +use iced_graphics::core::{Clipboard, Layout, Shell, Text, Widget, layout, renderer, text}; + +use std::borrow::Borrow; +use std::f32; + +/// A widget for selecting a single value from a list of options. +/// +/// # Example +/// ```no_run +/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; } +/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +/// # +/// use iced::widget::pick_list; +/// +/// struct State { +/// favorite: Option, +/// } +/// +/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// enum Fruit { +/// Apple, +/// Orange, +/// Strawberry, +/// Tomato, +/// } +/// +/// #[derive(Debug, Clone)] +/// enum Message { +/// FruitSelected(Fruit), +/// } +/// +/// fn view(state: &State) -> Element<'_, Message> { +/// let fruits = [ +/// Fruit::Apple, +/// Fruit::Orange, +/// Fruit::Strawberry, +/// Fruit::Tomato, +/// ]; +/// +/// pick_list( +/// fruits, +/// state.favorite, +/// Message::FruitSelected, +/// ) +/// .placeholder("Select your favorite fruit...") +/// .into() +/// } +/// +/// fn update(state: &mut State, message: Message) { +/// match message { +/// Message::FruitSelected(fruit) => { +/// state.favorite = Some(fruit); +/// } +/// } +/// } +/// +/// impl std::fmt::Display for Fruit { +/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +/// f.write_str(match self { +/// Self::Apple => "Apple", +/// Self::Orange => "Orange", +/// Self::Strawberry => "Strawberry", +/// Self::Tomato => "Tomato", +/// }) +/// } +/// } +/// ``` +#[allow(missing_debug_implementations)] +pub struct HeaderMenu< + 'a, + T, + L, + // V, + Message, + Theme = iced::Theme, + Renderer = iced::Renderer, +> where + T: ToString + PartialEq + Clone, + L: Borrow<[T]> + 'a, + // V: Borrow + 'a, + Theme: Catalog, + Renderer: text::Renderer, +{ + title: String, + on_select: Box Message + 'a>, + on_open: Option, + on_close: Option, + options: L, + placeholder: Option, + width: Length, + padding: Padding, + text_size: Option, + text_line_height: text::LineHeight, + text_shaping: text::Shaping, + font: Option, + handle: Handle, + class: ::Class<'a>, + menu_class: ::Class<'a>, +} + +impl<'a, T, L, Message, Theme, Renderer> HeaderMenu<'a, T, L, Message, Theme, Renderer> +where + T: ToString + PartialEq + Clone, + L: Borrow<[T]> + 'a, + // V: Borrow + 'a, + Message: Clone, + Theme: Catalog, + Renderer: text::Renderer, +{ + /// Creates a new [`PickList`] with the given list of options, the current + /// selected value, and the message to produce when an option is selected. + pub fn new( + title: impl Into, + options: L, + // selected: Option, + on_click: impl Fn(T) -> Message + 'a, + ) -> Self { + Self { + title: title.into(), + on_select: Box::new(on_click), + on_open: None, + on_close: None, + options, + placeholder: None, + // selected, + width: Length::Shrink, + padding: Padding::new(0.1), + text_size: None, + text_line_height: text::LineHeight::default(), + text_shaping: text::Shaping::default(), + font: None, + handle: Handle::default(), + class: ::default(), + menu_class: ::default_menu(), + } + } + + /// Sets the placeholder of the [`PickList`]. + pub fn placeholder(mut self, placeholder: impl Into) -> Self { + self.placeholder = Some(placeholder.into()); + self + } + + /// Sets the width of the [`PickList`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the [`Padding`] of the [`PickList`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the text size of the [`PickList`]. + pub fn text_size(mut self, size: impl Into) -> Self { + self.text_size = Some(size.into()); + self + } + + /// Sets the text [`text::LineHeight`] of the [`PickList`]. + pub fn text_line_height(mut self, line_height: impl Into) -> Self { + self.text_line_height = line_height.into(); + self + } + + /// Sets the [`text::Shaping`] strategy of the [`PickList`]. + pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { + self.text_shaping = shaping; + self + } + + /// Sets the font of the [`PickList`]. + pub fn font(mut self, font: impl Into) -> Self { + self.font = Some(font.into()); + self + } + + /// Sets the [`Handle`] of the [`PickList`]. + pub fn handle(mut self, handle: Handle) -> Self { + self.handle = handle; + self + } + + /// Sets the message that will be produced when the [`PickList`] is opened. + pub fn on_open(mut self, on_open: Message) -> Self { + self.on_open = Some(on_open); + self + } + + /// Sets the message that will be produced when the [`PickList`] is closed. + pub fn on_close(mut self, on_close: Message) -> Self { + self.on_close = Some(on_close); + self + } + + /// Sets the style of the [`PickList`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + ::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style of the [`Menu`]. + #[must_use] + pub fn menu_style(mut self, style: impl Fn(&Theme) -> menu::Style + 'a) -> Self + where + ::Class<'a>: From>, + { + self.menu_class = (Box::new(style) as menu::StyleFn<'a, Theme>).into(); + self + } +} + +impl<'a, T, L, Message, Theme, Renderer> Widget + for HeaderMenu<'a, T, L, Message, Theme, Renderer> +where + T: Clone + ToString + PartialEq + 'a, + L: Borrow<[T]>, + // V: Borrow, + Message: Clone + 'a, + Theme: Catalog + 'a, + Renderer: text::Renderer + 'a, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(State::::new()) + } + + fn size(&self) -> Size { + Size { + width: self.width, + height: Length::Shrink, + } + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let state = tree.state.downcast_mut::>(); + + let font = self.font.unwrap_or_else(|| renderer.default_font()); + let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); + let options = self.options.borrow(); + + state.options.resize_with(options.len(), Default::default); + + let option_text = Text { + content: "", + bounds: Size::new( + f32::INFINITY, + self.text_line_height.to_absolute(text_size).into(), + ), + size: text_size, + line_height: self.text_line_height, + font, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: self.text_shaping, + wrapping: text::Wrapping::default(), + }; + + for (option, paragraph) in options.iter().zip(state.options.iter_mut()) { + let label = option.to_string(); + + paragraph.update(Text { + content: &label, + ..option_text + }); + } + + if let Some(placeholder) = &self.placeholder { + state.placeholder.update(Text { + content: placeholder, + ..option_text + }); + } + + let max_width = match self.width { + Length::Shrink => { + let labels_width = state.options.iter().fold(0.0, |width, paragraph| { + f32::max(width, paragraph.min_width()) + }); + + labels_width.max( + self.placeholder + .as_ref() + .map(|_| state.placeholder.min_width()) + .unwrap_or(0.0), + ) + } + _ => 0.0, + }; + + let size = { + let intrinsic = Size::new( + max_width + text_size.0 + self.padding.left, + f32::from(self.text_line_height.to_absolute(text_size)), + ); + + limits + .width(self.width) + .shrink(self.padding) + .resolve(self.width, Length::Shrink, intrinsic) + .expand(self.padding) + }; + + layout::Node::new(size) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) -> event::Status { + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + let state = tree.state.downcast_mut::>(); + + if state.is_open { + // Event wasn't processed by overlay, so cursor was clicked either outside its + // bounds or on the drop-down, either way we close the overlay. + state.is_open = false; + + if let Some(on_close) = &self.on_close { + shell.publish(on_close.clone()); + } + + event::Status::Captured + } else if cursor.is_over(layout.bounds()) { + // let selected = self.selected.as_ref().map(Borrow::borrow); + + state.is_open = true; + // state.hovered_option = self + // .options + // .borrow() + // .iter() + // .position(|option| Some(option) == selected); + + if let Some(on_open) = &self.on_open { + shell.publish(on_open.clone()); + } + event::Status::Captured + } else { + event::Status::Ignored + } + } + Event::Mouse(mouse::Event::WheelScrolled { + delta: mouse::ScrollDelta::Lines { y, .. }, + }) => { + let state = tree.state.downcast_mut::>(); + + if state.keyboard_modifiers.command() + && cursor.is_over(layout.bounds()) + && !state.is_open + { + fn find_next<'a, T: PartialEq>( + selected: &'a T, + mut options: impl Iterator, + ) -> Option<&'a T> { + let _ = options.find(|&option| option == selected); + + options.next() + } + + let options = self.options.borrow(); + let selected = None; // self.selected.as_ref().map(Borrow::borrow); + + let next_option = if y < 0.0 { + if let Some(selected) = selected { + find_next(selected, options.iter()) + } else { + options.first() + } + } else if y > 0.0 { + if let Some(selected) = selected { + find_next(selected, options.iter().rev()) + } else { + options.last() + } + } else { + None + }; + + if let Some(next_option) = next_option { + shell.publish((self.on_select)(next_option.clone())); + } + + event::Status::Captured + } else { + event::Status::Ignored + } + } + Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { + let state = tree.state.downcast_mut::>(); + + state.keyboard_modifiers = modifiers; + + event::Status::Ignored + } + _ => event::Status::Ignored, + } + } + + fn mouse_interaction( + &self, + _tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); + + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + } + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + _style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + let font = self.font.unwrap_or_else(|| renderer.default_font()); + // let selected = None; // self.selected.as_ref().map(Borrow::borrow); + let state = tree.state.downcast_ref::>(); + + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); + // let is_selected = selected.is_some(); + + let status = if state.is_open { + Status::Opened + } else if is_mouse_over { + Status::Hovered + } else { + Status::Active + }; + + let style = Catalog::style(theme, &self.class, status); + + renderer.fill_quad( + renderer::Quad { + bounds, + border: style.border, + ..renderer::Quad::default() + }, + style.background, + ); + + let handle = match &self.handle { + Handle::Arrow { size } => Some(( + Renderer::ICON_FONT, + Renderer::ARROW_DOWN_ICON, + *size, + text::LineHeight::default(), + text::Shaping::Basic, + )), + Handle::Static(Icon { + font, + code_point, + size, + line_height, + shaping, + }) => Some((*font, *code_point, *size, *line_height, *shaping)), + Handle::Dynamic { open, closed } => { + if state.is_open { + Some(( + open.font, + open.code_point, + open.size, + open.line_height, + open.shaping, + )) + } else { + Some(( + closed.font, + closed.code_point, + closed.size, + closed.line_height, + closed.shaping, + )) + } + } + Handle::None => None, + }; + + if let Some((font, code_point, size, line_height, shaping)) = handle { + let size = size.unwrap_or_else(|| renderer.default_size()); + + renderer.fill_text( + Text { + content: code_point.to_string(), + size, + line_height, + font, + bounds: Size::new(bounds.width, f32::from(line_height.to_absolute(size))), + horizontal_alignment: alignment::Horizontal::Right, + vertical_alignment: alignment::Vertical::Center, + shaping, + wrapping: text::Wrapping::default(), + }, + Point::new( + bounds.x + bounds.width - self.padding.right, + bounds.center_y(), + ), + style.text_color, + *viewport, + ); + } + + // let label = None; // selected.map(ToString::to_string); + + let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); + + renderer.fill_text( + Text { + content: self.title.clone(), + size: text_size, + line_height: self.text_line_height, + font, + bounds: Size::new( + bounds.width - self.padding.horizontal(), + f32::from(self.text_line_height.to_absolute(text_size)), + ), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: self.text_shaping, + wrapping: text::Wrapping::default(), + }, + Point::new(bounds.x + self.padding.left, bounds.center_y()), + // if is_selected { + // style.text_color + // } else { + style.placeholder_color, + // }, + *viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + let state = tree.state.downcast_mut::>(); + let font = self.font.unwrap_or_else(|| renderer.default_font()); + + if state.is_open { + let bounds = layout.bounds(); + + let on_select = &self.on_select; + + let mut menu = Menu::new( + &mut state.menu, + self.options.borrow(), + &mut state.hovered_option, + |option| { + state.is_open = false; + + (on_select)(option) + }, + None, + &self.menu_class, + ) + .width(bounds.width) + .padding(self.padding) + .font(font) + .text_shaping(self.text_shaping); + + if let Some(text_size) = self.text_size { + menu = menu.text_size(text_size); + } + + Some(menu.overlay(layout.position() + translation, bounds.height)) + } else { + None + } + } +} + +impl<'a, T, L, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + T: Clone + ToString + PartialEq + 'a, + L: Borrow<[T]> + 'a, + // V: Borrow + 'a, + Message: Clone + 'a, + Theme: Catalog + 'a, + Renderer: text::Renderer + 'a, +{ + fn from(pick_list: HeaderMenu<'a, T, L, Message, Theme, Renderer>) -> Self { + Self::new(pick_list) + } +} + +#[derive(Debug)] +struct State { + menu: menu::State, + keyboard_modifiers: keyboard::Modifiers, + is_open: bool, + hovered_option: Option, + options: Vec>, + placeholder: paragraph::Plain

, +} + +impl State

{ + /// Creates a new [`State`] for a [`PickList`]. + fn new() -> Self { + Self { + menu: menu::State::default(), + keyboard_modifiers: keyboard::Modifiers::default(), + is_open: bool::default(), + hovered_option: Option::default(), + options: Vec::new(), + placeholder: paragraph::Plain::default(), + } + } +} + +impl Default for State

{ + fn default() -> Self { + Self::new() + } +} + +/// The handle to the right side of the [`PickList`]. +#[derive(Debug, Clone, PartialEq)] +pub enum Handle { + /// Displays an arrow icon (â–¼). + /// + /// This is the default. + Arrow { + /// Font size of the content. + size: Option, + }, + /// A custom static handle. + Static(Icon), + /// A custom dynamic handle. + Dynamic { + /// The [`Icon`] used when [`PickList`] is closed. + closed: Icon, + /// The [`Icon`] used when [`PickList`] is open. + open: Icon, + }, + /// No handle will be shown. + None, +} + +impl Default for Handle { + fn default() -> Self { + Self::Arrow { size: None } + } +} + +/// The icon of a [`Handle`]. +#[derive(Debug, Clone, PartialEq)] +pub struct Icon { + /// Font that will be used to display the `code_point`, + pub font: Font, + /// The unicode code point that will be used as the icon. + pub code_point: char, + /// Font size of the content. + pub size: Option, + /// Line height of the content. + pub line_height: text::LineHeight, + /// The shaping strategy of the icon. + pub shaping: text::Shaping, +} + +/// The possible status of a [`PickList`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`PickList`] can be interacted with. + Active, + /// The [`PickList`] is being hovered. + Hovered, + /// The [`PickList`] is open. + Opened, +} + +/// The appearance of a pick list. +#[derive(Debug, Clone, Copy)] +pub struct Style { + /// The text [`Color`] of the pick list. + pub text_color: Color, + /// The placeholder [`Color`] of the pick list. + pub placeholder_color: Color, + /// The handle [`Color`] of the pick list. + pub handle_color: Color, + /// The [`Background`] of the pick list. + pub background: Background, + /// The [`Border`] of the pick list. + pub border: Border, +} + +/// The theme catalog of a [`PickList`]. +pub trait Catalog: menu::Catalog { + /// The item class of the [`Catalog`]. + type Class<'a>; + + /// The default class produced by the [`Catalog`]. + fn default<'a>() -> ::Class<'a>; + + /// The default class for the menu of the [`PickList`]. + fn default_menu<'a>() -> ::Class<'a> { + ::default() + } + + /// The [`Style`] of a class with the given status. + fn style(&self, class: &::Class<'_>, status: Status) -> Style; +} + +/// A styling function for a [`PickList`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box Style + 'a>; + +impl Catalog for Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> StyleFn<'a, Self> { + Box::new(default) + } + + fn style(&self, class: &StyleFn<'_, Self>, status: Status) -> Style { + class(self, status) + } +} + +/// The default style of the field of a [`PickList`]. +pub fn default(theme: &Theme, status: Status) -> Style { + let palette = theme.extended_palette(); + + let active = Style { + text_color: palette.background.weak.text, + background: palette.background.weak.color.into(), + placeholder_color: palette.background.strong.color, + handle_color: palette.background.weak.text, + border: Border { + radius: 2.0.into(), + width: 1.0, + color: palette.background.strong.color, + }, + }; + + match status { + Status::Active => active, + Status::Hovered | Status::Opened => Style { + border: Border { + color: palette.primary.strong.color, + ..active.border + }, + ..active + }, + } +} + +/// Creates a new [`PickList`]. +/// +/// Pick lists display a dropdown list of selectable options. +/// +/// # Example +/// ```no_run +/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; } +/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +/// # +/// use iced::widget::pick_list; +/// +/// struct State { +/// favorite: Option, +/// } +/// +/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// enum Fruit { +/// Apple, +/// Orange, +/// Strawberry, +/// Tomato, +/// } +/// +/// #[derive(Debug, Clone)] +/// enum Message { +/// FruitSelected(Fruit), +/// } +/// +/// fn view(state: &State) -> Element<'_, Message> { +/// let fruits = [ +/// Fruit::Apple, +/// Fruit::Orange, +/// Fruit::Strawberry, +/// Fruit::Tomato, +/// ]; +/// +/// pick_list( +/// fruits, +/// state.favorite, +/// Message::FruitSelected, +/// ) +/// .placeholder("Select your favorite fruit...") +/// .into() +/// } +/// +/// fn update(state: &mut State, message: Message) { +/// match message { +/// Message::FruitSelected(fruit) => { +/// state.favorite = Some(fruit); +/// } +/// } +/// } +/// +/// impl std::fmt::Display for Fruit { +/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +/// f.write_str(match self { +/// Self::Apple => "Apple", +/// Self::Orange => "Orange", +/// Self::Strawberry => "Strawberry", +/// Self::Tomato => "Tomato", +/// }) +/// } +/// } +/// ``` +pub fn header_menu<'a, T, L, Message, Theme, Renderer>( + title: impl Into, + options: L, + on_selected: impl Fn(T) -> Message + 'a, +) -> HeaderMenu<'a, T, L, Message, Theme, Renderer> +where + T: ToString + PartialEq + Clone + 'a, + L: Borrow<[T]> + 'a, + Message: Clone, + Theme: Catalog + overlay::menu::Catalog, + Renderer: text::Renderer, +{ + HeaderMenu::new(title, options, on_selected) +} diff --git a/src/hex_view.rs b/src/hex_view.rs new file mode 100644 index 0000000..6f5088d --- /dev/null +++ b/src/hex_view.rs @@ -0,0 +1,57 @@ +use std::fmt; + +use iced::{widget::{column, lazy, row, text}, Element, Font, Length::Fill, Task}; + +pub trait Memory { + fn peek(&self, val: u16) -> Option; + fn edit_ver(&self) -> usize; +} + +#[derive(Debug, Clone)] +pub enum HexEvent {} + +pub struct HexView { + +} + +struct Val(Option); +impl fmt::Display for Val { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(val) = self.0 { + write!(f, "{:02X}", val) + } else { + write!(f, "XX") + } + } +} + +impl HexView { + pub fn new() -> Self { + Self {} + } + + pub fn render<'a>(&self, mem: &'a impl Memory) -> Element<'a, HexEvent> { + struct Row<'a, M>(u16, &'a M); + impl fmt::Display for Row<'_, M> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", Val(self.1.peek(self.0)))?; + for i in 1..16 { + write!(f, " {}", Val(self.1.peek(self.0 + i)))?; + } + Ok(()) + } + } + column![ + text!("Hex view"), + iced::widget::scrollable(lazy(mem.edit_ver(), |_| column([ + text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F").font(Font::MONOSPACE).into() + ].into_iter().chain((0..u16::MAX).step_by(16).map(|off| { + text!(" {off:04X} | {}", Row(off, mem)).font(Font::MONOSPACE).into() + }))))).width(Fill), + ].width(Fill).into() + } + + pub fn update(&mut self, ev: HexEvent) -> Task { + todo!() + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..602dcc4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,1574 @@ +mod apu; +mod controllers; +pub mod header_menu; +pub mod hex_view; +mod mem; +mod ppu; + +#[cfg(test)] +mod test_roms; + +pub use ppu::{Color, PPU, RenderBuffer}; + +use std::{fs::File, io::Read, path::Path}; + +use thiserror::Error; +use tracing::{debug, info}; + +use crate::{ + apu::APU, + controllers::Controllers, + hex_view::Memory, + mem::{MemoryMap, Segment}, +}; + +#[derive(Error, Debug)] +pub enum NESError { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("File loaded was invalid")] + InvalidFile, +} +type Result = std::result::Result; + +pub struct NES { + clock_count: usize, + cpu: Cpu, + dma: DmaState, + memory: MemoryMap, + ppu: PPU, + apu: APU, + controller: Controllers, + + cycle: usize, + last_instruction: String, + dbg_int: bool, + halted: bool, +} + +impl std::fmt::Debug for NES { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "NES Emulator")?; + writeln!( + f, + "CPU: {{ a: {:02X}, x: {:02X}, y: {:02X}, flags: {:?}, sp: {:02X}, pc: {:04X} }}", + self.cpu.a, self.cpu.x, self.cpu.y, self.cpu.status, self.cpu.sp, self.cpu.pc + )?; + writeln!(f, " Decode {:X?}", self.cpu.clock_state)?; + writeln!(f, " Last instruction: {}", self.last_instruction)?; + writeln!(f, " Cycle: CPU={}, PPU={}", self.cycle, self.ppu.cycle)?; + writeln!( + f, + "MEM: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", + self.peek(self.cpu.pc), + self.peek(self.cpu.pc + 1), + self.peek(self.cpu.pc + 2), + self.peek(self.cpu.pc + 3), + self.peek(self.cpu.pc + 4), + self.peek(self.cpu.pc + 5), + self.peek(self.cpu.pc + 6), + self.peek(self.cpu.pc + 7), + self.peek(self.cpu.pc + 8), + self.peek(self.cpu.pc + 9), + )?; + writeln!( + f, + "0x1F0: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", + self.peek(0x1F0), + self.peek(0x1F1), + self.peek(0x1F2), + self.peek(0x1F3), + self.peek(0x1F4), + self.peek(0x1F5), + self.peek(0x1F6), + self.peek(0x1F7), + self.peek(0x1F8), + self.peek(0x1F9), + self.peek(0x1FA), + self.peek(0x1FB), + self.peek(0x1FC), + self.peek(0x1FD), + self.peek(0x1FE), + self.peek(0x1FF), + )?; + writeln!( + f, + "STACK: {:02X} {:02X} | {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", + self.peek(self.cpu.sp as u16 + 0xFF), + self.peek(self.cpu.sp as u16 + 0x100), + self.peek(self.cpu.sp as u16 + 0x101), + self.peek(self.cpu.sp as u16 + 0x102), + self.peek(self.cpu.sp as u16 + 0x103), + self.peek(self.cpu.sp as u16 + 0x104), + self.peek(self.cpu.sp as u16 + 0x105), + self.peek(self.cpu.sp as u16 + 0x106), + self.peek(self.cpu.sp as u16 + 0x107), + self.peek(self.cpu.sp as u16 + 0x108), + )?; + write!(f, "PPU: {:?}", self.ppu)?; + Ok(()) + } +} + +bitfield::bitfield! { + pub struct CpuStatus(u8); + carry, set_carry: 0; + zero, set_zero: 1; + interrupt_disable, set_interrupt_disable: 2; + decimal, set_decimal: 3; + brk, set_brk: 4; + php, set_php: 5; + overflow, set_overflow: 6; + negative, set_negative: 7; +} + +impl std::fmt::Debug for CpuStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.negative() { + write!(f, "N")?; + } + if self.overflow() { + write!(f, "V")?; + } + if self.decimal() { + write!(f, "D")?; + } + if self.interrupt_disable() { + write!(f, "I")?; + } + if self.zero() { + write!(f, "Z")?; + } + if self.carry() { + write!(f, "C")?; + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct Cpu { + a: u8, + x: u8, + y: u8, + pc: u16, + sp: u8, + status: CpuStatus, + clock_state: ClockState, +} + +enum ClockState { + ReadInstruction, + ReadOperands { + instruction: u8, + ops: [u8; 5], + count: u8, + }, + Hold { + cycles: u8, + instruction: u8, + ops: [u8; 5], + count: u8, + }, + HoldNmi { + cycles: u8, + }, + HoldIrq { + cycles: u8, + }, +} + +impl std::fmt::Debug for ClockState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ReadInstruction => write!(f, "ReadInstruction"), + Self::ReadOperands { + instruction, + ops, + count, + } => f + .debug_struct("ReadOperands") + .field("instruction", instruction) + .field("ops", &&ops[..*count as usize]) + .finish(), + Self::Hold { + cycles, + instruction, + ops, + count, + } => f + .debug_struct("Hold") + .field("cycles", cycles) + .field("instruction", instruction) + .field("ops", &&ops[..*count as usize]) + .finish(), + ClockState::HoldNmi { cycles } => { + f.debug_struct("HoldNmi").field("cycles", cycles).finish() + } + ClockState::HoldIrq { cycles } => { + f.debug_struct("HoldIrq").field("cycles", cycles).finish() + } + } + } +} + +enum ExecState { + Done, + MoreParams, + Hold(u8), +} + +pub enum CPUMMRegisters { + PPU, + APU, +} + +impl Cpu { + fn init() -> Self { + Self { + a: 0, + x: 0, + y: 0, + pc: 0, + sp: 0, + status: CpuStatus(0), + clock_state: ClockState::Hold { cycles: 0, instruction: 0xEA, ops: [0; 5], count: 0 }, + } + } +} + +pub enum DmaState { + Passive, + Running { + cpu_addr: u16, + rem: u8, + read: Option, + }, +} + +pub struct CycleResult { + pub cpu_exec: bool, + pub ppu_frame: bool, + pub dma: bool, + pub dbg_int: bool, +} + +impl NES { + pub fn load_nes_file(file: impl AsRef) -> Result { + let mut raw = Vec::new(); + File::open(file)?.read_to_end(&mut raw)?; + Self::load_nes_file_mem(&raw) + } + + pub fn load_nes_file_mem(raw: &[u8]) -> Result { + if &raw[0..4] != &[0x4E, 0x45, 0x53, 0x1A] { + return Err(NESError::InvalidFile); + } + let prg_rom_size = raw[4]; + let chr_rom_size = raw[5]; + let mapper_flags = raw[6]; + // let nes_20 = raw[7]; + info!("PRG: {prg_rom_size}"); + info!("CHR: {chr_rom_size}"); + info!("FLAGS: {mapper_flags:b}"); + if mapper_flags & 0b11111110 != 0 { + todo!("Support other mapper flags"); + } + Ok(Self::from_rom( + &raw[16..][..16384 * prg_rom_size as usize], + &raw[16 + 16384 * prg_rom_size as usize..][..8192 * chr_rom_size as usize], + )) + } + + fn from_rom(prg_rom: &[u8], chr_rom: &[u8]) -> Self { + let mut segments = vec![ + Segment::ram("Internal RAM", 0x0000, 0x0800), + Segment::mirror("Mirror of iRAM", 0x0800, 0x1800, 0), + Segment::reg("PPU registers", 0x2000, 0x0008, CPUMMRegisters::PPU), + Segment::mirror("Mirror of PPU", 0x2008, 0x1FF8, 2), + Segment::reg("APU & IO registers", 0x4000, 0x0018, CPUMMRegisters::APU), + Segment::mirror("Mirror of APU & IO", 0x4018, 0x0008, 2), + ]; + // let mut cur = 0x4020; + segments.push(Segment::rom("PROG ROM", 0x8000, prg_rom)); + Self { + cycle: 7, + dbg_int: false, + halted: false, + + clock_count: 0, + memory: MemoryMap::new(segments), + cpu: Cpu::init(), + ppu: PPU::with_chr_rom(chr_rom), + apu: APU::init(), + controller: Controllers::init(), + dma: DmaState::Passive, + last_instruction: "".into(), + } + } + + pub fn read(&mut self, addr: u16) -> u8 { + self.memory.read(addr).reg_map(|reg, offset| match reg { + CPUMMRegisters::PPU => self.ppu.read_reg(offset), + CPUMMRegisters::APU => match offset { + 0x16 => self.controller.read_joy1(), + 0x17 => self.controller.read_joy2(), + _ => self.apu.read_reg(offset), + }, + }) + } + + pub fn peek(&self, addr: u16) -> u8 { + self.memory.read(addr).reg_map(|_, _| todo!()) + } + + pub fn write(&mut self, addr: u16, val: u8) { + self.memory.write(addr, val, |reg, offset, val| match reg { + CPUMMRegisters::PPU => self.ppu.write_reg(offset, val), + CPUMMRegisters::APU => match offset { + 0x14 => { + self.dma = DmaState::Running { + cpu_addr: u16::from_le_bytes([0, val]), + rem: 0xFF, + read: None, + } + } + 0x16 => self.controller.write_joy_strobe(val), + 0x17 => (), // TODO: frame counter control + _ => self.apu.write_reg(offset, val), + }, + }); + } + + pub fn read_abs(&mut self, low: u8, high: u8) -> u8 { + self.read(u16::from_le_bytes([low, high])) + } + pub fn read_abs_x(&mut self, low: u8, high: u8) -> u8 { + self.read(u16::from_le_bytes([low, high]) + self.cpu.x as u16) + } + pub fn read_abs_y(&mut self, low: u8, high: u8) -> u8 { + self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16) + } + pub fn read_zp_x(&mut self, low: u8) -> u8 { + self.read(u16::from_le_bytes([low.wrapping_add(self.cpu.x), 0])) + } + pub fn read_zp_y(&mut self, low: u8) -> u8 { + self.read(u16::from_le_bytes([low.wrapping_add(self.cpu.y), 0])) + } + pub fn write_abs(&mut self, low: u8, high: u8, val: u8) { + self.write(u16::from_le_bytes([low, high]), val) + } + pub fn write_abs_x(&mut self, low: u8, high: u8, val: u8) { + self.write(u16::from_le_bytes([low, high]) + self.cpu.x as u16, val) + } + pub fn write_abs_y(&mut self, low: u8, high: u8, val: u8) { + self.write(u16::from_le_bytes([low, high]) + self.cpu.y as u16, val) + } + pub fn write_zp_x(&mut self, low: u8, val: u8) { + self.write(u16::from_le_bytes([low.wrapping_add(self.cpu.x), 0]), val) + } + pub fn write_zp_y(&mut self, low: u8, val: u8) { + self.write(u16::from_le_bytes([low.wrapping_add(self.cpu.y), 0]), val) + } + + pub fn push(&mut self, val: u8) { + self.write(self.cpu.sp as u16 + 0x100, val); + self.cpu.sp = self.cpu.sp.wrapping_sub(1); + } + pub fn pop(&mut self) -> u8 { + self.cpu.sp = self.cpu.sp.wrapping_add(1); + self.read(self.cpu.sp as u16 + 0x100) + } + pub fn push_16(&mut self, val: u16) { + debug!("Writing rc = {:04X} at {:02X}", val, self.cpu.sp); + let [low, high] = val.to_le_bytes(); + self.push(high); + self.push(low); + } + pub fn pop_16(&mut self) -> u16 { + let low = self.pop(); + let high = self.pop(); + u16::from_le_bytes([low, high]) + } + + fn jmp_rel(&mut self, off: u8) { + if off < 128 { + self.cpu.pc = self.cpu.pc.wrapping_add(off as u16); + } else { + self.cpu.pc = self.cpu.pc.wrapping_sub(0x100 - off as u16); + } + } + + fn halt(&mut self) { + self.halted = true; + } + + /// Returns true if more bytes are needed + fn exec_instruction(&mut self, ins: u8, mut params: &[u8], held: bool) -> ExecState { + macro_rules! inst { + ($val:expr, $hold:expr, |$($name:pat_param),*| $eval:expr) => {{ + let hold_time: u8 = ($hold).into(); + if params.len() < ([$(inst!(@EMPTY $name)),*] as [(); _]).len() { + ExecState::MoreParams + } else if !held && hold_time != 0 { + ExecState::Hold(hold_time - 1) + } else { + debug!("Running 0x{:04X} {} :{:X} {:X?}", self.cpu.pc - (1 + params.len() as u16), $val, ins, params); + // debug!("Running 0x{:04X} {} :{:X} {:X?}", self.cpu.pc - (1 + params.len() as u16), $val, ins, params); + self.last_instruction = format!("0x{:04X} {} :{:X} {:X?}", self.cpu.pc - (1 + params.len() as u16), $val, ins, params); + $( + let $name = params[0]; + #[allow(unused_assignments)] + { params = ¶ms[1..]; } + )* + {$eval} + // self.cpu.status.set_interrupt_disable(true); + ExecState::Done + } + }}; + ($name:expr, $hold:expr, || $eval:expr) => { + inst!($name, $hold, | | $eval) + }; + (@EMPTY $n:pat_param) => { + () + }; + } + match ins { + 0x02 => inst!("HLT", 0, || self.halt()), + 0x38 => inst!("SEC", 1, || self.cpu.status.set_carry(true)), + 0x18 => inst!("CLC", 1, || self.cpu.status.set_carry(false)), + 0x78 => inst!("SEI", 1, || self.cpu.status.set_interrupt_disable(true)), + 0x58 => inst!("CLI", 1, || self.cpu.status.set_interrupt_disable(false)), + 0xF8 => inst!("SED", 1, || self.cpu.status.set_decimal(true)), + 0xD8 => inst!("CLD", 1, || self.cpu.status.set_decimal(false)), + 0xB8 => inst!("CLV", 1, || self.cpu.status.set_overflow(false)), + 0x00 => inst!("BRK", 7, |_ignored| { + self.push_16(self.cpu.pc); + self.push(self.cpu.status.0 | 0b00110000); + self.cpu.status.set_interrupt_disable(true); + self.cpu.pc = u16::from_le_bytes([self.read(0xFFFE), self.read(0xFFFF)]); + }), + + 0x48 => inst!("PHA", 2, || { + self.push(self.cpu.a); + }), + 0x08 => inst!("PHP", 2, || { + self.push(self.cpu.status.0 | 0b0011_0000); + }), + 0x68 => inst!("PLA", 3, || { + self.cpu.a = self.pop(); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x28 => inst!("PLP", 3, || { + self.cpu.status.0 = self.pop() & 0b1100_1111; + }), + + // Loads + 0xA9 => inst!("LDA imm", 0, |a| { + self.cpu.a = a; + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xA5 => inst!("LDA zp", 1, |off| { + self.cpu.a = self.read(off as u16); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xB5 => inst!("LDA zp,x", 2, |off| { + self.cpu.a = self.read_zp_x(off); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xAD => inst!("LDA abs", 1, |low, high| { + self.cpu.a = self.read_abs(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xBD => inst!("LDA abs,x", 1, |low, high| { + self.cpu.a = self.read_abs_x(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xB9 => inst!("LDA abs,y", 1, |low, high| { + self.cpu.a = self.read_abs_y(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xA1 => inst!("LDA (ind,x)", 4, |off| { + let low = self.read_zp_x(off); + let high = self.read_zp_x(off.wrapping_add(1)); + self.cpu.a = self.read_abs(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xB1 => inst!("LDA (ind),y", 3, |off| { + let low = self.read_abs(off, 0); + let high = self.read_abs(off.wrapping_add(1), 0); + self.cpu.a = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xA2 => inst!("LDX imm", 0, |x| { + self.cpu.x = x; + self.cpu.status.set_zero(self.cpu.x == 0); + self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); + }), + 0xA6 => inst!("LDX zp", 1, |off| { + self.cpu.x = self.read(off as u16); + self.cpu.status.set_zero(self.cpu.x == 0); + self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); + }), + 0xB6 => inst!("LDX zp,y", 1, |off| { + self.cpu.x = self.read_zp_y(off); + self.cpu.status.set_zero(self.cpu.x == 0); + self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); + }), + 0xAE => inst!("LDX abs", 1, |low, high| { + self.cpu.x = self.read_abs(low, high); + self.cpu.status.set_zero(self.cpu.x == 0); + self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); + }), + 0xBE => inst!("LDX abs,y", 1, |low, high| { + self.cpu.x = self.read_abs_y(low, high); + self.cpu.status.set_zero(self.cpu.x == 0); + self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); + }), + 0xA0 => inst!("LDy imm", 0, |y| { + self.cpu.y = y; + self.cpu.status.set_zero(self.cpu.y == 0); + self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); + }), + 0xA4 => inst!("LDY zp", 1, |off| { + self.cpu.y = self.read(off as u16); + self.cpu.status.set_zero(self.cpu.y == 0); + self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); + }), + 0xB4 => inst!("LDX zp,x", 1, |off| { + self.cpu.y = self.read_zp_x(off); + self.cpu.status.set_zero(self.cpu.y == 0); + self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); + }), + 0xAC => inst!("LDX abs", 1, |low, high| { + self.cpu.y = self.read_abs(low, high); + self.cpu.status.set_zero(self.cpu.y == 0); + self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); + }), + 0xBC => inst!("LDX abs,x", 1, |low, high| { + self.cpu.y = self.read_abs_x(low, high); + self.cpu.status.set_zero(self.cpu.y == 0); + self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); + }), + + // Stores + 0x85 => inst!("STA zp", 1, |off| { + self.write(off as u16, self.cpu.a); + }), + 0x95 => inst!("STA zp,x", 2, |off| { + self.write_zp_x(off, self.cpu.a); + }), + 0x8D => inst!("STA abs", 1, |low, high| { + self.write_abs(low, high, self.cpu.a); + }), + 0x9D => inst!("STA abs,x", 1, |low, high| { + self.write_abs_x(low, high, self.cpu.a); + }), + 0x99 => inst!("STA abs,y", 1, |low, high| { + self.write_abs_y(low, high, self.cpu.a); + }), + 0x81 => inst!("STA (ind,x)", 4, |off| { + let low = self.read_zp_x(off); + let high = self.read_zp_x(off.wrapping_add(1)); + self.write_abs(low, high, self.cpu.a); + }), + 0x91 => inst!("STA (ind),y", 4, |off| { + let low = self.read_abs(off, 0); + let high = self.read_abs(off.wrapping_add(1), 0); + self.write( + u16::from_le_bytes([low, high]) + self.cpu.y as u16, + self.cpu.a, + ); + }), + 0x86 => inst!("STX zp", 1, |off| { + self.write(off as u16, self.cpu.x); + }), + 0x96 => inst!("STX zp,y", 2, |off| { + self.write_zp_y(off, self.cpu.x); + }), + 0x8E => inst!("STX abs", 1, |low, high| { + self.write_abs(low, high, self.cpu.x); + }), + 0x84 => inst!("STY zp", 1, |off| { + self.write(off as u16, self.cpu.y); + }), + 0x94 => inst!("STY zp,x", 2, |off| { + self.write_zp_x(off, self.cpu.y); + }), + 0x8C => inst!("STY abs", 1, |low, high| { + self.write_abs(low, high, self.cpu.y); + }), + + // Transfers + 0xAA => inst!("TAX", 1, || { + self.cpu.x = self.cpu.a; + self.cpu.status.set_zero(self.cpu.x == 0); + self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); + }), + 0xA8 => inst!("TAY", 1, || { + self.cpu.y = self.cpu.a; + self.cpu.status.set_zero(self.cpu.y == 0); + self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); + }), + 0xBA => inst!("TSX", 1, || { + self.cpu.x = self.cpu.sp; + self.cpu.status.set_zero(self.cpu.x == 0); + self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); + }), + 0x8A => inst!("TXA", 1, || { + self.cpu.a = self.cpu.x; + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x9A => inst!("TXS", 1, || { + self.cpu.sp = self.cpu.x; + }), + 0x98 => inst!("TYA", 1, || { + self.cpu.a = self.cpu.y; + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + + // Branch + 0x90 => inst!("BCC", !self.cpu.status.carry(), |off| { + if !self.cpu.status.carry() { + self.jmp_rel(off); + } + }), + 0xB0 => inst!("BCS", self.cpu.status.carry(), |off| { + if self.cpu.status.carry() { + self.jmp_rel(off); + } + }), + 0xF0 => inst!("BEQ", self.cpu.status.zero(), |off| { + if self.cpu.status.zero() { + self.jmp_rel(off); + } + }), + 0x30 => inst!("BMI", self.cpu.status.negative(), |off| { + if self.cpu.status.negative() { + self.jmp_rel(off); + } + }), + 0xD0 => inst!("BNE", !self.cpu.status.zero(), |off| { + if !self.cpu.status.zero() { + self.jmp_rel(off); + } + }), + 0x10 => inst!("BPL", !self.cpu.status.negative(), |off| { + if !self.cpu.status.negative() { + self.jmp_rel(off); + } + }), + 0x4C => inst!("JMP abs", 3, |low, high| { + self.cpu.pc = u16::from_le_bytes([low, high]); + }), + 0x6C => inst!("JMP abs", 3, |low, high| { + self.cpu.pc = u16::from_le_bytes([ + self.read_abs(low, high), + self.read_abs(low.wrapping_add(1), high), // Known CPU bug + ]); + }), + 0x20 => inst!("JSR", 3, |low, high| { + self.push_16(self.cpu.pc - 1); + self.cpu.pc = u16::from_le_bytes([low, high]); + }), + 0x60 => inst!("RTS", 5, || { + self.cpu.pc = self.pop_16() + 1; + }), + 0x40 => inst!("RTI", 5, || { + self.cpu.status.0 = self.pop() & 0b1100_1111; + self.cpu.pc = self.pop_16(); + }), + + // CMP + 0xC9 => inst!("CMP imm", 0, |val| { + let v = self.cpu.a.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.a >= val); + }), + 0xC5 => inst!("CMP zp", 1, |off| { + let val = self.read_abs(off, 0); + let v = self.cpu.a.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.a >= val); + }), + 0xD5 => inst!("CMP zp,x", 2, |off| { + let val = self.read_zp_x(off); + let v = self.cpu.a.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.a >= val); + }), + 0xCD => inst!("CMP abs", 1, |low, high| { + let val = self.read_abs(low, high); + let v = self.cpu.a.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.a >= val); + }), + 0xDD => inst!("CMP abs,x", 1, |low, high| { + let val = self.read_abs_x(low, high); + let v = self.cpu.a.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.a >= val); + }), + 0xD9 => inst!("CMP abs,y", 1, |low, high| { + let val = self.read_abs_y(low, high); + let v = self.cpu.a.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.a >= val); + }), + 0xE0 => inst!("CPX imm", 0, |val| { + let v = self.cpu.x.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.x >= val); + }), + 0xE4 => inst!("CPX zp", 1, |off| { + let val = self.read_abs(off, 0); + let v = self.cpu.x.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.x >= val); + }), + 0xEC => inst!("CPX zp", 1, |low, high| { + let val = self.read_abs(low, high); + let v = self.cpu.x.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.x >= val); + }), + 0xC0 => inst!("CPY imm", 0, |val| { + let v = self.cpu.y.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.y >= val); + }), + 0xC4 => inst!("CPY zp", 1, |off| { + let val = self.read_abs(off, 0); + let v = self.cpu.y.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.y >= val); + }), + 0xCC => inst!("CPY zp", 1, |low, high| { + let val = self.read_abs(low, high); + let v = self.cpu.y.wrapping_sub(val); + self.cpu.status.set_zero(v == 0); + self.cpu.status.set_negative(v & 0x80 == 0x80); + self.cpu.status.set_carry(self.cpu.y >= val); + }), + + // Arithmetic + 0x69 => inst!("ADC imm", 0, |val| { + let (a, carry_1) = self.cpu.a.overflowing_add(val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x65 => inst!("ADC zp", 0, |off| { + let val = self.read_abs(off, 0); + let (a, carry_1) = self.cpu.a.overflowing_add(val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x75 => inst!("ADC zp,x", 0, |off| { + let val = self.read_zp_x(off); + let (a, carry_1) = self.cpu.a.overflowing_add(val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x6D => inst!("ADC abs", 0, |low, high| { + let val = self.read_abs(low, high); + let (a, carry_1) = self.cpu.a.overflowing_add(val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x7D => inst!("ADC abs,x", 0, |low, high| { + let val = self.read_abs_x(low, high); + let (a, carry_1) = self.cpu.a.overflowing_add(val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x79 => inst!("ADC abs,y", 0, |low, high| { + let val = self.read_abs_y(low, high); + let (a, carry_1) = self.cpu.a.overflowing_add(val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x61 => inst!("ADC (ind,x)", 0, |off| { + let low = self.read_abs(off, 0); + let high = self.read_abs(off.wrapping_add(1), 0); + let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); + let (a, carry_1) = self.cpu.a.overflowing_add(val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x71 => inst!("ADC (ind),y", 0, |off| { + let low = self.read_abs(off, 0); + let high = self.read_abs(off.wrapping_add(1), 0); + let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); + let (a, carry_1) = self.cpu.a.overflowing_add(val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xE9 => inst!("SBC imm", 0, |val| { + let (a, carry_1) = self.cpu.a.overflowing_add(!val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); + self.cpu.a = a; + // TODO: I'm pretty sure the carry logic is wrong here + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xE5 => inst!("SBC zp", 0, |off| { + let val = self.read_abs(off, 0); + let (a, carry_1) = self.cpu.a.overflowing_add(!val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xF5 => inst!("SBC zp,x", 0, |off| { + let val = self.read_zp_x(off); + let (a, carry_1) = self.cpu.a.overflowing_add(!val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xED => inst!("SBC abs", 0, |low, high| { + let val = self.read_abs(low, high); + let (a, carry_1) = self.cpu.a.overflowing_add(!val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xFD => inst!("SBC abs,x", 0, |low, high| { + let val = self.read_abs_x(low, high); + let (a, carry_1) = self.cpu.a.overflowing_add(!val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xF9 => inst!("SBC abs,y", 0, |low, high| { + let val = self.read_abs_y(low, high); + let (a, carry_1) = self.cpu.a.overflowing_add(!val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xE1 => inst!("SBC (ind,x)", 0, |off| { + let low = self.read_abs(off, 0); + let high = self.read_abs(off.wrapping_add(1), 0); + let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); + let (a, carry_1) = self.cpu.a.overflowing_add(!val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xF1 => inst!("SBC (ind),y", 0, |off| { + let low = self.read_abs(off, 0); + let high = self.read_abs(off.wrapping_add(1), 0); + let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); + let (a, carry_1) = self.cpu.a.overflowing_add(!val); + let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); + self.cpu + .status + .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); + self.cpu.a = a; + self.cpu.status.set_carry(carry_1 | carry_2); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0xC6 => inst!("DEC zp", 3, |off| { + let val = self.read_abs(off, 0); + self.write_abs(off, 0, val.wrapping_sub(1)); + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(val & 0x80 == 0x80); + }), + 0xD6 => inst!("DEC zp,x", 4, |off| { + let val = self.read_zp_x(off); + self.write_zp_x(off, val.wrapping_sub(1)); + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(val & 0x80 == 0x80); + }), + 0xCE => inst!("DEC abs", 3, |low, high| { + let val = self.read_abs(low, high); + self.write_abs(low, high, val.wrapping_sub(1)); + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(val & 0x80 == 0x80); + }), + 0xDE => inst!("DEC abs,x", 3, |low, high| { + let val = self.read_abs_x(low, high); + self.write_abs_x(low, high, val.wrapping_sub(1)); + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(val & 0x80 == 0x80); + }), + 0xCA => inst!("DEX", 1, || { + self.cpu.x = self.cpu.x.wrapping_sub(1); + self.cpu.status.set_zero(self.cpu.x == 0); + self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); + }), + 0x88 => inst!("DEY", 1, || { + self.cpu.y = self.cpu.y.wrapping_sub(1); + self.cpu.status.set_zero(self.cpu.y == 0); + self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); + }), + 0xE6 => inst!("INC zp", 3, |off| { + let val = self.read_abs(off, 0); + self.write_abs(off, 0, val.wrapping_add(1)); + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(val & 0x80 == 0x80); + }), + 0xF6 => inst!("INC zp,x", 4, |off| { + let val = self.read_zp_x(off); + self.write_zp_x(off, val.wrapping_add(1)); + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(val & 0x80 == 0x80); + }), + 0xEE => inst!("INC abs", 3, |low, high| { + let val = self.read_abs(low, high); + self.write_abs(low, high, val.wrapping_add(1)); + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(val & 0x80 == 0x80); + }), + 0xFE => inst!("INC abs,x", 3, |low, high| { + let val = self.read_abs_x(low, high); + self.write_abs_x(low, high, val.wrapping_add(1)); + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(val & 0x80 == 0x80); + }), + 0xE8 => inst!("INX", 1, || { + self.cpu.x = self.cpu.x.wrapping_add(1); + self.cpu.status.set_zero(self.cpu.x == 0); + self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); + }), + 0xC8 => inst!("INY", 1, || { + self.cpu.y = self.cpu.y.wrapping_add(1); + self.cpu.status.set_zero(self.cpu.y == 0); + self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); + }), + + // Logical + 0x09 => inst!("ORA imm", 0, |val| { + self.cpu.a |= val; + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x05 => inst!("ORA zp", 1, |val| { + self.cpu.a |= self.read_abs(val, 0); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x15 => inst!("ORA zp,x", 2, |val| { + self.cpu.a |= self.read_zp_x(val); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x0D => inst!("ORA abs", 2, |low, high| { + self.cpu.a |= self.read_abs(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x1D => inst!("ORA abs,x", 2, |low, high| { + self.cpu.a |= self.read_abs_x(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x19 => inst!("ORA abs,y", 1, |low, high| { + self.cpu.a |= self.read_abs_y(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x01 => inst!("ORA (ind,x)", 4, |off| { + let low = self.read_zp_x(off); + let high = self.read_zp_x(off.wrapping_add(1)); + self.cpu.a |= self.read_abs(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x11 => inst!("ORA (ind),y", 4, |off| { + let low = self.read_abs(off, 0); + let high = self.read_abs(off.wrapping_add(1), 0); + self.cpu.a |= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x29 => inst!("AND imm", 0, |val| { + self.cpu.a &= val; + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x25 => inst!("AND zp", 1, |val| { + self.cpu.a &= self.read_abs(val, 0); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x35 => inst!("AND zp,x", 2, |val| { + self.cpu.a &= self.read_zp_x(val); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x2D => inst!("AND abs", 2, |low, high| { + self.cpu.a &= self.read_abs(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x3D => inst!("AND abs,x", 2, |low, high| { + self.cpu.a &= self.read_abs_x(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x39 => inst!("AND abs,y", 1, |low, high| { + self.cpu.a &= self.read_abs_y(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x21 => inst!("AND (ind,x)", 4, |off| { + let low = self.read_zp_x(off); + let high = self.read_zp_x(off.wrapping_add(1)); + self.cpu.a &= self.read_abs(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x31 => inst!("AND (ind),y", 4, |off| { + let low = self.read_abs(off, 0); + let high = self.read_abs(off.wrapping_add(1), 0); + self.cpu.a &= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x49 => inst!("EOR imm", 0, |val| { + self.cpu.a ^= val; + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x45 => inst!("EOR zp", 1, |val| { + self.cpu.a ^= self.read_abs(val, 0); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x55 => inst!("EOR zp,x", 2, |val| { + self.cpu.a ^= self.read_zp_x(val); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x4D => inst!("EOR abs", 1, |low, high| { + self.cpu.a ^= self.read_abs(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x5D => inst!("EOR abs,x", 1, |low, high| { + self.cpu.a ^= self.read_abs_x(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x59 => inst!("EOR abs,y", 1, |low, high| { + self.cpu.a ^= self.read_abs_y(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x41 => inst!("EOR (ind,x)", 4, |off| { + let low = self.read_zp_x(off); + let high = self.read_zp_x(off.wrapping_add(1)); + self.cpu.a ^= self.read_abs(low, high); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x51 => inst!("EOR (ind),y", 3, |off| { + let low = self.read_abs(off, 0); + let high = self.read_abs(off.wrapping_add(1), 0); + self.cpu.a ^= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); + }), + 0x24 => inst!("BIT zp", 1, |val| { + let val = self.cpu.a & self.read_abs(val, 0); + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_overflow(val & 0x40 == 0x40); + self.cpu.status.set_negative(val & 0x80 == 0x80); + }), + 0x2C => inst!("BIT abs", 1, |low, high| { + let val = self.cpu.a & self.read_abs(low, high); + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_overflow(val & 0x40 == 0x40); + self.cpu.status.set_negative(val & 0x80 == 0x80); + }), + + // Shifts + 0x4A => inst!("LSR A", 1, || { + self.cpu.status.set_carry(self.cpu.a & 0b1 == 0b1); + self.cpu.a = self.cpu.a >> 1; + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(false); + }), + 0x46 => inst!("LSR zp", 3, |off| { + let val = self.read_abs(off, 0); + self.cpu.status.set_carry(val & 0b1 == 0b1); + let val = val >> 1; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs(off, 0, val); + }), + 0x56 => inst!("LSR zp,x", 4, |off| { + let val = self.read_zp_x(off); + self.cpu.status.set_carry(val & 0b1 == 0b1); + let val = val >> 1; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_zp_x(off, val); + }), + 0x4E => inst!("LSR abs", 3, |low, high| { + let val = self.read_abs(low, high); + self.cpu.status.set_carry(val & 0b1 == 0b1); + let val = val >> 1; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs(low, high, val); + }), + 0x5E => inst!("LSR zp,x", 4, |low, high| { + let val = self.read_abs_x(low, high); + self.cpu.status.set_carry(val & 0b1 == 0b1); + let val = val >> 1; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs_x(low, high, val); + }), + 0x0A => inst!("ASL A", 1, || { + self.cpu.status.set_carry(self.cpu.a & 0x80 == 0x80); + self.cpu.a = self.cpu.a << 1; + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(false); + }), + 0x06 => inst!("ASL zp", 3, |off| { + let val = self.read_abs(off, 0); + self.cpu.status.set_carry(val & 0x80 == 0x80); + let val = val << 1; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs(off, 0, val); + }), + 0x16 => inst!("ASL zp,x", 4, |off| { + let val = self.read_zp_x(off); + self.cpu.status.set_carry(val & 0x80 == 0x80); + let val = val << 1; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_zp_x(off, val); + }), + 0x0E => inst!("ASL abs", 3, |low, high| { + let val = self.read_abs(low, high); + self.cpu.status.set_carry(val & 0x80 == 0x80); + let val = val << 1; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs(low, high, val); + }), + 0x1E => inst!("ASL zp,x", 4, |low, high| { + let val = self.read_abs_x(low, high); + self.cpu.status.set_carry(val & 0x80 == 0x80); + let val = val << 1; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs_x(low, high, val); + }), + 0x6A => inst!("ROR A", 1, || { + let old_carry = if self.cpu.status.carry() { 0x80 } else { 0x00 }; + self.cpu.status.set_carry(self.cpu.a & 0b1 == 0b1); + self.cpu.a = self.cpu.a >> 1 | old_carry; + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(false); + }), + 0x66 => inst!("ROR zp", 3, |off| { + let old_carry = if self.cpu.status.carry() { 0x80 } else { 0x00 }; + let val = self.read_abs(off, 0); + self.cpu.status.set_carry(val & 0b1 == 0b1); + let val = val >> 1 | old_carry; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs(off, 0, val); + }), + 0x76 => inst!("ROR zp,x", 4, |off| { + let old_carry = if self.cpu.status.carry() { 0x80 } else { 0x00 }; + let val = self.read_zp_x(off); + self.cpu.status.set_carry(val & 0b1 == 0b1); + let val = val >> 1 | old_carry; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_zp_x(off, val); + }), + 0x6E => inst!("ROR abs", 3, |low, high| { + let old_carry = if self.cpu.status.carry() { 0x80 } else { 0x00 }; + let val = self.read_abs(low, high); + self.cpu.status.set_carry(val & 0b1 == 0b1); + let val = val >> 1 | old_carry; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs(low, high, val); + }), + 0x7E => inst!("ROR zp,x", 4, |low, high| { + let old_carry = if self.cpu.status.carry() { 0x80 } else { 0x00 }; + let val = self.read_abs_x(low, high); + self.cpu.status.set_carry(val & 0b1 == 0b1); + let val = val >> 1 | old_carry; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs_x(low, high, val); + }), + 0x2A => inst!("ROL A", 1, || { + let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 }; + self.cpu.status.set_carry(self.cpu.a & 0x80 == 0x80); + self.cpu.a = self.cpu.a << 1 | old_carry; + self.cpu.status.set_zero(self.cpu.a == 0); + self.cpu.status.set_negative(false); + }), + 0x26 => inst!("ROL zp", 3, |off| { + let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 }; + let val = self.read_abs(off, 0); + self.cpu.status.set_carry(val & 0x80 == 0x80); + let val = val << 1 | old_carry; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs(off, 0, val); + }), + 0x36 => inst!("ROL zp,x", 4, |off| { + let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 }; + let val = self.read_zp_x(off); + self.cpu.status.set_carry(val & 0x80 == 0x80); + let val = val << 1 | old_carry; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_zp_x(off, val); + }), + 0x2E => inst!("ROL abs", 3, |low, high| { + let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 }; + let val = self.read_abs(low, high); + self.cpu.status.set_carry(val & 0x80 == 0x80); + let val = val << 1 | old_carry; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs(low, high, val); + }), + 0x3E => inst!("ROL zp,x", 4, |low, high| { + let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 }; + let val = self.read_abs_x(low, high); + self.cpu.status.set_carry(val & 0x80 == 0x80); + let val = val << 1 | old_carry; + self.cpu.status.set_zero(val == 0); + self.cpu.status.set_negative(false); + self.write_abs_x(low, high, val); + }), + + 0xEA => inst!("NOP", 1, || {}), + _ => todo!("ins: 0x{:04X}: 0x{ins:X}, {params:X?}", self.cpu.pc - 1), + } + } + + pub fn peek_opcode(&self) -> u8 { + self.peek(self.cpu.pc) + } + + fn clock_cpu(&mut self) { + self.cpu.clock_state = match self.cpu.clock_state { + ClockState::HoldNmi { cycles } => { + if cycles == 0 { + self.push_16(self.cpu.pc); + self.push(self.cpu.status.0); + self.cpu.pc = u16::from_le_bytes([self.read(0xFFFA), self.read(0xFFFB)]); + ClockState::ReadInstruction + } else { + ClockState::HoldNmi { cycles: cycles - 1 } + } + } + ClockState::HoldIrq { cycles } => { + if cycles == 0 { + todo!("Run NMI"); + } else { + ClockState::HoldIrq { cycles: cycles - 1 } + } + } + ClockState::ReadInstruction => { + if self.ppu.nmi_waiting() || self.apu.nmi_waiting() { + ClockState::HoldNmi { cycles: 6 } + } else if self.ppu.irq_waiting() || self.apu.irq_waiting() { + ClockState::HoldIrq { cycles: 6 } + } else { + let instruction = self.read(self.cpu.pc); + self.cpu.pc = self.cpu.pc.wrapping_add(1); + match self.exec_instruction(instruction, &[], false) { + ExecState::Done => ClockState::ReadInstruction, + ExecState::MoreParams => ClockState::ReadOperands { + instruction, + ops: [0u8; 5], + count: 0, + }, + ExecState::Hold(cycles) => ClockState::Hold { + cycles, + instruction, + ops: [0u8; 5], + count: 0, + }, + } + } + } + ClockState::ReadOperands { + instruction, + mut ops, + count, + } => { + if count == 5 { + todo!() + } + ops[count as usize] = self.read(self.cpu.pc); + self.cpu.pc = self.cpu.pc.wrapping_add(1); + match self.exec_instruction(instruction, &ops[..count as usize + 1], false) { + ExecState::Done => ClockState::ReadInstruction, + ExecState::MoreParams => ClockState::ReadOperands { + instruction, + ops, + count: count + 1, + }, + ExecState::Hold(cycles) => ClockState::Hold { + cycles, + instruction, + ops, + count: count + 1, + }, + } + } + ClockState::Hold { + cycles, + instruction, + ops, + count, + } => { + if cycles == 0 { + match self.exec_instruction(instruction, &ops[..count as usize], true) { + ExecState::Done => ClockState::ReadInstruction, + ExecState::MoreParams => ClockState::ReadOperands { + instruction, + ops, + count: count + 1, + }, + ExecState::Hold(_) => panic!("Should never return Hold after holding"), + } + } else { + ClockState::Hold { + cycles: cycles - 1, + instruction, + ops, + count, + } + } + } + }; + } + + fn cpu_cycle(&mut self) { + self.cycle += 1; + match self.dma { + DmaState::Passive => self.clock_cpu(), + // TODO: Validate that this takes the correct number of cycles (513 or 514 cycles) + DmaState::Running { + cpu_addr, + rem, + read: Some(read), + } => { + self.ppu.write_reg(4, read); + debug!("OAM DMA write {:02X}", read); + if rem == 0 { + self.dma = DmaState::Passive; + } else { + self.dma = DmaState::Running { + cpu_addr: cpu_addr + 1, + rem: rem - 1, + read: None, + }; + } + } + DmaState::Running { + cpu_addr, + rem, + read: None, + } => { + let read = self.read(cpu_addr); + debug!("OAM DMA read {:04X} {:02X}", cpu_addr, read); + self.dma = DmaState::Running { + cpu_addr, + rem, + read: Some(read), + }; + } + } + if [0x8031, 0x8014].contains(&self.cpu.pc) { + self.dbg_int = true; + } + } + + pub fn run_one_clock_cycle(&mut self) -> CycleResult { + if self.halted { + return CycleResult { + cpu_exec: false, + ppu_frame: false, + dma: false, + dbg_int: true, + }; + } + self.clock_count += 1; + let cpu_exec = if self.clock_count % 3 == 1 { + self.cpu_cycle(); + matches!(self.cpu.clock_state, ClockState::ReadInstruction) + } else { + false + }; + // 3 PPU clock cycles for each CPU clock cycle + let ppu_frame = self.ppu.run_one_clock_cycle(); + let dbg_int = self.dbg_int | self.ppu.dbg_int; + self.dbg_int = false; + self.ppu.dbg_int = false; + CycleResult { + cpu_exec, + ppu_frame, + dma: matches!(self.dma, DmaState::Running { .. }), + dbg_int, + } + } + + pub fn reset(&mut self) { + self.cpu.pc = u16::from_le_bytes([self.read(0xFFFC), self.read(0xFFFD)]); + self.cpu.sp = 0xFD; + self.cpu.status.set_interrupt_disable(true); + self.ppu.reset(); + } + + pub fn power_cycle(&mut self) { + // self.memory.clear(); + // self.ppu.reset(); + // self.ppu.memory.clear(); + *self = Self::from_rom(self.memory.rom(6).expect("PRG ROM"), self.ppu.memory.rom(0).expect("CHR ROM")); + self.reset(); + } + + pub fn reset_and_run(&mut self) { + self.reset(); + while !self.halted { + // info!("Running clock cycle: {}", self.clock_count); + self.run_one_clock_cycle(); + } + } + + pub fn reset_and_run_with_timeout(&mut self, max_clock_cycles: usize) { + self.reset(); + let mut cur = 0; + while !self.halted && cur < max_clock_cycles { + // info!("Running clock cycle: {}", self.clock_count); + self.run_one_clock_cycle(); + cur += 1; + } + } + + pub fn image(&self) -> &RenderBuffer<256, 240> { + &self.ppu.render_buffer + } + + pub fn ppu(&self) -> &PPU { + &self.ppu + } + + pub fn cpu_mem(&self) -> &impl Memory { + &self.memory + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..56cf08e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,310 @@ +use std::{ + collections::HashMap, + fmt, + io::{Read, stdin}, +}; + +use iced::{ + Color, Element, Font, + Length::Fill, + Point, Renderer, Size, Subscription, Task, Theme, Vector, + futures::io::Window, + mouse, + widget::{ + Canvas, button, + canvas::{Frame, Program}, + column, container, row, text, + }, + window::{self, Id, Settings}, +}; +use nes_emu::{ + NES, PPU, RenderBuffer, + header_menu::header_menu, + hex_view::{self, HexEvent, HexView}, +}; +use tracing::{debug, info}; +use tracing_subscriber::EnvFilter; + +extern crate nes_emu; + +fn main() -> Result<(), iced::Error> { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + iced::daemon(Emulator::title, Emulator::update, Emulator::view) + .subscription(Emulator::subscriptions) + .theme(|_, _| Theme::Dark) + .run_with(Emulator::new) + + // iced::application(title, Emulator::update, Emulator::view) + // .subscription(Emulator::subscriptions) + // .theme(|_| Theme::Dark) + // .centered() + // .run() +} + +enum MemoryTy { + Cpu, +} + +enum WindowType { + Main, + Memory(MemoryTy, HexView), + TileMap, + TileViewer, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum HeaderButton { + OpenMemory, + OpenTileMap, + OpenTileViewer, + Reset, + PowerCycle, +} + +impl fmt::Display for HeaderButton { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::OpenMemory => write!(f, "Open Memory Viewer"), + Self::OpenTileMap => write!(f, "Open TileMap Viewer"), + Self::OpenTileViewer => write!(f, "Open Tile Viewer"), + Self::Reset => write!(f, "Reset"), + Self::PowerCycle => write!(f, "Power Cycle"), + } + } +} + +struct Emulator { + nes: NES, + windows: HashMap, +} + +#[derive(Debug, Clone)] +enum Message { + Tick(usize), + Frame, + DMA, + CPU, + DebugInt, + WindowOpened(Id), + WindowClosed(Id), + Header(HeaderButton), + Hex(Id, HexEvent), +} + +impl Emulator { + fn new() -> (Self, Task) { + let rom_file = concat!(env!("ROM_DIR"), "/", "read_write.nes"); + // let rom_file = "./Super Mario Bros. (World).nes"; + let mut nes = nes_emu::NES::load_nes_file(rom_file) + .expect("Failed to load nes file"); + nes.reset(); + let (win, task) = iced::window::open(Settings::default()); + ( + Self { + nes, + windows: HashMap::from_iter([(win, WindowType::Main)]), + }, + task.map(Message::WindowOpened), + ) + } + fn title(&self, win: Id) -> String { + match self.windows.get(&win) { + Some(WindowType::Main) => "NES emu".into(), + Some(WindowType::Memory(_, _)) => "NES MemoryView".into(), + Some(WindowType::TileMap) => "NES TileMap".into(), + Some(WindowType::TileViewer) => "NES Tile Viewer".into(), + None => todo!(), + } + } + + fn open(&mut self, ty: WindowType) -> Task { + let (win, task) = iced::window::open(Settings::default()); + self.windows.insert(win, ty); + return task.map(Message::WindowOpened); + } + + fn update(&mut self, message: Message) -> Task { + match message { + Message::Tick(count) => { + for _ in 0..count { + self.nes.run_one_clock_cycle(); + } + } + Message::Frame => while !self.nes.run_one_clock_cycle().ppu_frame {}, + Message::DMA => while !self.nes.run_one_clock_cycle().dma {}, + Message::CPU => while !self.nes.run_one_clock_cycle().cpu_exec {}, + Message::DebugInt => while !self.nes.run_one_clock_cycle().dbg_int {}, + Message::WindowOpened(id) => { + // Window + } + Message::WindowClosed(id) => { + if let Some(WindowType::Main) = self.windows.remove(&id) { + return iced::exit(); + } + } + Message::Header(HeaderButton::OpenMemory) => { + return self.open(WindowType::Memory(MemoryTy::Cpu, HexView::new())); + } + Message::Header(HeaderButton::OpenTileMap) => { + return self.open(WindowType::TileMap); + } + Message::Header(HeaderButton::OpenTileViewer) => { + return self.open(WindowType::TileViewer); + } + Message::Hex(id, val) => { + if let Some(WindowType::Memory(_, view)) = self.windows.get_mut(&id) { + return view.update(val).map(move |e| Message::Hex(id, e)); + } + } + Message::Header(HeaderButton::Reset) => { self.nes.reset(); } + Message::Header(HeaderButton::PowerCycle) => { self.nes.power_cycle(); } + } + // self.image.0.clone_from(self.nes.image()); + Task::none() + } + + fn subscriptions(&self) -> Subscription { + window::close_events().map(Message::WindowClosed) + } + + fn view(&self, win: Id) -> Element<'_, Message> { + match self.windows.get(&win) { + Some(WindowType::Main) => { + let content = column![ + self.dropdowns(), + Element::from(Canvas::new(self).width(Fill).height(255. * 2.)), + self.cpu_state(), + self.controls(), + ] + .height(Fill); + container(content).width(Fill).height(Fill).into() + } + Some(WindowType::Memory(ty, view)) => { + let hex = match ty { + MemoryTy::Cpu => view + .render(self.nes.cpu_mem()) + .map(move |e| Message::Hex(win, e)), + }; + let content = column![hex].width(Fill).height(Fill); + container(content).width(Fill).height(Fill).into() + } + Some(WindowType::TileMap) => { + container(Canvas::new(DbgImage::PatternTable(self.nes.ppu()))).width(Fill).height(Fill).into() + } + Some(WindowType::TileViewer) => { + container(Canvas::new(DbgImage::NameTable(self.nes.ppu()))).width(Fill).height(Fill).into() + } + _ => todo!(), + } + } + + fn cpu_state(&self) -> Element<'_, Message> { + row![column![ + // text!("Registers").font(Font::MONOSPACE), + text!("{:?}", self.nes).font(Font::MONOSPACE), + ],] + .width(Fill) + .into() + } + + fn controls(&self) -> Element<'_, Message> { + row![ + button("Clock tick").on_press(Message::Tick(1)), + button("CPU tick").on_press(Message::CPU), + button("Next Frame").on_press(Message::Frame), + button("Next DMA").on_press(Message::DMA), + button("Next DBG").on_press(Message::DebugInt), + ] + .width(Fill) + .into() + } + + fn dropdowns(&self) -> Element<'_, Message> { + row![ + header_menu( + "Console", + [ + HeaderButton::Reset, + HeaderButton::PowerCycle, + ], + Message::Header + ), + header_menu( + "Debugging", + [ + HeaderButton::OpenMemory, + HeaderButton::OpenTileMap, + HeaderButton::OpenTileViewer, + ], + Message::Header + ) + ] + .width(Fill) + .into() + } +} + +impl Program for Emulator { + type State = (); + + fn draw( + &self, + state: &Self::State, + renderer: &Renderer, + theme: &Theme, + bounds: iced::Rectangle, + _cursor: mouse::Cursor, + ) -> Vec> { + const SIZE: f32 = 2.; + let mut frame = Frame::new( + renderer, + iced::Size { + width: 256. * 2., + height: 240. * 2., + }, + ); + frame.scale(2.); + for y in 0..240 { + for x in 0..256 { + let c = self.nes.image().read(y, x); + frame.fill_rectangle( + Point::new(x as f32, y as f32), + Size::new(1., 1.), + Color::from_rgb8(c.r, c.g, c.b), + ); + } + } + vec![frame.into_geometry()] + } +} + +enum DbgImage<'a> { + NameTable(&'a PPU), + PatternTable(&'a PPU), +} + +impl Program for DbgImage<'_> { + type State = (); + + fn draw( + &self, + state: &Self::State, + renderer: &Renderer, + theme: &Theme, + bounds: iced::Rectangle, + _cursor: mouse::Cursor, + ) -> Vec> { + const SIZE: f32 = 2.; + let mut name_table_frame = + Frame::new(renderer, Size::new(256. * 4. + 260. * 2., 256. * 4.)); + name_table_frame.scale(2.); + match self { + DbgImage::NameTable(ppu) => ppu.render_name_table(&mut name_table_frame), + DbgImage::PatternTable(ppu) => ppu.render_pattern_tables(&mut name_table_frame), + } + vec![name_table_frame.into_geometry()] + } +} diff --git a/src/mem.rs b/src/mem.rs new file mode 100644 index 0000000..8327ae3 --- /dev/null +++ b/src/mem.rs @@ -0,0 +1,163 @@ +use crate::hex_view::Memory; + +pub enum Value<'a, R> { + Value(u8), + Register { reg: &'a R, offset: u16 }, +} + +impl Value<'_, R> { + pub fn reg_map(self, f: impl FnOnce(&R, u16) -> u8) -> u8 { + match self { + Value::Value(v) => v, + Value::Register { reg, offset } => f(reg, offset), + } + } +} + +pub enum Data { + RAM(Vec), + ROM(Vec), + Mirror(usize), + Reg(R), + // Disabled(), +} + +pub struct Segment { + name: &'static str, + position: u16, + size: u16, + mem: Data, +} + +impl Segment { + pub fn ram(name: &'static str, position: u16, size: u16) -> Self { + Self { + name, + position, + size, + mem: Data::RAM(vec![0u8; size as usize]), + } + } + pub fn rom(name: &'static str, position: u16, raw: &[u8]) -> Self { + Self { + name, + position, + size: raw.len() as u16, + mem: Data::ROM(Vec::from(raw)), + } + } + pub fn reg(name: &'static str, position: u16, size: u16, reg: R) -> Self { + Self { + name, + position, + size, + mem: Data::Reg(reg), + } + } + pub fn mirror(name: &'static str, position: u16, size: u16, of: usize) -> Self { + Self { + name, + position, + size, + mem: Data::Mirror(of), + } + } +} + +pub struct MemoryMap { + edit_ver: usize, + segments: Vec>, +} + +impl MemoryMap { + pub fn new(segments: Vec>) -> Self { + Self { edit_ver: 0, segments } + } + pub fn read(&self, addr: u16) -> Value<'_, R> { + // self.edit_ver += 1; + for segment in &self.segments { + if segment.position <= addr && addr - segment.position < segment.size { + return match &segment.mem { + Data::RAM(items) => Value::Value(items[(addr - segment.position) as usize]), + Data::ROM(items) => Value::Value(items[(addr - segment.position) as usize]), + Data::Reg(reg) => Value::Register { + reg, + offset: addr - segment.position, + }, + Data::Mirror(index) => { + let offset = addr - segment.position; + let s = &self.segments[*index]; + self.read(s.position + offset % s.size) + } + // Data::Disabled() => todo!(), + }; + } + } + todo!("Open bus") + } + + pub fn write(&mut self, addr: u16, val: u8, reg_fn: impl FnOnce(&R, u16, u8)) { + self.edit_ver += 1; + for segment in &mut self.segments { + if segment.position <= addr && addr - segment.position < segment.size { + return match &mut segment.mem { + Data::RAM(items) => { + items[(addr - segment.position) as usize] = val; + } + Data::ROM(_items) => (), + Data::Reg(reg) => reg_fn(reg, addr - segment.position, val), + Data::Mirror(index) => { + let offset = addr - segment.position; + let index = *index; + let s = &self.segments[index]; + self.write(s.position + offset % s.size, val, reg_fn) + } + // Data::Disabled() => todo!(), + }; + } + } + todo!("Open bus") + } + + pub fn clear(&mut self) { + for s in &mut self.segments { + match &mut s.mem { + Data::RAM(items) => items.fill(0), + _ => (), + } + } + } + + pub(crate) fn rom(&self, idx: usize) -> Option<&[u8]> { + if let Some(Segment { mem: Data::ROM(val), .. }) = self.segments.get(idx) { + Some(val) + } else { + None + } + } +} + +impl Memory for MemoryMap { + fn peek(&self, addr: u16) -> Option { + for segment in &self.segments { + if segment.position <= addr && addr - segment.position < segment.size { + return match &segment.mem { + Data::RAM(items) => Some(items[(addr - segment.position) as usize]), + Data::ROM(items) => Some(items[(addr - segment.position) as usize]), + Data::Reg(_) => None, + Data::Mirror(index) => { + let offset = addr - segment.position; + let s = &self.segments[*index]; + self.peek(s.position + offset % s.size) + } + // Data::Disabled() => todo!(), + }; + } + } + None + } + + fn edit_ver(&self) -> usize { + self.edit_ver + } +} diff --git a/src/ppu.rs b/src/ppu.rs new file mode 100644 index 0000000..de6b093 --- /dev/null +++ b/src/ppu.rs @@ -0,0 +1,495 @@ +use iced::{Point, Size, widget::canvas::Frame}; +use iced_graphics::geometry::{Fill, Renderer}; + +use crate::{hex_view::Memory, mem::{MemoryMap, Segment}}; + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct Color { + pub r: u8, + pub g: u8, + pub b: u8, +} + +impl Into for Color { + fn into(self) -> Fill { + iced::Color::from_rgb8(self.r, self.g, self.b).into() + } +} + +pub struct RenderBuffer { + buffer: Box<[Color]>, +} + +impl RenderBuffer { + pub fn empty() -> Self { + Self { + buffer: vec![Color { r: 0, g: 0, b: 0 }; W * H].into_boxed_slice(), + } + } + + pub fn write(&mut self, line: usize, pixel: usize, color: Color) { + assert!(line < H && pixel < W); + self.buffer[line * W + pixel] = color; + } + pub fn read(&self, line: usize, pixel: usize) -> Color { + assert!(line < H && pixel < W); + self.buffer[line * W + pixel] + } + + pub fn clone_from(&mut self, other: &Self) { + self.buffer.copy_from_slice(&other.buffer); + } +} + +pub(crate) enum PPUMMRegisters { + Palette, +} + +pub struct OAM { + mem: Vec, + addr: u8, +} + +enum BackgroundState { + NameTableBytePre, + NameTableByte, + AttrTableBytePre, + AttrTableByte, + PatternTableTileLowPre, + PatternTableTileLow, + PatternTableTileHighPre, + PatternTableTileHigh, +} + +pub struct Background { + /// Current vram address, 15 bits + v: u16, + /// Temp vram address, 15 bits + t: u16, + /// Fine X control, 3 bits + x: u8, + /// Whether this is the first or second write to PPUSCROLL + /// When false, writes to x + w: bool, + + /// When true, v is incremented by 32 after each read + vram_column: bool, + + state: BackgroundState, + + cur_shift_high: u8, + cur_shift_low: u8, +} + +struct Mask { + grayscale: bool, + background_on_left_edge: bool, + sprites_on_left_edge: bool, + enable_background: bool, + enable_sprites: bool, + em_red: bool, + em_green: bool, + em_blue: bool, +} + +pub struct PPU { + // registers: PPURegisters, + frame_count: usize, + nmi_enabled: bool, + nmi_waiting: bool, + even: bool, + scanline: usize, + pixel: usize, + + mask: Mask, + vblank: bool, + + pub(crate) memory: MemoryMap, + background: Background, + oam: OAM, + pub render_buffer: RenderBuffer<256, 240>, + pub dbg_int: bool, + pub cycle: usize, +} + +bitfield::bitfield! { + pub struct PPUStatus(u8); + impl Debug; + vblank, set_vblank: 7; + sprite0, set_sprite0: 6; + sprite_overflow, set_sprite_overflow: 5; +} + +impl std::fmt::Debug for PPU { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "PPU: f {}, s {}, p {}", + self.frame_count, self.scanline, self.pixel + ) + } +} + +impl PPU { + pub fn with_chr_rom(rom: &[u8]) -> Self { + Self { + cycle: 25, + dbg_int: false, + + mask: Mask { + grayscale: false, + background_on_left_edge: false, + sprites_on_left_edge: false, + enable_background: false, + enable_sprites: false, + em_red: false, + em_green: false, + em_blue: false, + }, + vblank: false, + frame_count: 0, + nmi_enabled: false, + nmi_waiting: false, + even: true, // ?? + scanline: 0, + pixel: 25, + render_buffer: RenderBuffer::empty(), + memory: MemoryMap::new(vec![ + Segment::rom("CHR ROM", 0x0000, rom), + Segment::ram("Internal VRAM", 0x2000, 0x1000), + Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1), + Segment::reg("Palette Control", 0x3F00, 0x0100, PPUMMRegisters::Palette), + ]), + background: Background { + v: 0, + t: 0, + x: 0, + w: false, + vram_column: false, + state: BackgroundState::NameTableBytePre, + cur_shift_high: 0, + cur_shift_low: 0, + }, + oam: OAM { + mem: vec![0u8; 256], + addr: 0, + }, + } + } + pub fn reset(&mut self) { + *self = Self { + memory: std::mem::replace(&mut self.memory, MemoryMap::new(vec![])), + ..Self::with_chr_rom(&[]) + }; + } + + pub fn read_reg(&mut self, offset: u16) -> u8 { + match offset { + 0 => panic!("ppuctrl is write-only"), + 1 => panic!("ppumask is write-only"), + 2 => { + let tmp = if self.vblank { 0b1000_0000 } else { 0 }; + self.vblank = false; + self.background.w = false; + tmp + } + 3 => panic!("oamaddr is write-only"), + 4 => self.oam.mem[self.oam.addr as usize], + 5 => panic!("ppuscroll is write-only"), + 6 => panic!("ppuaddr is write-only"), + 7 => { + let val = self.memory.read(self.background.v).reg_map(|a, _| match a { + PPUMMRegisters::Palette => todo!(), + }); + // if self.background + self.background.v = self + .background + .v + .wrapping_add(if self.background.vram_column { 32 } else { 1 }); + val + } + // 7 => self.registers.data, + _ => panic!("No register at {:02X}", offset), + } + } + pub fn write_reg(&mut self, offset: u16, val: u8) { + match offset { + 0x00 => { + self.nmi_enabled = val & 0b1000_0000 != 0; + self.background.t = + (self.background.t & 0b0001_1000_0000_0000) | (((val & 0b11) as u16) << 10); + self.background.vram_column = val & 0b0000_0010 != 0; + // TODO: other control fields + } + 0x01 => { + // self.dbg_int = true; + self.mask.grayscale = val & 0b0000_0001 != 0; + self.mask.background_on_left_edge = val & 0b0000_0010 != 0; + self.mask.sprites_on_left_edge = val & 0b0000_0100 != 0; + self.mask.enable_background = val & 0b0000_1000 != 0; + self.mask.enable_background = val & 0b0001_0000 != 0; + self.mask.em_red = val & 0b0010_0000 != 0; + self.mask.em_green = val & 0b0100_0000 != 0; + self.mask.em_blue = val & 0b1000_0000 != 0; + // todo!("Mask: {:02X}", val) + } + 0x02 => { + todo!("Unable to write to PPU status") + // TODO: ppu status + } + 0x03 => self.oam.addr = val, + 0x04 => { + self.oam.mem[self.oam.addr as usize] = val; + self.oam.addr = self.oam.addr.wrapping_add(1); + } + 0x05 => { + if self.background.w { + // Is y scroll + self.background.t = (self.background.t & 0b0000_1100_0001_1111) + | (((val as u16) << 2) & 0b0000_0011_1110_0000) + | (((val as u16) << 12) & 0b0111_0000_0000_0000); + self.background.w = false; + } else { + // Is x scroll + self.background.x = val & 0b111; // Lowest three bits + self.background.t = + (self.background.t & 0b0111_1111_1110_0000) | (val as u16 >> 3); + self.background.w = true; + } + } + 0x06 => { + // TODO: ppu addr + if self.background.w { + self.background.v = + u16::from_le_bytes([val, self.background.v.to_le_bytes()[1]]); + self.background.w = true; + } else { + self.background.v = + u16::from_le_bytes([self.background.v.to_le_bytes()[0], val & 0b0011_1111]); + self.background.w = true; + } + // todo!("PPUADDR write") + } + 0x07 => { + // TODO: ppu data + } + _ => panic!("No register at {:02X}", offset), + } + // TODO: use data in PPU + } + // pub fn write_oamdma(&mut self, val: u8) { + // // TODO: OAM high addr + // } + + pub fn run_one_clock_cycle(&mut self) -> bool { + self.cycle += 1; + self.pixel += 1; + if self.scanline == 261 && (self.pixel == 341 || (self.pixel == 340 && self.even)) { + self.scanline = 0; + self.pixel = 0; + self.even = !self.even; + } + if self.pixel == 341 { + self.pixel = 0; + self.scanline += 1; + } + if self.mask.enable_background || self.mask.enable_sprites { + if self.pixel == 257 { + self.background.v = (self.background.v & 0b0111_1011_1110_0000) + | (self.background.t & 0b0000_0100_0001_1111); + } + if self.pixel == 280 && self.scanline == 260 { + self.background.v = (self.background.v & 0b0000_0100_0001_1111) + | (self.background.t & 0b0111_1011_1110_0000); + } + if self.scanline < 240 || self.scanline == 261 { + // TODO + if self.pixel == 0 { + // self.dbg_int = true; + // idle cycle + } else if self.pixel < 257 { + // self.dbg_int = true; + if self.scanline < 240 { + self.render_buffer.write( + self.scanline, + self.pixel - 1, + Color { r: 255, g: 0, b: 0 }, + ); // TODO: this should come from shift registers + } + self.background.state = match self.background.state { + BackgroundState::NameTableByte => { + // TODO: Fetch name table byte + BackgroundState::AttrTableBytePre + } + BackgroundState::AttrTableByte => { + // TODO: Fetch attr table byte + BackgroundState::PatternTableTileLowPre + } + BackgroundState::PatternTableTileLow => { + // TODO: Fetch + BackgroundState::PatternTableTileHighPre + } + BackgroundState::PatternTableTileHigh => { + // TODO: Fetch + BackgroundState::NameTableBytePre + } + BackgroundState::NameTableBytePre => BackgroundState::NameTableByte, + BackgroundState::AttrTableBytePre => BackgroundState::AttrTableByte, + BackgroundState::PatternTableTileLowPre => { + BackgroundState::PatternTableTileLow + } + BackgroundState::PatternTableTileHighPre => { + BackgroundState::PatternTableTileHigh + } + }; + } else { + // TODO: Sprite fetches + } + } + } + if self.scanline == 241 && self.pixel == 1 { + self.vblank = true; + self.nmi_waiting = self.nmi_enabled; + self.frame_count += 1; + self.background.state = BackgroundState::NameTableBytePre; + return true; + } + return false; + } + + pub fn nmi_waiting(&mut self) -> bool { + if self.nmi_waiting { + self.nmi_waiting = false; + return true; + } else { + return false; + } + } + pub fn irq_waiting(&mut self) -> bool { + false + } + + pub fn render_name_table(&self, frame: &mut Frame) { + for y in 0..280 / 8 { + for x in 0..512 / 8 { + let name = self.memory.peek(x + y * 512 / 8 + 0x2000).unwrap() as u16 * 16; + for y_off in 0..8 { + let low = self.memory.peek(name + y_off).unwrap(); + let high = self.memory.peek(name + y_off + 8).unwrap(); + for bit in (0..8).rev() { + frame.fill_rectangle( + Point::new(x as f32 * 8. + bit as f32, y as f32 * 8. + y_off as f32), + Size::new(1., 1.), + match (low & (1 << bit) != 0, high & (1 << bit) != 0) { + (false, false) => Color { r: 0, g: 0, b: 0 }, + (true, false) => Color { r: 64, g: 64, b: 64 }, + (false, true) => Color { r: 128, g: 128, b: 128 }, + (true, true) => Color { r: 255, g: 255, b: 255 }, + } + ); + } + } + // for + // let pat = self.memory.peek(); + } + } + } + pub fn render_pattern_tables(&self, frame: &mut Frame) { + for y in 0..16 { + for x in 0..16 { + let name = (y * 16 + x) * 16; + for y_off in 0..8 { + let low = self.memory.peek(name + y_off).unwrap(); + let high = self.memory.peek(name + y_off + 8).unwrap(); + for bit in 0..8 { + frame.fill_rectangle( + Point::new(x as f32 * 8. + 8. - bit as f32, y as f32 * 8. + y_off as f32), + Size::new(1., 1.), + match (low & (1 << bit) != 0, high & (1 << bit) != 0) { + (false, false) => Color { r: 0, g: 0, b: 0 }, + (true, false) => Color { r: 64, g: 64, b: 64 }, + (false, true) => Color { r: 128, g: 128, b: 128 }, + (true, true) => Color { r: 255, g: 255, b: 255 }, + } + ); + } + } + } + } + + for y in 0..16 { + for x in 0..16 { + let name = (y * 16 + x) * 16; + for y_off in 0..8 { + let low = self.memory.peek(name + y_off + 0x1000).unwrap(); + let high = self.memory.peek(name + y_off + 8 + 0x1000).unwrap(); + for bit in 0..8 { + frame.fill_rectangle( + Point::new(x as f32 * 8. + 8. - bit as f32, y as f32 * 8. + y_off as f32 + 130.), + Size::new(1., 1.), + match (low & (1 << bit) != 0, high & (1 << bit) != 0) { + (false, false) => Color { r: 0, g: 0, b: 0 }, + (true, false) => Color { r: 64, g: 64, b: 64 }, + (false, true) => Color { r: 128, g: 128, b: 128 }, + (true, true) => Color { r: 255, g: 255, b: 255 }, + } + ); + } + } + // for + // let pat = self.memory.peek(); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ppu_registers() { + let mut ppu = PPU::with_chr_rom(&[0u8; 8192]); + assert_eq!(ppu.background.v, 0); + assert_eq!(ppu.background.t, 0); + assert_eq!(ppu.background.x, 0); + assert_eq!(ppu.background.w, false); + ppu.write_reg(0, 0); + assert_eq!(ppu.background.v, 0); + ppu.write_reg(0, 0b11); + assert_eq!( + ppu.background.t, 0b0001100_00000000, + "Actual: {:016b}", + ppu.background.t + ); + assert_eq!(ppu.background.w, false); + + ppu.write_reg(5, 0x7D); + assert_eq!( + ppu.background.t, 0b0001100_00001111, + "Actual: {:016b}", + ppu.background.t + ); + assert_eq!(ppu.background.x, 0b101); + assert_eq!(ppu.background.w, true); + ppu.write_reg(5, 0x5E); + assert_eq!( + ppu.background.t, 0b1101101_01101111, + "Actual: {:016b}", + ppu.background.t + ); + assert_eq!(ppu.background.x, 0b101); + assert_eq!(ppu.background.w, false); + + ppu.write_reg(5, 0x7D); + assert_eq!( + ppu.background.t, 0b1101101_01101111, + "Actual: {:016b}", + ppu.background.t + ); + assert_eq!(ppu.background.x, 0b101); + assert_eq!(ppu.background.w, true); + ppu.read_reg(2); + assert_eq!(ppu.background.w, false); + } +} diff --git a/src/test_roms/Sprites.pcx b/src/test_roms/Sprites.pcx new file mode 100644 index 0000000000000000000000000000000000000000..c408ca4e9549c3469695193e5d578d3ea06ff759 GIT binary patch literal 5479 zcmeI0+iu%N5Qcd`fuiU`;dPy$JI+M-BLH@WIffS^WT_Wx#fxJy~6 zfTDMesm<)p&ir#Exy(0T)n8Zo`%c%Z`cVn!w}}NiMqg=a3c<;A z!3nHEiZ>dA%q(7xnvP~JoX-f@VL!X$Yv&;cB!O>4i6l(L9{bF(QAP@{lw}@kbQr`- zVjbIYV((O+^~1%Gr2+=EM4w)UV zD>Yhc#ghO^w>)eAx2pX`%LqP!xUH!@n0=20dp}Wuq5W~uP}P?ljLO_zyR-(vKc@jU zPY;?AnS;@x{i8tQws-gX_Cb22<%7l#s=R#Cu4lGycj6*}G!$3m7ADLSmz&yq@j;1! z{G%++)$f`S{*&mgvPRri;yh}-RZ8%Y=K(k2TnfIFO*Ef>}OQ#_=G-DD&e5yMeohUlJcp_arX~5Di zmbZxoJ4WAVY7)W8bWsefL5epTgUl>mj+%~UE}YK@*kM1r<7?+32PA=SM2RF!#UA_2 zu~9|}uaspTYjhaIOJW_{aboXOpY_YJxV~9xZ0D{=#sUz+!o8ZzpHR%i7LKCJ~%W z7sWV(aeBMa;HbWM$yNkBK&L)@Nkw zmu{j*yc{_kJKGD)kS{UU^CbftOLi^>s<;$IDQo8tzL$&CRn(g6u1FL6=$2+3J4G!k zSPitG>qL~=F(*g@U+Uy%2yy)Uv@#h=r__s`GambVy<`LQA?P@zZ?cQX9(1m4phE4? zluP9igaK_Q_(IG(4BMjxj4uq(&Ubf6m_WMMynE7DL+KE6Z?Yy!(%s}k3hNnqKP~+kh-=TZKlF(9$ zCx)Brh2+gRx7IY$CIok=uO6gHBIcHFU^$V(LuMyF`^bL6xCIy~nyP#8(T(B3J&Qw# zPolflo-Er|oJXy<3Ol~1Y5%$1r)Qn})C;p0_qcGAySv2h6b{bgD(XjplB%~@;Cj@T z!8;wdZ*(BmVR+Dl#aw+TW{QI|`db3Vq8o&Mrvv2jEL(6|F3I5CPO9uW| G8TcE=0K#_w literal 0 HcmV?d00001 diff --git a/src/test_roms/Tiles.pcx b/src/test_roms/Tiles.pcx new file mode 100644 index 0000000000000000000000000000000000000000..43074a0539cae07bc89a20fbb9a95694b3bde387 GIT binary patch literal 9631 zcmeHN3vSym47FVh7=|6Hm*{!c^Yj>YJ7Bw1_8vbHN!cwhG#Qd5E=2Pq{zOrBnr~m` zuM<8$a6QhCGj5-ro?c#FPNx&;AAaWNX`Y^O{oUs@ou+Nt9^`Lcr)8bCdD@mfE>l1v zW4vGE?1Vt}!LAwpk_Wg!bq7EMotBMM118g%E>xjSa&prV>`Hi44R-)m_#u`QHELt3 zkD2P21<+hIt4=1|v#XKiuuFi=N6SWG!%$9ALHk454unvan|S z`pq^x`@QG$VI^IcDaHpkh9 zzA^yF)F8n=3~oOg7QO|w1jP(S(&xNnj*9c1GOppt?=si0FuCS+qCxm*M*#okekQKBIL5jm=WJ2PY5}BUwFJB7r zC}wlRh5a4?0*+{A?qafMx}JjC6jy6p z5ME~~5}Ny!FaPD6gg+ys#IaAb9c74;jz+GntCEwoA#4h5!LGgpR(31zKaw-Sr7bba z-pdRdWs{V%I}}v|dXt60P_fqu&h#XS_J~E|k-!>+Ayla%Qi+CTkOVV}=ZPp`-*v@} z=)7c(lJTt~zdyJwbush0{%p-K@!s3+T$8 zB<_9WQxBM%t`+QbYm?Zlg5W7p_i`MIjBE?#dy_?%k(N8WZ{6hSf~>WZ`rbi$E>J|N z`=CZrs!%10vsQWrrF6W@@|lp#J>dEhuNh5L7N+Qt)41y^XgPiOEdbQ4+ygj#9Wx>< z;Towjqse59_QH&Un#;vk3)7=@Zc5X@v~ZiS<438cQxvYT^1IRKaSZ1mD<Yr&;o@PAxkT4xHLAjvI7Ib(!Nx%HV&Q>ii1;8>%~%hrgl=f5Dt}>-FdSi> z;hqW$Ng3nF1UAuxK=vXpLT*H#0MXiE7higbb@ic-QAQ?ecrZ}(w%tzQ%MNW2T^rkC?WRaBsyR7Og>}JZ7 zWR@M(Mu$kkT0{&5I0pt;d{K=Z|7?2LAJb*9&3r&zIaN32~%LRxqDIeo{EO4`p+@Gt-S-#r8W HRR;b5IBRB( literal 0 HcmV?d00001 diff --git a/src/test_roms/basic-cpu.asm b/src/test_roms/basic-cpu.asm new file mode 100644 index 0000000..71c597c --- /dev/null +++ b/src/test_roms/basic-cpu.asm @@ -0,0 +1,26 @@ +; FINAL = "" + +.inesprg 2 ; 2 banks +.ineschr 1 ; +.inesmap 0 ; mapper 0 = NROM +.inesmir 0 ; background mirroring, horizontal + +.org $8000 +RESET: + sed + hlt +ERROR_: + hlt + +IGNORE: + rti + +.org $FFFA ; Interrupt vectors go here: +.word IGNORE ; NMI +.word RESET ; Reset +.word IGNORE; IRQ + +;;;; NESASM COMPILER STUFF, ADDING THE PATTERN DATA ;;;; + +.incbin "Sprites.pcx" +.incbin "Tiles.pcx" diff --git a/src/test_roms/mod.rs b/src/test_roms/mod.rs new file mode 100644 index 0000000..e8a0163 --- /dev/null +++ b/src/test_roms/mod.rs @@ -0,0 +1,41 @@ +use crate::{NES, hex_view::Memory}; + +macro_rules! rom_test { + ($name:ident, $rom:literal, |$nes:ident| $eval:expr) => { + #[test] + fn $name() { + let rom_file = concat!(env!("ROM_DIR"), "/", $rom); + println!("{}: {}", stringify!($name), rom_file); + let mut $nes = NES::load_nes_file(rom_file).expect("Failed to create nes object"); + $nes.reset_and_run_with_timeout(1000000); + println!("Final: {:?}", $nes); + $eval + } + }; +} + +rom_test!(basic_cpu, "basic-cpu.nes", |nes| { + assert_eq!(nes.last_instruction, "0x8001 HLT :2 []"); + // Off by one from Mesen, since Mesen doesn't count the clock cycle attempting to execute the 'invalid' opcode + assert_eq!(nes.cycle, 11); + // This is off by one from Mesen, because Mesen is left pointing at the 'invalid' opcode + assert_eq!(nes.cpu.pc, 0x8002); + + assert_eq!(nes.cpu.sp, 0xFD); + // assert_eq!(nes.cpu.a, 0x00); + // assert_eq!(nes.cpu.x, 0x00); + // assert_eq!(nes.cpu.y, 0x00); +}); + +rom_test!(read_write, "read_write.nes", |nes| { + assert_eq!(nes.last_instruction, "0x8011 HLT :2 []"); + assert_eq!(nes.cycle, 31); + assert_eq!(nes.cpu.pc, 0x8012); + assert_eq!(nes.cpu.sp, 0xFD); + assert_eq!(nes.cpu.a, 0xAA); + assert_eq!(nes.cpu.x, 0xAA); + assert_eq!(nes.cpu.y, 0xAA); + assert_eq!(nes.cpu_mem().peek(0x0000).unwrap(), 0xAA); + assert_eq!(nes.cpu_mem().peek(0x0001).unwrap(), 0xAA); + assert_eq!(nes.cpu_mem().peek(0x0002).unwrap(), 0xAA); +}); diff --git a/src/test_roms/read_write.asm b/src/test_roms/read_write.asm new file mode 100644 index 0000000..9e67a34 --- /dev/null +++ b/src/test_roms/read_write.asm @@ -0,0 +1,31 @@ +; FINAL = "" + +.inesprg 2 ; 2 banks +.ineschr 1 ; +.inesmap 0 ; mapper 0 = NROM +.inesmir 0 ; background mirroring, horizontal + +.org $8000 +RESET: + lda #$aa + sta $0000 + ldx $0000 + ldy $0000 + stx $0001 + sty $0002 + hlt +ERROR_: + hlt + +IGNORE: + rti + +.org $FFFA ; Interrupt vectors go here: +.word IGNORE ; NMI +.word RESET ; Reset +.word IGNORE; IRQ + +;;;; NESASM COMPILER STUFF, ADDING THE PATTERN DATA ;;;; + +.incbin "Sprites.pcx" +.incbin "Tiles.pcx"