summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <bridle.benjamin@gmail.com>2023-12-24 22:19:11 +1300
committerBen Bridle <bridle.benjamin@gmail.com>2023-12-24 22:19:11 +1300
commit77067f6e244a9cf4a6ef59df2e3d735b4f172c35 (patch)
tree74eb1a226da5b367f65e2f9012dc8d440e1ccce5
downloadbedrock-pc-77067f6e244a9cf4a6ef59df2e3d735b4f172c35.zip
First commitv0.1.0
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock1618
-rw-r--r--Cargo.toml23
-rw-r--r--src/devices.rs339
-rw-r--r--src/devices/clock.rs130
-rw-r--r--src/devices/file.rs90
-rw-r--r--src/devices/input.rs128
-rw-r--r--src/devices/math.rs35
-rw-r--r--src/devices/scratch.rs58
-rw-r--r--src/devices/screen.rs469
-rw-r--r--src/devices/screen/sprite_data.rs33
-rw-r--r--src/devices/screen/vector_points.rs28
-rw-r--r--src/devices/stream.rs46
-rw-r--r--src/emulator.rs194
-rw-r--r--src/main.rs22
15 files changed, 3214 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..e120e69
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,1618 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "android-activity"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0"
+dependencies = [
+ "android-properties",
+ "bitflags 1.3.2",
+ "cc",
+ "jni-sys",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-sys",
+ "num_enum 0.6.1",
+]
+
+[[package]]
+name = "android-properties"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
+
+[[package]]
+name = "as-raw-xcb-connection"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bedrock_core"
+version = "1.0.0"
+source = "git+git://benbridle.com/bedrock_core?tag=v1.0.0#2b16a99ba3d1ba6536a1db28471288a0c2274a71"
+
+[[package]]
+name = "bedrock_emu"
+version = "0.1.0"
+dependencies = [
+ "bedrock_core",
+ "geometry 1.0.0 (git+git://benbridle.com/geometry?tag=v1.0.0)",
+ "mini_paste",
+ "phosphor",
+ "time",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "block-sys"
+version = "0.1.0-beta.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146"
+dependencies = [
+ "objc-sys",
+]
+
+[[package]]
+name = "block2"
+version = "0.2.0-alpha.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42"
+dependencies = [
+ "block-sys",
+ "objc2-encode",
+]
+
+[[package]]
+name = "buffer"
+version = "1.0.0"
+source = "git+git://benbridle.com/buffer#2322c4c2b335ca398ec039503257e20d139a699f"
+dependencies = [
+ "colour",
+ "geometry 1.0.0 (git+git://benbridle.com/geometry?tag=v1.0.0)",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "bytemuck"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.42",
+]
+
+[[package]]
+name = "calloop"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8"
+dependencies = [
+ "bitflags 1.3.2",
+ "log",
+ "nix 0.25.1",
+ "slotmap",
+ "thiserror",
+ "vec_map",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+
+[[package]]
+name = "cocoa"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
+dependencies = [
+ "bitflags 1.3.2",
+ "block",
+ "cocoa-foundation",
+ "core-foundation",
+ "core-graphics 0.23.1",
+ "foreign-types 0.5.0",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "cocoa-foundation"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
+dependencies = [
+ "bitflags 1.3.2",
+ "block",
+ "core-foundation",
+ "core-graphics-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "colour"
+version = "1.0.0"
+source = "git+git://benbridle.com/colour?tag=v1.0.0#8875d9d6dd48400a9ca1cf8c64889d860700d40c"
+dependencies = [
+ "proportion",
+]
+
+[[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-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "core-graphics"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types 0.3.2",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types 0.5.0",
+ "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",
+ "libc",
+]
+
+[[package]]
+name = "ctor"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
+dependencies = [
+ "quote",
+ "syn 2.0.42",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc"
+dependencies = [
+ "powerfmt",
+]
+
+[[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.1",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
+
+[[package]]
+name = "drm"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb1b703ffbc7ebd216eba7900008049a56ace55580ecb2ee7fa801e8d8be87"
+dependencies = [
+ "bitflags 2.4.1",
+ "bytemuck",
+ "drm-ffi",
+ "drm-fourcc",
+ "nix 0.27.1",
+]
+
+[[package]]
+name = "drm-ffi"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba7d1c19c4b6270e89d59fb27dc6d02a317c658a8a54e54781e1db9b5947595d"
+dependencies = [
+ "drm-sys",
+ "nix 0.27.1",
+]
+
+[[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.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a4f1c0468062a56cd5705f1e3b5409eb286d5596a2028ec8e947595d7e715ae"
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared 0.1.1",
+]
+
+[[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 0.3.1",
+]
+
+[[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.42",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "geometry"
+version = "1.0.0"
+source = "git+git://benbridle.com/geometry?tag=v1.0.0#c649ec04d86dfec7dff7027664af4ba1b9cc983a"
+
+[[package]]
+name = "geometry"
+version = "1.0.0"
+source = "git+git://benbridle.com/geometry#8547ad9c2f2867da9b0e655dd4ff0fe78f04ffba"
+
+[[package]]
+name = "gethostname"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "indexmap"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "jobserver"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
+
+[[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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "libredox"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607"
+dependencies = [
+ "bitflags 2.4.1",
+ "libc",
+ "redox_syscall 0.4.1",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+
+[[package]]
+name = "memmap2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memmap2"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mini_paste"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2499b7bd9834270bf24cfc4dd96be59020ba6fd7f3276b772aee2de66e82b63"
+dependencies = [
+ "mini_paste-proc_macro",
+]
+
+[[package]]
+name = "mini_paste-proc_macro"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c5f1f52e39b728e73af4b454f1b29173d4544607bd395dafe1918fd149db67"
+
+[[package]]
+name = "mio"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ndk"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0"
+dependencies = [
+ "bitflags 1.3.2",
+ "jni-sys",
+ "ndk-sys",
+ "num_enum 0.5.11",
+ "raw-window-handle",
+ "thiserror",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "ndk-sys"
+version = "0.4.1+23.1.7779620"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "nix"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+ "memoffset 0.6.5",
+]
+
+[[package]]
+name = "nix"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
+dependencies = [
+ "autocfg",
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+ "memoffset 0.6.5",
+]
+
+[[package]]
+name = "nix"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+ "memoffset 0.7.1",
+]
+
+[[package]]
+name = "nix"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
+dependencies = [
+ "bitflags 2.4.1",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
+dependencies = [
+ "num_enum_derive 0.5.11",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1"
+dependencies = [
+ "num_enum_derive 0.6.1",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.42",
+]
+
+[[package]]
+name = "num_threads"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "objc-sys"
+version = "0.2.0-beta.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7"
+
+[[package]]
+name = "objc2"
+version = "0.3.0-beta.3.patch-leaks.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468"
+dependencies = [
+ "block2",
+ "objc-sys",
+ "objc2-encode",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "2.0.0-pre.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512"
+dependencies = [
+ "objc-sys",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "orbclient"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166"
+dependencies = [
+ "libredox",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "phosphor"
+version = "1.0.0"
+source = "git+git://benbridle.com/phosphor?tag=v1.0.0#8866d8f43679dbfa286e3fb827c238dc616619df"
+dependencies = [
+ "buffer",
+ "geometry 1.0.0 (git+git://benbridle.com/geometry)",
+ "softbuffer",
+ "winit",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proportion"
+version = "1.0.0"
+source = "git+git://benbridle.com/proportion?tag=v1.0.0#291bb4de2a2e5940fbebd8d400f35c1fc3b0c7a2"
+
+[[package]]
+name = "quick-xml"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "raw-window-handle"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "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 = "rustix"
+version = "0.38.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "serde"
+version = "1.0.193"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.193"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.42",
+]
+
+[[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.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9"
+dependencies = [
+ "bitflags 1.3.2",
+ "calloop",
+ "dlib",
+ "lazy_static",
+ "log",
+ "memmap2 0.5.10",
+ "nix 0.24.3",
+ "pkg-config",
+ "wayland-client 0.29.5",
+ "wayland-cursor",
+ "wayland-protocols",
+]
+
+[[package]]
+name = "softbuffer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "826da2ead8e85d1b4ea579fae3d58ec10c81a77d61deab8918c4a4f7514b2948"
+dependencies = [
+ "as-raw-xcb-connection",
+ "bytemuck",
+ "cfg_aliases",
+ "cocoa",
+ "core-graphics 0.23.1",
+ "drm",
+ "fastrand",
+ "foreign-types 0.5.0",
+ "js-sys",
+ "log",
+ "memmap2 0.9.3",
+ "objc",
+ "raw-window-handle",
+ "redox_syscall 0.4.1",
+ "rustix",
+ "tiny-xlib",
+ "wasm-bindgen",
+ "wayland-backend",
+ "wayland-client 0.31.1",
+ "wayland-sys 0.31.1",
+ "web-sys",
+ "windows-sys 0.48.0",
+ "x11rb",
+]
+
+[[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.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.42",
+]
+
+[[package]]
+name = "time"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
+dependencies = [
+ "deranged",
+ "libc",
+ "num_threads",
+ "powerfmt",
+ "serde",
+ "time-core",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "tiny-xlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4098d49269baa034a8d1eae9bd63e9fa532148d772121dace3bcd6a6c98eb6d"
+dependencies = [
+ "as-raw-xcb-connection",
+ "ctor",
+ "libloading 0.8.1",
+ "tracing",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.42",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.42",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
+
+[[package]]
+name = "wayland-backend"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "nix 0.26.4",
+ "scoped-tls",
+ "smallvec",
+ "wayland-sys 0.31.1",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.29.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
+dependencies = [
+ "bitflags 1.3.2",
+ "downcast-rs",
+ "libc",
+ "nix 0.24.3",
+ "scoped-tls",
+ "wayland-commons",
+ "wayland-scanner 0.29.5",
+ "wayland-sys 0.29.5",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3"
+dependencies = [
+ "bitflags 2.4.1",
+ "nix 0.26.4",
+ "wayland-backend",
+ "wayland-scanner 0.31.0",
+]
+
+[[package]]
+name = "wayland-commons"
+version = "0.29.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
+dependencies = [
+ "nix 0.24.3",
+ "once_cell",
+ "smallvec",
+ "wayland-sys 0.29.5",
+]
+
+[[package]]
+name = "wayland-cursor"
+version = "0.29.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
+dependencies = [
+ "nix 0.24.3",
+ "wayland-client 0.29.5",
+ "xcursor",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.29.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
+dependencies = [
+ "bitflags 1.3.2",
+ "wayland-client 0.29.5",
+ "wayland-commons",
+ "wayland-scanner 0.29.5",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.29.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "xml-rs",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.29.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
+dependencies = [
+ "dlib",
+ "pkg-config",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af"
+dependencies = [
+ "dlib",
+ "log",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-wsapoll"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-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.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[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.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
+name = "winit"
+version = "0.28.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94"
+dependencies = [
+ "android-activity",
+ "bitflags 1.3.2",
+ "cfg_aliases",
+ "core-foundation",
+ "core-graphics 0.22.3",
+ "dispatch",
+ "instant",
+ "libc",
+ "log",
+ "mio",
+ "ndk",
+ "objc2",
+ "once_cell",
+ "orbclient",
+ "percent-encoding",
+ "raw-window-handle",
+ "redox_syscall 0.3.5",
+ "smithay-client-toolkit",
+ "wasm-bindgen",
+ "wayland-client 0.29.5",
+ "wayland-commons",
+ "wayland-protocols",
+ "wayland-scanner 0.29.5",
+ "web-sys",
+ "windows-sys 0.45.0",
+ "x11-dl",
+]
+
+[[package]]
+name = "winnow"
+version = "0.5.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "x11-dl"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
+dependencies = [
+ "libc",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "x11rb"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a"
+dependencies = [
+ "as-raw-xcb-connection",
+ "gethostname",
+ "libc",
+ "libloading 0.7.4",
+ "nix 0.26.4",
+ "once_cell",
+ "winapi",
+ "winapi-wsapoll",
+ "x11rb-protocol",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc"
+dependencies = [
+ "nix 0.26.4",
+]
+
+[[package]]
+name = "xcursor"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..db0a1a3
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "bedrock_emu"
+version = "0.1.0"
+authors = ["Ben Bridle"]
+edition = "2021"
+description = "Emulator for running Bedrock programs"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+bedrock_core = { git = "git://benbridle.com/bedrock_core", tag = "v1.0.0" }
+phosphor = { git = "git://benbridle.com/phosphor", tag = "v1.0.0" }
+geometry = { git = "git://benbridle.com/geometry", tag = "v1.0.0" }
+
+mini_paste = "0.1.11"
+time = { version = "0.3.30", features = [ "local-offset" ] }
+
+[profile.release]
+lto=true
+opt-level="s"
+debug=false
+strip=true
+codegen-units=1
diff --git a/src/devices.rs b/src/devices.rs
new file mode 100644
index 0000000..562b664
--- /dev/null
+++ b/src/devices.rs
@@ -0,0 +1,339 @@
+use bedrock_core::*;
+
+mod math;
+mod clock;
+mod input;
+mod screen;
+mod scratch;
+mod stream;
+mod file;
+
+pub use math::*;
+pub use clock::*;
+pub use input::*;
+pub use screen::*;
+pub use scratch::*;
+pub use stream::*;
+pub use file::*;
+
+pub struct StandardDevices {
+ pub math: MathDevice,
+ pub clock: ClockDevice,
+ pub input: InputDevice,
+ pub screen: ScreenDevice,
+ pub scratch: ScratchDevice,
+ pub stream: StreamDevice,
+ pub file: FileDevice,
+
+ pub wake_mask: u16,
+ pub wake_device: u8,
+}
+
+impl StandardDevices {
+ pub fn new() -> Self {
+ let mut screen = ScreenDevice::new();
+ screen.resize(ScreenDimensions::new(256, 192));
+
+ Self {
+ math: MathDevice::new(),
+ clock: ClockDevice::new(),
+ input: InputDevice::new(),
+ screen,
+ scratch: ScratchDevice::new(),
+ stream: StreamDevice::new(),
+ file: FileDevice::new(),
+
+ wake_mask: 0x0000,
+ wake_device: 0x00,
+ }
+ }
+
+ pub fn can_wake(&mut self) -> bool {
+ macro_rules! test_wake {
+ ($flag:expr, $mask:expr, $index:expr) => {
+ if $flag && self.wake_mask & $mask != 0 {
+ $flag = false; self.wake_device = $index; return true;
+ }
+ };
+ }
+ self.clock.update_timer_1();
+ self.clock.update_timer_2();
+ self.clock.update_timer_3();
+ self.clock.update_timer_4();
+ test_wake!(self.clock.wake_flag, 0x1000, 0x03);
+ test_wake!(self.input.wake_flag, 0x0800, 0x04);
+ test_wake!(self.screen.wake_flag, 0x0400, 0x05);
+ test_wake!(self.stream.wake_flag, 0x0040, 0x09);
+ return false;
+ }
+}
+
+impl DeviceBus for StandardDevices {
+ fn read_u8(&mut self, port: u8) -> u8 {
+ macro_rules! read_hh { ($v:expr) => { ($v>>24) as u8 }; }
+ macro_rules! read_hl { ($v:expr) => { ($v>>16) as u8 }; }
+ macro_rules! read_lh { ($v:expr) => { ($v>>8) as u8 }; }
+ macro_rules! read_ll { ($v:expr) => { $v as u8 }; }
+ macro_rules! read_h { ($v:expr) => { ($v>>8) as u8 }; }
+ macro_rules! read_l { ($v:expr) => { $v as u8 }; }
+ macro_rules! read_b { ($b:expr) => { if $b { 0xff } else { 0x00 } }; }
+
+ macro_rules! no_read { () => { 0x00 }; }
+
+ match port {
+ // System
+ 0x00 => no_read!(),
+ 0x01 => no_read!(),
+ 0x02 => self.wake_device,
+ 0x03 => no_read!(),
+ 0x04 => no_read!(),
+ 0x05 => no_read!(),
+ 0x06 => no_read!(),
+ 0x07 => no_read!(),
+ 0x08 => 0xFF,
+ 0x09 => 0xFF,
+ 0x0A => 0xFF,
+ 0x0B => 0xFF,
+ 0x0C => 0xBC,
+ 0x0D => 0x80,
+ 0x0E => 0xBC,
+ 0x0F => 0x80,
+ // Math
+ 0x20 => no_read!(),
+ 0x21 => no_read!(),
+ 0x22 => no_read!(),
+ 0x23 => no_read!(),
+ 0x24 => no_read!(),
+ 0x25 => no_read!(),
+ 0x26 => no_read!(),
+ 0x27 => no_read!(),
+ 0x28 => { self.math.multiply(); read_h!(self.math.product_high) },
+ 0x29 => { self.math.multiply(); read_l!(self.math.product_high) },
+ 0x2A => { self.math.multiply(); read_h!(self.math.product_low) },
+ 0x2B => { self.math.multiply(); read_l!(self.math.product_low) },
+ 0x2C => { self.math.divide(); read_h!(self.math.quotient) },
+ 0x2D => { self.math.divide(); read_l!(self.math.quotient) },
+ 0x2E => { self.math.modulo(); read_h!(self.math.remainder) },
+ 0x2F => { self.math.modulo(); read_l!(self.math.remainder) },
+ // Clock
+ 0x30 => self.clock.year(),
+ 0x31 => self.clock.month(),
+ 0x32 => self.clock.day(),
+ 0x33 => self.clock.hour(),
+ 0x34 => self.clock.minute(),
+ 0x35 => self.clock.second(),
+ 0x36 => { self.clock.update_cumulative_seconds(); read_h!(self.clock.cumulative_seconds) },
+ 0x37 => read_l!(self.clock.cumulative_seconds),
+ 0x38 => { self.clock.update_timer_1(); read_h!(self.clock.timer_1) }
+ 0x39 => read_l!(self.clock.timer_1),
+ 0x3A => { self.clock.update_timer_2(); read_h!(self.clock.timer_2) }
+ 0x3B => read_l!(self.clock.timer_2),
+ 0x3C => { self.clock.update_timer_3(); read_h!(self.clock.timer_3) }
+ 0x3D => read_l!(self.clock.timer_3),
+ 0x3E => { self.clock.update_timer_4(); read_h!(self.clock.timer_4) }
+ 0x3F => read_l!(self.clock.timer_4),
+ // Input
+ 0x40 => read_h!(self.input.mouse_position.x),
+ 0x41 => read_l!(self.input.mouse_position.x),
+ 0x42 => read_h!(self.input.mouse_position.y),
+ 0x43 => read_l!(self.input.mouse_position.y),
+ 0x44 => read_h!(self.input.horizontal_scroll_value),
+ 0x45 => read_l!(self.input.horizontal_scroll_value),
+ 0x46 => read_h!(self.input.vertical_scroll_value),
+ 0x47 => read_l!(self.input.vertical_scroll_value),
+ 0x48 => self.input.character_queue.pop_front().unwrap_or(0),
+ 0x49 => self.input.modifier_state,
+ 0x4A => self.input.mouse_button_state,
+ 0x4B => self.input.navigation_state,
+ // Screen
+ 0x50 => read_h!(self.screen.dimensions.width),
+ 0x51 => read_l!(self.screen.dimensions.width),
+ 0x52 => read_h!(self.screen.dimensions.height),
+ 0x53 => read_l!(self.screen.dimensions.height),
+ 0x54 => read_h!(self.screen.cursor.x),
+ 0x55 => read_l!(self.screen.cursor.x),
+ 0x56 => read_h!(self.screen.cursor.y),
+ 0x57 => read_l!(self.screen.cursor.y),
+ 0x58 => no_read!(),
+ 0x59 => no_read!(),
+ 0x5A => no_read!(),
+ 0x5B => no_read!(),
+ 0x5C => no_read!(),
+ 0x5D => no_read!(),
+ 0x5E => no_read!(),
+ 0x5F => no_read!(),
+ // Scratch
+ 0x80 => no_read!(),
+ 0x81 => no_read!(),
+ 0x82 => no_read!(),
+ 0x83 => no_read!(),
+ 0x84 => no_read!(),
+ 0x85 => no_read!(),
+ 0x86 => no_read!(),
+ 0x87 => no_read!(),
+ 0x88 => self.scratch.read_head_1(),
+ 0x89 => self.scratch.read_head_1(),
+ 0x8A => self.scratch.read_head_2(),
+ 0x8B => self.scratch.read_head_2(),
+ 0x8C => read_hh!(self.scratch.max_capacity),
+ 0x8D => read_hl!(self.scratch.max_capacity),
+ 0x8E => read_lh!(self.scratch.max_capacity),
+ 0x8F => read_ll!(self.scratch.max_capacity),
+ // Stream
+ // File
+ 0xA0 => read_b!(self.file.file.is_some()),
+ 0xA1 => read_b!(self.file.operation_state),
+ 0xA2 => no_read!(),
+ 0xA3 => no_read!(),
+ 0xA4 => read_hh!(self.file.pointer),
+ 0xA5 => read_hl!(self.file.pointer),
+ 0xA6 => read_lh!(self.file.pointer),
+ 0xA7 => read_ll!(self.file.pointer),
+ 0xA8 => self.file.read(),
+ 0xA9 => self.file.read(),
+ 0xAA => no_read!(),
+ 0xAB => no_read!(),
+ 0xAC => read_hh!(self.file.file_size),
+ 0xAD => read_hl!(self.file.file_size),
+ 0xAE => read_lh!(self.file.file_size),
+ 0xAF => read_ll!(self.file.file_size),
+
+ _ => unimplemented!("Reading from device port 0x{port:02x}"),
+ }
+ }
+
+ fn write_u8(&mut self, val: u8, port: u8) -> Option<Signal> {
+
+ macro_rules! write_hh { ($v:expr) => { $v = $v & 0x00ffffff | ((val as u32) << 24) }; }
+ macro_rules! write_hl { ($v:expr) => { $v = $v & 0xff00ffff | ((val as u32) << 16) }; }
+ macro_rules! write_lh { ($v:expr) => { $v = $v & 0x00ff | ((val as u32) << 8) }; }
+ macro_rules! write_ll { ($v:expr) => { $v = $v & 0xff00 | (val as u32) }; }
+ macro_rules! write_h { ($v:expr) => { $v = $v & 0x00ff | ((val as u16) << 8) }; }
+ macro_rules! write_l { ($v:expr) => { $v = $v & 0xff00 | (val as u16) }; }
+
+ macro_rules! no_write { () => { () }; }
+
+ match port {
+ // System
+ 0x00 => write_h!(self.wake_mask),
+ 0x01 => { write_l!(self.wake_mask); return Some(Signal::Pause) },
+ 0x02 => no_write!(),
+ 0x03 => no_write!(),
+ 0x04 => no_write!(),
+ 0x05 => no_write!(),
+ 0x06 => no_write!(),
+ 0x07 => no_write!(),
+ 0x08 => no_write!(),
+ 0x09 => no_write!(),
+ 0x0A => no_write!(),
+ 0x0B => no_write!(),
+ 0x0C => no_write!(),
+ 0x0D => no_write!(),
+ 0x0E => no_write!(),
+ 0x0F => no_write!(),
+ // Math
+ 0x20 => write_h!(self.math.operand_1),
+ 0x21 => write_l!(self.math.operand_1),
+ 0x22 => write_h!(self.math.operand_2),
+ 0x23 => write_l!(self.math.operand_2),
+ 0x24 => no_write!(),
+ 0x25 => no_write!(),
+ 0x26 => no_write!(),
+ 0x27 => no_write!(),
+ 0x28 => no_write!(),
+ 0x29 => no_write!(),
+ 0x2A => no_write!(),
+ 0x2B => no_write!(),
+ 0x2C => no_write!(),
+ 0x2D => no_write!(),
+ 0x2E => no_write!(),
+ 0x2F => no_write!(),
+ // Clock
+ 0x38 => write_h!(self.clock.timer_1),
+ 0x39 => { write_l!(self.clock.timer_1); self.clock.set_timer_1() },
+ 0x3A => write_h!(self.clock.timer_2),
+ 0x3B => { write_l!(self.clock.timer_2); self.clock.set_timer_2() },
+ 0x3C => write_h!(self.clock.timer_3),
+ 0x3D => { write_l!(self.clock.timer_3); self.clock.set_timer_3() },
+ 0x3E => write_h!(self.clock.timer_4),
+ 0x3F => { write_l!(self.clock.timer_4); self.clock.set_timer_4() },
+ // Input
+ 0x40 => no_write!(),
+ 0x41 => no_write!(),
+ 0x42 => no_write!(),
+ 0x43 => no_write!(),
+ 0x44 => no_write!(),
+ 0x45 => no_write!(),
+ 0x46 => no_write!(),
+ 0x47 => no_write!(),
+ 0x48 => self.input.character_queue.clear(),
+ 0x49 => no_write!(),
+ 0x4A => no_write!(),
+ 0x4B => no_write!(),
+ 0x4C => no_write!(),
+ 0x4D => no_write!(),
+ 0x4E => no_write!(),
+ 0x4F => no_write!(),
+ // Screen
+ 0x50 => write_h!(self.screen.dimensions.width),
+ 0x51 => { write_l!(self.screen.dimensions.width); self.screen.set_size(self.screen.dimensions) },
+ 0x52 => write_h!(self.screen.dimensions.height),
+ 0x53 => { write_l!(self.screen.dimensions.height); self.screen.set_size(self.screen.dimensions) },
+ 0x54 => write_h!(self.screen.cursor.x),
+ 0x55 => write_l!(self.screen.cursor.x),
+ 0x56 => write_h!(self.screen.cursor.y),
+ 0x57 => write_l!(self.screen.cursor.y),
+ 0x58 => self.screen.draw(val),
+ 0x59 => self.screen.shunt(val),
+ 0x5A => self.screen.sprite_data.push(val),
+ 0x5B => self.screen.sprite_data.push(val),
+ 0x5C => self.screen.set_palette_high(val),
+ 0x5D => self.screen.set_palette_low(val),
+ 0x5E => self.screen.set_sprite_colour_high(val),
+ 0x5F => self.screen.set_sprite_colour_low(val),
+ // Scratch
+ 0x80 => write_hh!(self.scratch.pointer_1),
+ 0x81 => write_hl!(self.scratch.pointer_1),
+ 0x82 => write_lh!(self.scratch.pointer_1),
+ 0x83 => write_ll!(self.scratch.pointer_1),
+ 0x84 => write_hh!(self.scratch.pointer_2),
+ 0x85 => write_hl!(self.scratch.pointer_2),
+ 0x86 => write_lh!(self.scratch.pointer_2),
+ 0x87 => write_ll!(self.scratch.pointer_2),
+ 0x88 => self.scratch.write_head_1(val),
+ 0x89 => self.scratch.write_head_1(val),
+ 0x8A => self.scratch.write_head_2(val),
+ 0x8B => self.scratch.write_head_2(val),
+ 0x8C => no_write!(),
+ 0x8D => no_write!(),
+ 0x8E => no_write!(),
+ 0x8F => no_write!(),
+ // File
+ 0xA0 => self.file.push_name_byte(val),
+ 0xA1 => no_write!(),
+ 0xA2 => no_write!(),
+ 0xA3 => no_write!(),
+ 0xA4 => write_hh!(self.file.pointer),
+ 0xA5 => write_hl!(self.file.pointer),
+ 0xA6 => write_lh!(self.file.pointer),
+ 0xA7 => write_ll!(self.file.pointer),
+ 0xA8 => no_write!(),
+ 0xA9 => no_write!(),
+ 0xAA => no_write!(),
+ 0xAB => no_write!(),
+ 0xAC => write_hh!(self.file.file_size),
+ 0xAD => write_hl!(self.file.file_size),
+ 0xAE => write_lh!(self.file.file_size),
+ 0xAF => { write_ll!(self.file.file_size); self.file.set_file_size() },
+
+ // Bytestreams
+ 0x96 => self.stream.write_stdout(val),
+ 0x97 => self.stream.write_stdout(val),
+
+ _ => unimplemented!("Writing to device port 0x{port:02x}"),
+ };
+
+ return None;
+
+ }
+}
diff --git a/src/devices/clock.rs b/src/devices/clock.rs
new file mode 100644
index 0000000..063c67b
--- /dev/null
+++ b/src/devices/clock.rs
@@ -0,0 +1,130 @@
+use std::time::{Duration, Instant};
+
+macro_rules! now { () => {
+ time::OffsetDateTime::now_local()
+ .unwrap_or_else(|_| time::OffsetDateTime::now_utc())
+};}
+
+macro_rules! time_from_ticks {
+ ($ticks:expr) => { Instant::now() + Duration::from_millis(($ticks * 4).into()) };
+}
+
+macro_rules! ticks_since_time {
+ ($time:expr) => { ($time.duration_since(Instant::now()).as_millis() / 4) as u16 };
+}
+
+/// Create a method to set the instant of a timer.
+macro_rules! generate_set_timer_method {
+ ($i:tt) => {mini_paste::item!{
+ pub fn [< set_timer_ $i >] (&mut self) {
+ self. [< timer_ $i _instant >] = time_from_ticks!(self. [< timer_ $i >]);
+ }
+ }};
+}
+
+/// Create a method to update the value of a timer.
+macro_rules! generate_update_timer_method {
+ ($i:tt) => {mini_paste::item!{
+ pub fn [< update_timer_ $i >] (&mut self) {
+ if self. [< timer_ $i >] > 0 {
+ self. [< timer_ $i >] = ticks_since_time!(self.[< timer_ $i _instant >]);
+ if self. [< timer_ $i >] == 0 { self.wake_flag = true; }
+ }
+ }
+ }};
+}
+
+pub struct ClockDevice {
+ pub wake_flag: bool,
+
+ pub boot_time: Instant,
+ pub cumulative_seconds: u16,
+
+ pub timer_1_instant: Instant,
+ pub timer_2_instant: Instant,
+ pub timer_3_instant: Instant,
+ pub timer_4_instant: Instant,
+ pub timer_1: u16,
+ pub timer_2: u16,
+ pub timer_3: u16,
+ pub timer_4: u16,
+}
+
+impl ClockDevice {
+ pub fn new() -> Self {
+ Self {
+ wake_flag: false,
+
+ boot_time: Instant::now(),
+ cumulative_seconds: 0,
+
+ timer_1_instant: Instant::now(),
+ timer_2_instant: Instant::now(),
+ timer_3_instant: Instant::now(),
+ timer_4_instant: Instant::now(),
+ timer_1: 0,
+ timer_2: 0,
+ timer_3: 0,
+ timer_4: 0,
+ }
+ }
+
+ pub fn update_cumulative_seconds(&mut self) {
+ self.cumulative_seconds = self.boot_time.elapsed().as_secs() as u16;
+ }
+
+ generate_set_timer_method!{1}
+ generate_set_timer_method!{2}
+ generate_set_timer_method!{3}
+ generate_set_timer_method!{4}
+
+ generate_update_timer_method!{1}
+ generate_update_timer_method!{2}
+ generate_update_timer_method!{3}
+ generate_update_timer_method!{4}
+
+ pub fn year(&self) -> u8 {
+ now!().year().saturating_sub(2000).try_into().unwrap_or(u8::MAX)
+ }
+
+ pub fn month(&self) -> u8 {
+ now!().month() as u8 - 1
+ }
+
+ pub fn day(&self) -> u8 {
+ now!().day() as u8 - 1
+ }
+
+ pub fn hour(&self) -> u8 {
+ now!().hour()
+ }
+
+ pub fn minute(&self) -> u8 {
+ now!().minute()
+ }
+
+ pub fn second(&self) -> u8 {
+ now!().second()
+ }
+
+ pub fn shortest_active_timer(&self) -> Option<Duration> {
+ let mut is_some = false;
+ let mut value = u16::MAX;
+ macro_rules! contribute_to_min {
+ ($timer:expr) => {
+ if $timer > 0 {
+ is_some = true;
+ value = std::cmp::min(value, $timer);
+ }
+ };
+ }
+ contribute_to_min!(self.timer_1);
+ contribute_to_min!(self.timer_2);
+ contribute_to_min!(self.timer_3);
+ contribute_to_min!(self.timer_4);
+ match is_some {
+ true => Some(Duration::from_millis((value*4).into())),
+ false => None,
+ }
+ }
+}
diff --git a/src/devices/file.rs b/src/devices/file.rs
new file mode 100644
index 0000000..f442dd8
--- /dev/null
+++ b/src/devices/file.rs
@@ -0,0 +1,90 @@
+use std::io::{ErrorKind, Read, Seek, SeekFrom};
+use std::fs::{File, OpenOptions};
+
+pub struct FileDevice {
+ pub name: Vec<u8>,
+ pub rename: Vec<u8>,
+
+ pub file: Option<File>,
+ pub operation_state: bool,
+ pub pointer: u32,
+ pub file_size: u32,
+}
+
+impl FileDevice {
+ pub fn new() -> Self {
+ Self {
+ name: Vec::new(),
+ rename: Vec::new(),
+
+ file: None,
+ operation_state: false,
+ pointer: 0,
+ file_size: 0,
+ }
+ }
+
+ pub fn push_name_byte(&mut self, byte: u8) {
+ if byte != 0 { self.name.push(byte); return }
+ // If char was null, attempt to open the file and read file size.
+ let path: std::ffi::OsString = std::os::unix::ffi::OsStringExt::from_vec(
+ std::mem::take(&mut self.name));
+ let try_open = OpenOptions::new().read(true).write(true).open(path);
+ if let Ok(file) = try_open {
+ if let Ok(metadata) = file.metadata() {
+ // Success.
+ self.file = Some(file);
+ match u32::try_from(metadata.len()) {
+ Ok(len) => self.file_size = len,
+ Err(_) => self.file_size = u32::MAX,
+ }
+ return;
+ }
+ }
+ // Failure.
+ self.close();
+ }
+
+ pub fn set_file_size(&mut self) {
+ if let Some(file) = &self.file {
+ if let Ok(()) = file.set_len(self.file_size.into()) {
+ return;
+ };
+ }
+ self.close();
+ }
+
+ pub fn read(&mut self) -> u8 {
+ if let Some(file) = &mut self.file {
+ let mut buffer: [u8; 1] = [0; 1];
+ match file.read_exact(&mut buffer) {
+ Ok(()) => return buffer[0],
+ Err(err) => match err.kind() {
+ ErrorKind::UnexpectedEof => return 0,
+ _ => unimplemented!("File read error: ErrorKind::{:?}", err.kind()),
+ }
+ }
+ }
+ self.close();
+ return 0;
+ }
+
+ pub fn seek(&mut self) {
+ if let Some(file) = &mut self.file {
+ let point = SeekFrom::Start(self.pointer.into());
+ if let Ok(pointer) = file.seek(point) {
+ if let Ok(pointer_u32) = pointer.try_into() {
+ self.pointer = pointer_u32;
+ return;
+ }
+ };
+ }
+ self.close();
+ }
+
+ pub fn close(&mut self) {
+ self.file = None;
+ self.pointer = 0;
+ self.file_size = 0;
+ }
+}
diff --git a/src/devices/input.rs b/src/devices/input.rs
new file mode 100644
index 0000000..f3191dd
--- /dev/null
+++ b/src/devices/input.rs
@@ -0,0 +1,128 @@
+use crate::*;
+
+use std::collections::VecDeque;
+
+pub struct InputDevice {
+ pub wake_flag: bool,
+
+ pub mouse_position: ScreenPosition,
+ pub mouse_button_state: u8,
+
+ pub horizontal_scroll_value: u16,
+ pub vertical_scroll_value: u16,
+ pub horizontal_scroll_value_delta: f64,
+ pub vertical_scroll_value_delta: f64,
+
+ pub character_queue: VecDeque<u8>,
+ pub modifier_state: u8,
+ pub navigation_state: u8,
+}
+
+impl InputDevice {
+ pub fn new() -> Self {
+ Self {
+ wake_flag: false,
+
+ mouse_position: ScreenPosition::ZERO,
+ mouse_button_state: 0x00,
+
+ horizontal_scroll_value: 0x0000,
+ vertical_scroll_value: 0x0000,
+ horizontal_scroll_value_delta: 0.0,
+ vertical_scroll_value_delta: 0.0,
+
+ character_queue: VecDeque::new(),
+ modifier_state: 0x00,
+ navigation_state: 0x00,
+ }
+ }
+
+ pub fn mouse_button_action(&mut self, mask: u8, action: phosphor::Action) {
+ let new_button_state = match action {
+ phosphor::Action::Pressed => self.mouse_button_state | mask,
+ phosphor::Action::Released => self.mouse_button_state & !mask,
+ };
+ if new_button_state != self.mouse_button_state {
+ self.mouse_button_state = new_button_state;
+ self.wake_flag = true;
+ }
+ }
+
+ pub fn move_mouse(&mut self, new_mouse_position: ScreenPosition) {
+ let old_mouse_position = self.mouse_position;
+ if new_mouse_position != old_mouse_position {
+ self.mouse_position = new_mouse_position;
+ self.wake_flag = true;
+ }
+ }
+
+ pub fn on_scroll_horizontal(&mut self, delta: f64) {
+ self.horizontal_scroll_value_delta += delta;
+ while self.horizontal_scroll_value_delta > 20.0 {
+ self.horizontal_scroll_value += 1;
+ self.horizontal_scroll_value_delta -= 20.0;
+ self.wake_flag = true;
+ }
+ while self.horizontal_scroll_value_delta < -20.0 {
+ self.horizontal_scroll_value -= 1;
+ self.horizontal_scroll_value_delta += 20.0;
+ self.wake_flag = true;
+ }
+ }
+
+ pub fn on_scroll_vertical(&mut self, delta: f64) {
+ self.vertical_scroll_value_delta += delta;
+ while self.vertical_scroll_value_delta > 20.0 {
+ self.vertical_scroll_value += 1;
+ self.vertical_scroll_value_delta -= 20.0;
+ self.wake_flag = true;
+ }
+ while self.vertical_scroll_value_delta < -20.0 {
+ self.vertical_scroll_value -= 1;
+ self.vertical_scroll_value_delta += 20.0;
+ self.wake_flag = true;
+ }
+ }
+
+ pub fn on_character_input(&mut self, input: char) {
+ if let Ok(ascii) = u8::try_from(u32::from(input)) {
+ self.character_queue.push_back(ascii);
+ self.wake_flag = true;
+ }
+ }
+
+ pub fn on_keyboard_input(&mut self, input: phosphor::KeyboardInput) {
+ let tab = self.modifier_state & 0x40 != 0;
+ let mask = match input.key {
+ phosphor::KeyCode::Up => 0x80,
+ phosphor::KeyCode::Down => 0x40,
+ phosphor::KeyCode::Left => 0x20,
+ phosphor::KeyCode::Right => 0x10,
+ phosphor::KeyCode::Tab => match tab { false => 0x08, true => 0x04 },
+ phosphor::KeyCode::Return => 0x02,
+ phosphor::KeyCode::Escape => 0x01,
+ _ => return,
+ };
+ let new_navigation_state = match input.action {
+ phosphor::Action::Pressed => self.navigation_state | mask,
+ phosphor::Action::Released => self.navigation_state & !mask,
+ };
+ if new_navigation_state != self.navigation_state {
+ self.navigation_state = new_navigation_state;
+ self.wake_flag = true;
+ }
+ }
+
+ pub fn on_modifier_change(&mut self, modifiers: phosphor::ModifiersState) {
+ let mut new_modifier_state = 0x00;
+ if modifiers.ctrl() { new_modifier_state |= 0x80 }
+ if modifiers.shift() { new_modifier_state |= 0x40 }
+ if modifiers.alt() { new_modifier_state |= 0x20 }
+ if modifiers.logo() { new_modifier_state |= 0x10 }
+ if new_modifier_state != self.modifier_state {
+ self.modifier_state = new_modifier_state;
+ self.wake_flag = true;
+ }
+
+ }
+}
diff --git a/src/devices/math.rs b/src/devices/math.rs
new file mode 100644
index 0000000..6260529
--- /dev/null
+++ b/src/devices/math.rs
@@ -0,0 +1,35 @@
+pub struct MathDevice {
+ pub operand_1: u16,
+ pub operand_2: u16,
+ pub product_high: u16,
+ pub product_low: u16,
+ pub quotient: u16,
+ pub remainder: u16,
+}
+
+impl MathDevice {
+ pub fn new() -> Self {
+ Self {
+ operand_1: 0x0000,
+ operand_2: 0x0000,
+ product_high: 0x0000,
+ product_low: 0x0000,
+ quotient: 0x0000,
+ remainder: 0x0000,
+ }
+ }
+
+ pub fn multiply(&mut self) {
+ let (high, low) = self.operand_1.widening_mul(self.operand_2);
+ self.product_high = high;
+ self.product_low = low;
+ }
+
+ pub fn divide(&mut self) {
+ self.quotient = self.operand_1.checked_div(self.operand_2).unwrap_or(0);
+ }
+
+ pub fn modulo(&mut self) {
+ self.remainder = self.operand_1.checked_rem(self.operand_2).unwrap_or(0);
+ }
+}
diff --git a/src/devices/scratch.rs b/src/devices/scratch.rs
new file mode 100644
index 0000000..eea011b
--- /dev/null
+++ b/src/devices/scratch.rs
@@ -0,0 +1,58 @@
+pub struct ScratchDevice {
+ memory: Vec<u8>,
+ pub max_capacity: u32,
+ pub pointer_1: u32,
+ pub pointer_2: u32,
+}
+
+impl ScratchDevice {
+ pub fn new() -> Self {
+ Self {
+ memory: Vec::new(),
+ max_capacity: 16 * 65536,
+
+ pointer_1: 0,
+ pointer_2: 0,
+ }
+ }
+
+ pub fn read_head_1(&mut self) -> u8 {
+ let value = self.read_byte(self.pointer_1);
+ self.pointer_1 += 1;
+ return value;
+ }
+
+ pub fn read_head_2(&mut self) -> u8 {
+ let value = self.read_byte(self.pointer_2);
+ self.pointer_2 += 1;
+ return value;
+ }
+
+ pub fn write_head_1(&mut self, value: u8) {
+ self.write_byte(self.pointer_1, value);
+ self.pointer_1 += 1;
+ }
+
+ pub fn write_head_2(&mut self, value: u8) {
+ self.write_byte(self.pointer_2, value);
+ self.pointer_2 += 1;
+ }
+
+ fn read_byte(&self, pointer: u32) -> u8 {
+ let pointer = pointer as usize;
+ match self.memory.get(pointer) {
+ Some(value) => *value,
+ None => 0,
+ }
+ }
+
+ fn write_byte(&mut self, pointer: u32, value: u8) {
+ if pointer < self.max_capacity {
+ let pointer = pointer as usize;
+ if pointer >= self.memory.len() {
+ self.memory.resize(pointer + 1, 0);
+ }
+ self.memory[pointer] = value;
+ }
+ }
+}
diff --git a/src/devices/screen.rs b/src/devices/screen.rs
new file mode 100644
index 0000000..df9539a
--- /dev/null
+++ b/src/devices/screen.rs
@@ -0,0 +1,469 @@
+mod sprite_data;
+mod vector_points;
+
+pub use sprite_data::*;
+pub use vector_points::*;
+
+use geometry::HasDimensions;
+use phosphor::*;
+
+use std::cmp::{min, max, Ordering};
+use std::iter::zip;
+
+pub type ScreenDimensions = geometry::Dimensions<u16>;
+pub type ScreenPosition = geometry::Point<u16>;
+
+#[derive(Copy, Clone)]
+pub enum ScreenLayer { Background, Foreground }
+
+pub struct ScreenDevice {
+ pub wake_flag: bool,
+ /// Each byte represents a screen pixel, left-right-top-bottom.
+ // Only the bottom four bits of each byte are used.
+ pub foreground: Vec<u8>,
+ pub background: Vec<u8>,
+ pub is_dirty: bool,
+ pub is_resizable: bool,
+
+ pub cursor: ScreenPosition,
+ pub dimensions: ScreenDimensions,
+ pub sprite_data: SpriteData,
+ pub palette: [Colour; 16],
+ pub palette_high: u8,
+ pub sprite_colours: [u8; 4],
+ pub vector: VectorPoints,
+}
+
+impl ScreenDevice {
+ pub fn new() -> Self {
+ Self {
+ wake_flag: false,
+
+ foreground: Vec::new(),
+ background: Vec::new(),
+ is_dirty: false,
+ is_resizable: true,
+
+ cursor: ScreenPosition::ZERO,
+ dimensions: ScreenDimensions::ZERO,
+ sprite_data: SpriteData::new(),
+ palette: [Colour::BLACK; 16],
+ palette_high: 0,
+ sprite_colours: [0; 4],
+
+ vector: VectorPoints::new(),
+ }
+ }
+
+ pub fn set_size(&mut self, dimensions: ScreenDimensions) {
+ self.is_resizable = false;
+ self.resize(dimensions);
+ }
+
+ pub fn resize(&mut self, dimensions: ScreenDimensions) {
+ let old_width = self.dimensions.width as usize;
+ let old_height = self.dimensions.height as usize;
+ let new_width = dimensions.width as usize;
+ let new_height = dimensions.height as usize;
+ let new_area = dimensions.area_usize();
+ let y_range = 0..min(old_height, new_height);
+
+ match new_width.cmp(&old_width) {
+ Ordering::Less => {
+ for y in y_range {
+ let from = y * old_width;
+ let to = y * new_width;
+ let len = new_width;
+ self.foreground.copy_within(from..from+len, to);
+ self.background.copy_within(from..from+len, to);
+ }
+ self.foreground.resize(new_area, 0);
+ self.background.resize(new_area, 0);
+ },
+ Ordering::Greater => {
+ self.foreground.resize(new_area, 0);
+ self.background.resize(new_area, 0);
+ for y in y_range.rev() {
+ let from = y * old_width;
+ let to = y * new_width;
+ let len = old_width;
+ self.foreground.copy_within(from..from+len, to);
+ self.background.copy_within(from..from+len, to);
+ self.foreground[to+len..to+new_width].fill(0);
+ self.background[to+len..to+new_width].fill(0);
+ }
+ },
+ Ordering::Equal => {
+ self.foreground.resize(new_area, 0);
+ self.background.resize(new_area, 0);
+ },
+ };
+
+ self.dimensions = dimensions;
+ self.is_dirty = true;
+ self.wake_flag = true;
+ }
+
+ pub fn render(&mut self, buffer: &mut Buffer) {
+ // Pre-calculate a lookup table for the colour palette
+ let mut palette = [Colour::BLACK; 256];
+ for (i, c) in palette.iter_mut().enumerate() {
+ match i > 0x0f {
+ true => *c = self.palette[i >> 4],
+ false => *c = self.palette[i & 0x0f],
+ }
+ };
+ let b_width = buffer.width() as usize;
+ let b_height = buffer.height() as usize;
+ let s_width = self.dimensions.width() as usize;
+ let s_height = self.dimensions.height() as usize;
+
+ // Write colours to the buffer
+ if b_width == s_width && b_height == s_height {
+ let screen_iter = zip(&self.background, &self.foreground);
+ let buffer_iter = buffer.as_mut_slice();
+ for (b, (bg, fg)) in zip(buffer_iter, screen_iter) {
+ *b = palette[(fg << 4 | bg) as usize];
+ }
+ } else {
+ let width = min(b_width, s_width);
+ let height = min(b_height, s_height);
+ let width_excess = b_width.saturating_sub(width);
+ let b_slice = &mut buffer.as_mut_slice();
+ let mut bi = 0;
+ let mut si = 0;
+ for _ in 0..height {
+ let b_iter = &mut b_slice[bi..bi+width];
+ let s_iter = zip(
+ &self.background[si..si+width],
+ &self.foreground[si..si+width],
+ );
+ for (b, (bg, fg)) in zip(b_iter, s_iter) {
+ *b = palette[(fg << 4 | bg) as usize];
+ }
+ b_slice[bi+width..bi+width+width_excess].fill(palette[0]);
+ bi += b_width;
+ si += s_width;
+ }
+ b_slice[bi..].fill(palette[0]);
+ }
+
+ // Set flags
+ self.is_dirty = false;
+ }
+
+ pub fn set_palette_high(&mut self, val: u8) {
+ self.palette_high = val;
+ }
+
+ pub fn set_palette_low(&mut self, val: u8) {
+ let index = (self.palette_high >> 4) as usize;
+ let red = (self.palette_high & 0x0f) * 17;
+ let green = (val >> 4) * 17;
+ let blue = (val & 0x0f) * 17;
+ self.palette[index] = Colour::from_rgb(red, green, blue);
+ self.is_dirty = true;
+ }
+
+ pub fn set_sprite_colour_high(&mut self, val: u8) {
+ self.sprite_colours[0] = val >> 4;
+ self.sprite_colours[1] = val & 0x0f;
+ }
+
+ pub fn set_sprite_colour_low(&mut self, val: u8) {
+ self.sprite_colours[2] = val >> 4;
+ self.sprite_colours[3] = val & 0x0f;
+ }
+
+ pub fn shunt(&mut self, val: u8) {
+ let is_negative = val & 0x80 != 0;
+ let is_vertical = val & 0x40 != 0;
+ let dist = (val & 0x3f) as u16;
+ match (is_negative, is_vertical) {
+ (false, false) => self.cursor.x = self.cursor.x.wrapping_add(dist),
+ (false, true) => self.cursor.y = self.cursor.y.wrapping_add(dist),
+ ( true, false) => self.cursor.x = self.cursor.x.wrapping_sub(dist),
+ ( true, true) => self.cursor.y = self.cursor.y.wrapping_sub(dist),
+ };
+ }
+
+ pub fn draw(&mut self, val: u8) {
+ self.vector.push(self.cursor);
+ self.is_dirty = true;
+ // Parse draw byte
+ let draw_mode = val & 0x70;
+ let params = val & 0x0f;
+ let layer = match val & 0x80 != 0 {
+ true => ScreenLayer::Foreground,
+ false => ScreenLayer::Background
+ };
+ match draw_mode {
+ 0x00 => self.draw_pixel(params, layer, self.cursor),
+ 0x10 => self.draw_sprite_1bit(params, layer),
+ 0x20 => self.fill_layer(params, layer),
+ 0x30 => self.draw_sprite_2bit(params, layer),
+ 0x40 => self.draw_line(params, layer),
+ 0x50 => self.draw_rect(params, layer),
+ 0x60 => todo!("Draw 1-bit sprite triangle"),
+ 0x70 => self.draw_rect_1bit(params, layer),
+ _ => unreachable!(),
+ };
+ }
+
+ fn draw_pixel(&mut self, colour: u8, layer: ScreenLayer, point: ScreenPosition) {
+ let dim = self.dimensions;
+ if !dim.contains_point(point) { return }
+ let index = point.x as usize + ((dim.width as usize) * (point.y as usize));
+ match layer {
+ ScreenLayer::Background => self.background[index] = colour,
+ ScreenLayer::Foreground => self.foreground[index] = colour,
+ };
+ }
+
+ fn draw_sprite_1bit(&mut self, params: u8, layer: ScreenLayer) {
+ let mut sprite = [0; 64];
+ let mut pointer: usize = 0;
+ let data = self.sprite_data.get_1bit_sprite();
+ for row in data {
+ for x in (0..8).rev() {
+ sprite[pointer] = (row >> x) & 0x1;
+ pointer += 1;
+ }
+ }
+ self.draw_sprite(params, layer, sprite);
+ }
+
+ fn fill_layer(&mut self, colour: u8, layer: ScreenLayer) {
+ match layer {
+ ScreenLayer::Background => self.background.fill(colour),
+ ScreenLayer::Foreground => self.foreground.fill(colour),
+ }
+ }
+
+ fn draw_sprite_2bit(&mut self, params: u8, layer: ScreenLayer) {
+ let mut sprite = [0; 64];
+ let mut pointer: usize = 0;
+ let data = self.sprite_data.get_2bit_sprite();
+ let (spr1, spr2) = data.split_array_ref::<8>();
+ for (row1, row2) in std::iter::zip(spr1, spr2) {
+ for x in (0..8).rev() {
+ let bit1 = (row1 >> x << 1) & 0x2;
+ let bit2 = (row2 >> x) & 0x1;
+ sprite[pointer] = bit1 | bit2;
+ pointer += 1;
+ }
+ }
+ self.draw_sprite(params, layer, sprite);
+ }
+
+ fn draw_line(&mut self, colour: u8, layer: ScreenLayer) {
+ let points = self.vector.get_pair();
+ match (points[0].x == points[1].x, points[0].y == points[1].y) {
+ (false, false) => self.draw_diagonal_line(colour, layer, points),
+ (false, true) => self.draw_horizontal_line(colour, layer, points),
+ ( true, false) => self.draw_vertical_line(colour, layer, points),
+ ( true, true) => self.draw_pixel(colour, layer, points[0]),
+ };
+ }
+
+ fn draw_diagonal_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) {
+ fn abs_diff(v0: u16, v1: u16) -> u16 {
+ let v = v1.wrapping_sub(v0);
+ if v > 0x8000 { !v + 1 } else { v }
+ }
+ let [p0, p1] = points;
+
+ // If the slope of the line is greater than 1.
+ if abs_diff(p0.y, p1.y) > abs_diff(p0.x, p1.x) {
+ // Swap points 0 and 1 so that y0 is always smaller than y1.
+ let (x0, y0, x1, y1) = match points[0].y > points[1].y {
+ true => (points[1].x, points[1].y, points[0].x, points[0].y),
+ false => (points[0].x, points[0].y, points[1].x, points[1].y),
+ };
+
+ let dy = y1 - y0;
+ let (dx, xi) = match x0 > x1 {
+ true => (x0 - x1, 0xffff),
+ false => (x1 - x0, 0x0001),
+ };
+ let dxdy2 = (dx.wrapping_sub(dy)).wrapping_mul(2);
+ let dx2 = dx * 2;
+ let mut d = dx2.wrapping_sub(dy);
+ let mut x = x0;
+
+ for y in y0..=y1 {
+ self.draw_pixel(colour, layer, ScreenPosition::new(x, y));
+ if d < 0x8000 {
+ x = x.wrapping_add(xi);
+ d = d.wrapping_add(dxdy2);
+ } else {
+ d = d.wrapping_add(dx2);
+ }
+ }
+ // If the slope of the line is less than or equal to 1.
+ } else {
+ // Swap points 0 and 1 so that x0 is always smaller than x1.
+ let (x0, y0, x1, y1) = match points[0].x > points[1].x {
+ true => (points[1].x, points[1].y, points[0].x, points[0].y),
+ false => (points[0].x, points[0].y, points[1].x, points[1].y),
+ };
+
+ let dx = x1 - x0;
+ let (dy, yi) = match y0 > y1 {
+ true => (y0 - y1, 0xffff),
+ false => (y1 - y0, 0x0001),
+ };
+ let dydx2 = (dy.wrapping_sub(dx)).wrapping_mul(2);
+ let dy2 = dy * 2;
+ let mut d = dy2.wrapping_sub(dx);
+ let mut y = y0;
+
+ for x in x0..=x1 {
+ self.draw_pixel(colour, layer, ScreenPosition::new(x, y));
+ if d < 0x8000 {
+ y = y.wrapping_add(yi);
+ d = d.wrapping_add(dydx2);
+ } else {
+ d = d.wrapping_add(dy2);
+ }
+ }
+ }
+ }
+
+ fn draw_horizontal_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) {
+ let [start, end] = points;
+ let dim = self.dimensions;
+ let x0 = min(start.x, end.x);
+ let x1 = max(start.x, end.x);
+ if (x0 >= dim.width && x1 >= dim.width) || start.y >= dim.height { return }
+ let x0 = min(x0, dim.width.saturating_sub(1));
+ let x1 = min(x1, dim.width.saturating_sub(1));
+ let row_i = (dim.width as usize) * (start.y as usize);
+ let start_i = row_i + x0 as usize;
+ let end_i = row_i + x1 as usize;
+ let layer = match layer {
+ ScreenLayer::Background => &mut self.background,
+ ScreenLayer::Foreground => &mut self.foreground,
+ };
+ layer[start_i..=end_i].fill(colour);
+ return
+ }
+
+ fn draw_vertical_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) {
+ let [start, end] = points;
+ let dim = self.dimensions;
+ let y0 = min(start.y, end.y);
+ let y1 = max(start.y, end.y);
+ if (y0 >= dim.height && y1 >= dim.height) || start.x >= dim.width { return }
+ let y0 = min(y0, dim.height.saturating_sub(1));
+ let y1 = min(y1, dim.height.saturating_sub(1));
+ let mut i = (start.x as usize) + (dim.width as usize * (y0 as usize));
+ let pixels = match layer {
+ ScreenLayer::Background => &mut self.background,
+ ScreenLayer::Foreground => &mut self.foreground,
+ };
+ for _ in y0..=y1 {
+ pixels[i] = colour;
+ i += dim.width as usize;
+ }
+ return
+ }
+
+ fn draw_rect(&mut self, colour: u8, layer: ScreenLayer) {
+ let [start, end] = self.vector.get_pair();
+ let dim = self.dimensions;
+ let x0 = min(start.x, end.x);
+ let x1 = max(start.x, end.x);
+ let y0 = min(start.y, end.y);
+ let y1 = max(start.y, end.y);
+ if (x0 >= dim.width && x1 >= dim.width) || (y0 >= dim.height && y1 >= dim.height) { return }
+ let x0 = min(x0, dim.width.saturating_sub(1)) as usize;
+ let x1 = min(x1, dim.width.saturating_sub(1)) as usize;
+ let y0 = min(y0, dim.height.saturating_sub(1)) as usize;
+ let y1 = min(y1, dim.height.saturating_sub(1)) as usize;
+ let width = x1 - x0 + 1;
+ let mut i = x0 + ((dim.width as usize) * y0);
+ let pixels = match layer {
+ ScreenLayer::Background => &mut self.background,
+ ScreenLayer::Foreground => &mut self.foreground,
+ };
+ for _ in y0..=y1 {
+ pixels[i..i+width].fill(colour);
+ i += dim.width as usize;
+ }
+ }
+
+ fn draw_rect_1bit(&mut self, params: u8, layer: ScreenLayer) {
+ let [start, end] = self.vector.get_pair();
+ let dim = self.dimensions;
+ let x0 = min(start.x, end.x);
+ let x1 = max(start.x, end.x);
+ let y0 = min(start.y, end.y);
+ let y1 = max(start.y, end.y);
+ if (x0 >= dim.width && x1 >= dim.width) || (y0 >= dim.height && y1 >= dim.height) { return }
+ let x0 = min(x0, dim.width.saturating_sub(1)) as usize;
+ let x1 = min(x1, dim.width.saturating_sub(1)) as usize;
+ let y0 = min(y0, dim.height.saturating_sub(1)) as usize;
+ let y1 = min(y1, dim.height.saturating_sub(1)) as usize;
+ let width = x1 - x0 + 1;
+ let mut i = x0 + ((dim.width as usize) * y0);
+ let pixels = match layer {
+ ScreenLayer::Background => &mut self.background,
+ ScreenLayer::Foreground => &mut self.foreground,
+ };
+
+ let sprite_data = self.sprite_data.get_1bit_sprite();
+ let mut sprite_i = y0 % 8;
+ let sprite_x_off = (x0 % 8) as u32;
+ let transparent = params & 0x08 != 0;
+ if params & 0x07 != 0 {
+ todo!("Pre-treat sprite, with rotation/translation");
+ }
+
+ for _ in y0..=y1 {
+ let mut row = sprite_data[sprite_i].rotate_left(sprite_x_off);
+ for _ in x0..=x1 {
+ let colour = (row >> 7) as usize;
+ if !(transparent && colour == 0) {
+ pixels[i] = self.sprite_colours[colour];
+ }
+ row = row.rotate_left(1);
+ i += 1;
+ }
+ sprite_i = (sprite_i + 1) % 8;
+ i += (dim.width as usize) - width;
+ }
+ }
+
+ fn draw_sprite(&mut self, params: u8, layer: ScreenLayer, sprite: [u8; 64]) {
+ let transparent = params & 0x08 != 0;
+ let mut position = self.cursor;
+ let mut pointer: usize = 0;
+
+ macro_rules! inc_x { ($v:expr) => { position.x = position.x.wrapping_add($v) }; }
+ macro_rules! dec_x { ($v:expr) => { position.x = position.x.wrapping_sub($v) }; }
+ macro_rules! inc_y { ($v:expr) => { position.y = position.y.wrapping_add($v) }; }
+ macro_rules! dec_y { ($v:expr) => { position.y = position.y.wrapping_sub($v) }; }
+ macro_rules! plot { () => {
+ let colour = sprite[pointer];
+ if !(transparent && colour == 0) {
+ self.draw_pixel(self.sprite_colours[colour as usize], layer, position);
+ }
+ pointer += 1;
+ }; }
+
+ match params & 0x07 {
+ 0x00 => { for _ in 0..8 { for _ in 0..8 { plot!(); inc_x!(1); } dec_x!(8); inc_y!(1); } }
+ 0x01 => { inc_x!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_x!(1); } inc_x!(8); inc_y!(1); } }
+ 0x02 => { inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); inc_x!(1); } dec_x!(8); dec_y!(1); } }
+ 0x03 => { inc_x!(7); inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_x!(1); } inc_x!(8); dec_y!(1); } }
+
+ 0x04 => { for _ in 0..8 { for _ in 0..8 { plot!(); inc_y!(1); } dec_y!(8); inc_x!(1); } }
+ 0x05 => { inc_x!(7); for _ in 0..8 { for _ in 0..8 { plot!(); inc_y!(1); } dec_y!(8); dec_x!(1); } }
+ 0x06 => { inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_y!(1); } inc_y!(8); inc_x!(1); } }
+ 0x07 => { inc_x!(7); inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_y!(1); } inc_y!(8); dec_x!(1); } }
+
+ _ => unreachable!(),
+ }
+ }
+}
diff --git a/src/devices/screen/sprite_data.rs b/src/devices/screen/sprite_data.rs
new file mode 100644
index 0000000..aade2c6
--- /dev/null
+++ b/src/devices/screen/sprite_data.rs
@@ -0,0 +1,33 @@
+pub struct SpriteData {
+ data: [u8; 16],
+ pointer: usize,
+}
+
+impl SpriteData {
+ pub fn new() -> Self {
+ Self { data: [0; 16], pointer: 0 }
+ }
+
+ pub fn push(&mut self, val: u8) {
+ self.data[self.pointer] = val;
+ self.pointer = (self.pointer + 1) % 16;
+ }
+
+ pub fn get_1bit_sprite(&self) -> [u8; 8] {
+ let mut sprite = [0u8; 8];
+ for (i, r) in sprite.iter_mut().enumerate() {
+ *r = self.data[(self.pointer + i + 8) % 16]
+ }
+ return sprite;
+ }
+
+ pub fn get_2bit_sprite(&self) -> [u8; 16] {
+ let mut sprite = [0u8; 16];
+ for (i, r) in sprite.iter_mut().enumerate() {
+ *r = self.data[(self.pointer + i) % 16]
+ }
+ return sprite;
+ }
+}
+
+
diff --git a/src/devices/screen/vector_points.rs b/src/devices/screen/vector_points.rs
new file mode 100644
index 0000000..97d95b6
--- /dev/null
+++ b/src/devices/screen/vector_points.rs
@@ -0,0 +1,28 @@
+use crate::*;
+
+pub struct VectorPoints {
+ points: [ScreenPosition; 3],
+ pointer: usize,
+}
+
+impl VectorPoints {
+ pub fn new() -> Self {
+ Self { points: [ScreenPosition::ZERO; 3], pointer: 0 }
+ }
+
+ pub fn push(&mut self, point: ScreenPosition) {
+ self.points[self.pointer] = point;
+ self.pointer = (self.pointer + 1) % 3;
+ }
+
+ pub fn get_pair(&self) -> [ScreenPosition; 2] {
+ [
+ self.points[(self.pointer + 1) % 3],
+ self.points[(self.pointer + 2) % 3],
+ ]
+ }
+
+ pub fn get_triple(&self) -> [ScreenPosition; 3] {
+ self.points
+ }
+}
diff --git a/src/devices/stream.rs b/src/devices/stream.rs
new file mode 100644
index 0000000..3d376b6
--- /dev/null
+++ b/src/devices/stream.rs
@@ -0,0 +1,46 @@
+use std::io::{Read, Write, Stdin, Stdout};
+
+pub struct StreamDevice {
+ pub wake_flag: bool,
+
+ pub input_control: bool,
+ pub output_control: bool,
+ pub read_queue: Vec<u8>,
+
+ pub stdin: Stdin,
+ pub stdout: Stdout,
+}
+
+impl StreamDevice {
+ pub fn new() -> Self {
+ Self {
+ wake_flag: false,
+
+ input_control: true,
+ output_control: true,
+ read_queue: Vec::with_capacity(256),
+
+ stdin: std::io::stdin(),
+ stdout: std::io::stdout(),
+ }
+ }
+
+ // pub fn fetch_stdin(&mut self) {
+ // match self.stdin.read(&mut self.read_queue) {
+ // Ok() => (),
+ // Err() => (),
+ // }
+ // }
+
+ pub fn read_stdin(&mut self) -> u8 {
+ let mut buffer = [0; 1];
+ match self.stdin.read_exact(&mut buffer) {
+ Ok(()) => buffer[0],
+ Err(_) => 0,
+ }
+ }
+
+ pub fn write_stdout(&mut self, val: u8) {
+ self.stdout.write_all(&[val]).unwrap();
+ }
+}
diff --git a/src/emulator.rs b/src/emulator.rs
new file mode 100644
index 0000000..91d33e9
--- /dev/null
+++ b/src/emulator.rs
@@ -0,0 +1,194 @@
+use crate::*;
+
+use bedrock_core::*;
+use phosphor::*;
+
+use std::time::*;
+
+const FRAME: Duration = Duration::from_micros(16666);
+
+pub struct BedrockEmulator {
+ vm: Processor<StandardDevices>,
+ process_mark: Instant,
+ frame_mark: Instant,
+ debug_mark: Instant,
+ waiting_to_start: bool,
+ is_paused: bool,
+ scale: u32,
+ cycles_since_debug: usize,
+}
+
+impl BedrockEmulator {
+ pub fn new(bytecode: &[u8]) -> Self {
+ let mut vm = Processor::new(StandardDevices::new());
+ vm.load_program(bytecode);
+ Self {
+ vm,
+ process_mark: Instant::now(),
+ frame_mark: Instant::now(),
+ debug_mark: Instant::now(),
+ waiting_to_start: true,
+ is_paused: false,
+ scale: 2,
+ cycles_since_debug: 0,
+ }
+ }
+
+ pub fn debug(&mut self) {
+ macro_rules! yellow {()=>{eprint!("\x1b[33m")};}
+ macro_rules! normal {()=>{eprint!("\x1b[0m")};}
+ macro_rules! print_stack {
+ ($stack:expr, $len:expr) => {
+ for i in 0..$len {
+ if i == $stack.sp as usize { yellow!(); } else { normal!(); }
+ eprint!("{:02x} ", $stack.mem[i]);
+ }
+ normal!();
+ };
+ }
+ eprintln!("\n PC: 0x{:04x} Cycles: {} (+{} in {:.2?})",
+ self.vm.mem.pc, self.vm.cycles,
+ self.vm.cycles - self.cycles_since_debug,
+ self.debug_mark.elapsed());
+ eprint!("WST: ");
+ print_stack!(self.vm.wst, 0x10);
+ eprint!("\nRST: ");
+ print_stack!(self.vm.rst, 0x10);
+ eprintln!();
+ self.cycles_since_debug = self.vm.cycles;
+ self.debug_mark = Instant::now();
+ }
+
+ pub fn run(self) -> ! {
+ let mut wm = WindowManager::new();
+ wm.add_window(Box::new(self));
+ wm.run();
+ }
+}
+
+impl WindowController for BedrockEmulator {
+ fn title(&self) -> String {
+ String::from("Bedrock emulator")
+ }
+
+ fn exact_size(&self) -> Option<Dimensions> {
+ match self.vm.dev.screen.is_resizable {
+ true => None,
+ false => Some(Dimensions::new(
+ self.vm.dev.screen.dimensions.width as u32,
+ self.vm.dev.screen.dimensions.height as u32,
+ )),
+ }
+ }
+
+ fn is_cursor_visible(&self) -> bool {
+ let pos = self.vm.dev.input.mouse_position;
+ let dim = self.vm.dev.screen.dimensions;
+ pos.x >= dim.width || pos.y >= dim.height
+ }
+
+ fn pixel_scale(&self) -> NonZeroU32 {
+ NonZeroU32::new(self.scale).unwrap()
+ }
+
+ fn on_resize(&mut self, dimensions: Dimensions) {
+ self.vm.dev.screen.resize(ScreenDimensions {
+ width: dimensions.width as u16,
+ height: dimensions.height as u16,
+ });
+ self.waiting_to_start = false;
+ }
+
+ fn on_cursor_move(&mut self, position: Point) {
+ self.vm.dev.input.move_mouse(ScreenPosition::new(position.x as u16, position.y as u16));
+ }
+ fn on_cursor_exit(&mut self) {
+ self.vm.dev.input.move_mouse(ScreenPosition::new(0x7FFF, 0x7FFF));
+ }
+ fn on_left_mouse_button(&mut self, action: Action) {
+ self.vm.dev.input.mouse_button_action(0x80, action);
+ }
+ fn on_middle_mouse_button(&mut self, action: Action) {
+ self.vm.dev.input.mouse_button_action(0x40, action);
+ }
+ fn on_right_mouse_button(&mut self, action: Action) {
+ self.vm.dev.input.mouse_button_action(0x20, action);
+ }
+ fn on_line_scroll_horizontal(&mut self, delta: f64) {
+ self.vm.dev.input.on_scroll_horizontal(delta * 20.0);
+ }
+ fn on_line_scroll_vertical(&mut self, delta: f64) {
+ self.vm.dev.input.on_scroll_vertical(delta * 20.0);
+ }
+ fn on_pixel_scroll_horizontal(&mut self, delta: f64) {
+ self.vm.dev.input.on_scroll_horizontal(delta);
+ }
+ fn on_pixel_scroll_vertical(&mut self, delta: f64) {
+ self.vm.dev.input.on_scroll_vertical(delta);
+ }
+ fn on_character_input(&mut self, input: char) {
+ self.vm.dev.input.on_character_input(input);
+ }
+ fn on_keyboard_input(&mut self, input: KeyboardInput) {
+ self.vm.dev.input.on_keyboard_input(input);
+ if input.action.is_pressed() {
+ match input.key {
+ KeyCode::F1 => self.scale = std::cmp::max(1, self.scale - 1),
+ KeyCode::F2 => self.scale = std::cmp::min(8, self.scale + 1),
+ _ => (),
+ }
+ }
+ }
+ fn on_keyboard_modifier_change(&mut self, modifiers: ModifiersState) {
+ self.vm.dev.input.on_modifier_change(modifiers);
+ }
+
+ fn on_process(&mut self) {
+ let frame_remaining = match self.frame_mark.elapsed() < FRAME {
+ true => FRAME.saturating_sub(self.frame_mark.elapsed()),
+ false => FRAME,
+ };
+ if self.waiting_to_start || (self.is_paused && !self.vm.dev.can_wake()) {
+ let sleep_duration = match self.vm.dev.clock.shortest_active_timer() {
+ Some(ms) => std::cmp::min(frame_remaining, ms),
+ None => frame_remaining,
+ };
+ std::thread::sleep(sleep_duration);
+ self.process_mark = Instant::now();
+ return;
+ }
+ self.is_paused = false;
+ while self.process_mark.elapsed() < frame_remaining {
+ // std::thread::sleep(std::time::Duration::from_micros(1 * 1000 as u64));
+ if let Some(signal) = self.vm.evaluate(1000) {
+ match signal {
+ Signal::Debug => self.debug(),
+ Signal::Pause => { self.is_paused = true; break },
+ Signal::Halt => exit(0),
+ }
+ }
+ }
+ std::thread::sleep(frame_remaining.saturating_sub(self.process_mark.elapsed()));
+ self.process_mark = Instant::now();
+ }
+
+ fn render_request(&mut self) -> RenderRequest {
+ if self.vm.dev.screen.is_dirty {
+ match self.is_paused {
+ true => RenderRequest::UPDATE,
+ false => match self.frame_mark.elapsed() >= 6 * FRAME {
+ true => RenderRequest::UPDATE,
+ false => RenderRequest::NONE,
+ }
+ }
+ } else {
+ self.frame_mark = Instant::now();
+ RenderRequest::NONE
+ }
+ }
+
+ fn on_render(&mut self, buffer: &mut Buffer, _hint: RenderHint) {
+ self.vm.dev.screen.render(buffer);
+ self.frame_mark = Instant::now();
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..98bc20f
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,22 @@
+#![feature(bigint_helper_methods)]
+#![feature(split_array)]
+
+use std::io::Read;
+use std::process::exit;
+
+mod devices;
+mod emulator;
+
+pub use devices::*;
+pub use emulator::*;
+
+fn main() {
+ // Read bytecode from standard input
+ let mut bytecode: Vec<u8> = Vec::new();
+ match std::io::stdin().take(64*1024).read_to_end(&mut bytecode) {
+ Ok(len) => eprintln!("Loaded {len} bytes of bytecode."),
+ Err(err) => { eprintln!("Could not read from standard input, quitting.\n({err:?})"); exit(1); }
+ };
+ BedrockEmulator::new(&bytecode).run();
+}
+