Compare commits

...

31 Commits

Author SHA1 Message Date
august 5a37caf8b2 prettier moons 2024-09-26 22:53:14 -04:00
august 3de2c63fe0 fix too wide glyphs 2024-09-26 22:11:18 -04:00
august 3856d81c47 make sure GeorgeEmu can't be constructed without a rom 2024-09-26 21:13:57 -04:00
august a621ec235a ignore Cargo.lock 2024-09-26 21:12:55 -04:00
august f464dbfaf1 enabled piping a program from stdin 2024-09-26 21:08:45 -04:00
august 7d0b66f418 build script! no more manually rebuilding cozette! 2024-09-26 21:02:03 -04:00
august 6e55faa2c0 Update georgescii encoding 2024-09-26 19:04:34 -04:00
august 952b79cf91 Big refactor! Better handling of wasm/native targets, and cute demo :) 2024-09-07 22:24:51 -04:00
august 8ac0cbc57b Very messy but we're running on the web now! (not in this crate, but will add web-sys to georgeemu bin in a bit) 2024-09-01 23:13:06 -04:00
august c2aef4f249 asm fiddling and better debug info 2024-08-28 21:32:29 -04:00
august 07d40c05d5 hehe the keyboard finally works 2024-07-05 00:19:49 -04:00
august dcaeece7fa interrupts work now! 2024-07-04 22:53:55 -04:00
august 7f8e00af23 the only thing broken is interrupts lol 2024-07-04 22:01:27 -04:00
august fb4fae430b cleaned up instructions, using termion now but is kinda broken, might switch back to crossterm 2024-07-04 13:04:13 -04:00
august ac4619406d much better cli! more ergonomic screen struct! window screen type still broken but working on it! 2024-06-30 22:49:54 -04:00
august 8890853656 fix rom help 2024-06-30 00:07:30 -04:00
august 4f0615b563 refactoring! 2024-06-30 00:03:21 -04:00
august 001d3e434c terminal mode! i like this better, also functional cli now 2024-06-29 23:38:02 -04:00
august 229b8b450d I really should be using branches for diff experiments, but we can display in the terminal now! 2024-06-23 13:23:29 -04:00
august f9198cd0b1 Architecture changes 2024-06-19 17:26:09 -04:00
august 0ec54d6672 Started work on tui debugger! 2024-06-15 22:45:27 -04:00
august b1b9c64468 nix! flakes! 2024-04-10 18:10:25 -04:00
august 5f65c32e56 Update .gitignore 2024-04-10 17:30:23 -04:00
august c154bdc89a Added interrupts, some restructuring & refactoring 2024-04-10 17:28:34 -04:00
august 705dcd3185 we can type all the letters! 2024-03-26 20:56:04 -04:00
august 40ede17ae1 we can type the letter w! 2024-03-03 13:55:56 -05:00
august 9808616203 asm cleanup 2024-02-21 23:38:33 -05:00
august 10559bde8b added data stack, breakpoints, and character coordinates :) 2024-02-21 23:27:44 -05:00
august 5b9312f643 i'm so happy she works :))))) we can display text for realll :))))))))) 2024-02-14 03:14:49 -05:00
august 95acceeabd good god that took way to long but this kinda works 2024-02-13 22:27:24 -05:00
august 78dad90fc9 Split code into modules, add font stuff 2024-02-10 00:01:32 -05:00
50 changed files with 21432 additions and 4819 deletions
+5
View File
@@ -1,2 +1,7 @@
/target /target
log log
george.o
Cargo.lock
.DS_Store
*.bin
/result
Generated
-805
View File
@@ -1,805 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[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.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cty"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]]
name = "dlib"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
"libloading",
]
[[package]]
name = "downcast-rs"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[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 = "futures"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-macro"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "georgeemu"
version = "0.1.0"
dependencies = [
"minifb",
]
[[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 = "js-sys"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
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.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[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.2",
"libc",
"redox_syscall",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "minifb"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05eddefadb505d3dcb66a89fa77dd0936e72ec84e891cc8fc36e3c05bfe61103"
dependencies = [
"cc",
"dlib",
"futures",
"instant",
"js-sys",
"lazy_static",
"libc",
"orbclient",
"raw-window-handle",
"serde",
"serde_derive",
"tempfile",
"wasm-bindgen-futures",
"wayland-client",
"wayland-cursor",
"wayland-protocols",
"winapi",
"x11-dl",
]
[[package]]
name = "nix"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "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 = [
"libc",
"libredox",
"sdl2",
"sdl2-sys",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "raw-window-handle"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41"
dependencies = [
"cty",
]
[[package]]
name = "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.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags 2.4.2",
"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 = "sdl2"
version = "0.35.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a"
dependencies = [
"bitflags 1.3.2",
"lazy_static",
"libc",
"sdl2-sys",
]
[[package]]
name = "sdl2-sys"
version = "0.35.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0"
dependencies = [
"cfg-if",
"libc",
"version-compare",
]
[[package]]
name = "serde"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "syn"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
"windows-sys 0.52.0",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]]
name = "wasm-bindgen"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
[[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",
"scoped-tls",
"wayland-commons",
"wayland-scanner",
"wayland-sys",
]
[[package]]
name = "wayland-commons"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
dependencies = [
"nix",
"once_cell",
"smallvec",
"wayland-sys",
]
[[package]]
name = "wayland-cursor"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
dependencies = [
"nix",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-protocols"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
dependencies = [
"bitflags 1.3.2",
"wayland-client",
"wayland-commons",
"wayland-scanner",
]
[[package]]
name = "wayland-scanner"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
dependencies = [
"proc-macro2",
"quote",
"xml-rs",
]
[[package]]
name = "wayland-sys"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
dependencies = [
"dlib",
"lazy_static",
"pkg-config",
]
[[package]]
name = "web-sys"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
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-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.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.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.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.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.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.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.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.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.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 = "x11-dl"
version = "2.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
dependencies = [
"libc",
"once_cell",
"pkg-config",
]
[[package]]
name = "xcursor"
version = "0.3.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"
+22 -1
View File
@@ -2,8 +2,29 @@
name = "georgeemu" name = "georgeemu"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
build = "build.rs"
[[target.'cfg(not(target_arch = "wasm32"))'.bin]]
path = "src/bin/main.rs"
name = "george"
[target.'cfg(target_arch = "wasm32")'.lib]
crate-type = ["cdylib", "rlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
bdf-parser = "0.1.0"
[dependencies] [dependencies]
minifb = "0.25.0" anyhow = "1.0.81"
minifb = { git = "https://github.com/emoon/rust_minifb" }
serde = { version = "1.0.197", features = ["serde_derive", "derive"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
web-sys = "0.3.70"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
termion = "4.0.2"
toml = { version = "0.8.12" }
+12
View File
@@ -9,3 +9,15 @@ plenty of inspiration & guidance taken from [emulator_6502](https://docs.rs/emul
see [the george wiki](https://git.augustkline.com/august/george/wiki) for how george works, why she exists, who she is, etc. see [the george wiki](https://git.augustkline.com/august/george/wiki) for how george works, why she exists, who she is, etc.
the george emulator contains 3 main structs: `Cpu`, `Mem`, and `MemMappedDevice`. `Cpu` represents a 65c02 processor, with functions for interacting with memory and executing instructions. `Mem` is a collection of `MemMappedDevice`'s, which each hold the data for some address space. `Mem` can add `MemMappedDevice`'s, and read from and write to them. A `MemMappedDevice` can have multiple banks at the same address space, and can translate 'global' addresses to 'local' ones (e.g. 0xFFFF in the system's [memory map](https://git.augustkline.com/august/george-hardware/src/branch/main/memory.md) corresponds to 0x1FFF in the ROM). the george emulator contains 3 main structs: `Cpu`, `Mem`, and `MemMappedDevice`. `Cpu` represents a 65c02 processor, with functions for interacting with memory and executing instructions. `Mem` is a collection of `MemMappedDevice`'s, which each hold the data for some address space. `Mem` can add `MemMappedDevice`'s, and read from and write to them. A `MemMappedDevice` can have multiple banks at the same address space, and can translate 'global' addresses to 'local' ones (e.g. 0xFFFF in the system's [memory map](https://git.augustkline.com/august/george-hardware/src/branch/main/memory.md) corresponds to 0x1FFF in the ROM).
## font generation
george uses a modified version of [cozette](https://github.com/slavfox/Cozette) for her main font. the modified version has 8 pixel wide characters for use with the character generator rom, and has a limit of 256 (0xFF) characters. the toolchain to generate a rom binary is still pretty clunky, and someday i might get around to streamlining it, but for now to make any changes:
- open `./src/Cozette.sfd` in fontforge
- make your edits
- to reorder glyphs, edit `./src/georgeencoding.txt` and load it with "Encoding -> Load Encoding -> (select georgeencoding.txt, name it whatever) -> Reencode -> (name of encoding)"
- ensure that there are exactly 256 characters in the font
- generate a bdf font with "File -> Generate Fonts... -> (name of the font, select bdf in the options panel)"
- open the generated bdf file in a text editor and change the line `FONTBOUNDINGBOX 11 13 0 -3` to `FONTBOUNDINGBOX 8 13 0 -3`
- now open the font in [bdf view](https://emurenmrz.github.io/bdf_view/), and export a single-row png
+83
View File
@@ -0,0 +1,83 @@
use std::{
env,
fs::File,
io::{stdin, stdout, IsTerminal, Read, Write},
ops::Neg,
os::unix::process::CommandExt,
path::Path,
process::{exit, Command},
};
// takes all charaters in bdf and returns a vec of each character row byte in order, normalized to
// width & height of the font (only works with 8 or fewer pixel wide fonts, should work for any height)
fn bdf_to_bitmap(mut bdf: File) -> [u8; 0x8000] {
let mut bdf_font_bytes = Vec::new();
bdf.read_to_end(&mut bdf_font_bytes).unwrap();
let bdf_font = bdf_parser::BdfFont::parse(&bdf_font_bytes).unwrap();
let mut bdf_vec = vec![];
for glyph in bdf_font.glyphs.iter() {
let glyph_offset_x = glyph.bounding_box.offset.x;
let glyph_offset_y = glyph.bounding_box.offset.y;
let glyph_height = glyph.bounding_box.size.y;
let font_height = bdf_font.metadata.bounding_box.size.y;
let font_offset_y = bdf_font.metadata.bounding_box.offset.y;
let top_space = font_height + font_offset_y - glyph_height - glyph_offset_y;
for _ in 0..top_space {
bdf_vec.push(0x00);
}
for bitmap in glyph.bitmap.iter() {
bdf_vec.push(bitmap >> glyph_offset_x);
}
let bottom_space = font_offset_y.neg() + glyph_offset_y;
for _ in 0..bottom_space {
bdf_vec.push(0x00);
}
}
let height = bdf_font.metadata.bounding_box.size.y as usize;
reorder_bitmap(&bdf_vec, height)
}
// takes an vec of ordered characters and translates them for use with the character rom
// TODO: make this work for any arbitrary char rom pin format using some kinda interface
fn reorder_bitmap(bitmap: &[u8], font_height: usize) -> [u8; 0x8000] {
let mut rom = [0; 0x8000]; // create vec the size of character rom
for row in 0..font_height {
for ascii_address in 0..u8::MAX {
// first 8 bits of address pick character
// next 5 bits pick row
// TODO: final 2 pick character set
let byte = bitmap[ascii_address as usize * font_height + row];
let rom_index: u16 = ((row as u16) << 8) + ascii_address as u16;
rom[rom_index as usize] = byte;
}
}
rom
}
fn rom_from_file<P>(path: P) -> [u8; 0x8000]
where
P: AsRef<Path>,
{
let file = File::open(path).unwrap();
bdf_to_bitmap(file)
}
fn main() {
let mut regen_font = Command::new("regen-font.sh");
let mut cleanup = Command::new("cleanup.sh");
regen_font.exec();
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("cozette.rom");
let cozette_rom_bytes = rom_from_file("build/cozette.bdf");
let mut cozette_rom = File::create(dest_path).unwrap();
cozette_rom.write_all(&cozette_rom_bytes).unwrap();
cleanup.exec();
}
+2410
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
#! /bin/sh
rm *.bdf*
File diff suppressed because it is too large Load Diff
+3903
View File
File diff suppressed because it is too large Load Diff
+3903
View File
File diff suppressed because it is too large Load Diff
+523
View File
@@ -0,0 +1,523 @@
# this is georgescii, august's version of extended ascii for george <3
# we're limited to 255 characters
# format: ascii byte, unicode hex, # unicode name
#
# format: three tab-separated columns
# column #1 is the ISO/IEC 8859-1 code (in hex as 0xXX)
# column #2 is the Unicode (in hex as 0xXXXX)
# column #3 the Unicode name (follows a comment sign, '#')
# 0x00 0x00 #
# 0x01 0x2591 # ░
# 0x02 0x2592 # ▒
# 0x03 0x2593 # ▓
# 0x04 0x2661 # ♡
# 0x05 0x2665 # ♥
# 0x06 0x2B50 # ⭐
# 0x07 0x272D # ✭
# 0x08 0xF005 # 
# 0x09 0x2726 # ✦
# 0x0a 0x2728 # ✨
# 0x0b 0x2640 # ♀
# 0x0c 0x2642 # ♂
# 0x0d 0x26A2 # ⚢
# 0x0E 0x26A3 # ⚣
# 0x0F 0x26A5 # ⚥
# 0x10 0x2669 # ♩
# 0x11 0x266A # ♪
# 0x12 0x266B # ♫
# 0x13 0x266C # ♬
# 0x14 0xFC5D # ﱝ
# 0x15 0xF026 # 
# 0x16 0xF027 # 
# 0x17 0xF028 # 
# 0x18 0xFA7E # 奄
# 0x19 0xFA7F # 奔
# 0x1A 0xFA80 # 婢
# 0x1B 0xFC5C # ﱜ
# 0x1C 0xFC5B # ﱛ
# 0x1D 0xF0AC # 
# 0x1E 0xF04B # 
# 0x1F 0xF04D # 
# 0x20 0x0020 #
# 0x21 0x0021 # !
# 0x22 0x0022 # "
# 0x23 0x0023 # #
# 0x24 0x0024 # $
# 0x25 0x0025 # %
# 0x26 0x0026 # &
# 0x27 0x0027 # '
# 0x28 0x0028 # (
# 0x29 0x0029 # )
# 0x2A 0x002A # *
# 0x2B 0x002B # +
# 0x2C 0x002C # ,
# 0x2D 0x002D # -
# 0x2E 0x002E # .
# 0x2F 0x002F # /
# 0x30 0x0030 # 0
# 0x31 0x0031 # 1
# 0x32 0x0032 # 2
# 0x33 0x0033 # 3
# 0x34 0x0034 # 4
# 0x35 0x0035 # 5
# 0x36 0x0036 # 6
# 0x37 0x0037 # 7
# 0x38 0x0038 # 8
# 0x39 0x0039 # 9
# 0x3A 0x003A # :
# 0x3B 0x003B # ;
# 0x3C 0x003C # <
# 0x3D 0x003D # =
# 0x3E 0x003E # >
# 0x3F 0x003F # ?
# 0x40 0x0040 # @
# 0x41 0x0041 # A
# 0x42 0x0042 # B
# 0x43 0x0043 # C
# 0x44 0x0044 # D
# 0x45 0x0045 # E
# 0x46 0x0046 # F
# 0x47 0x0047 # G
# 0x48 0x0048 # H
# 0x49 0x0049 # I
# 0x4A 0x004A # J
# 0x4B 0x004B # K
# 0x4C 0x004C # L
# 0x4D 0x004D # M
# 0x4E 0x004E # N
# 0x4F 0x004F # O
# 0x50 0x0050 # P
# 0x51 0x0051 # Q
# 0x52 0x0052 # R
# 0x53 0x0053 # S
# 0x54 0x0054 # T
# 0x55 0x0055 # U
# 0x56 0x0056 # V
# 0x57 0x0057 # W
# 0x58 0x0058 # X
# 0x59 0x0059 # Y
# 0x5A 0x005A # Z
# 0x5B 0x005B # [
# 0x5C 0x005C # \
# 0x5D 0x005D # ]
# 0x5E 0x005E # ^
# 0x5F 0x005F # _
# 0x60 0x0060 # `
# 0x61 0x0061 # a
# 0x62 0x0062 # b
# 0x63 0x0063 # c
# 0x64 0x0064 # d
# 0x65 0x0065 # e
# 0x66 0x0066 # f
# 0x67 0x0067 # g
# 0x68 0x0068 # h
# 0x69 0x0069 # i
# 0x6A 0x006A # j
# 0x6B 0x006B # k
# 0x6C 0x006C # l
# 0x6D 0x006D # m
# 0x6E 0x006E # n
# 0x6F 0x006F # o
# 0x70 0x0070 # p
# 0x71 0x0071 # q
# 0x72 0x0072 # r
# 0x73 0x0073 # s
# 0x74 0x0074 # t
# 0x75 0x0075 # u
# 0x76 0x0076 # v
# 0x77 0x0077 # w
# 0x78 0x0078 # x
# 0x79 0x0079 # y
# 0x7A 0x007A # z
# 0x7B 0x007B # {
# 0x7C 0x007C # |
# 0x7D 0x007D # }
# 0x7E 0x007E # ~
# 0x7F 0x2500 # ─
# 0x80 0x2502 # │
# 0x81 0x250C # ┌
# 0x82 0x2514 # └
# 0x83 0x251C # ├
# 0x84 0x2524 # ┤
# 0x85 0x252C # ┬
# 0x86 0x2534 # ┴
# 0x87 0x253C # ┼
# 0x88 0x256D # ╭
# 0x89 0x256E # ╮
# 0x8A 0x256F # ╯
# 0x8B 0x2570 # ╰
# 0x8C 0x2571 #
# 0x8D 0x2572 # ╲
# 0x8E 0x2573 #
# 0x8F 0x2550 # ═
# 0x90 0x2551 # ║
# 0x91 0x2554 # ╔
# 0x92 0x2557 # ╗
# 0x93 0x255a # ╚
# 0x94 0x255D # ╝
# 0x95 0x2560 # ╠
# 0x96 0x2563 # ╣
# 0x97 0x2566 # ╦
# 0x98 0x2569 # ╩
# 0x99 0x256C # ╬
# 0x9A 0xF04E # 
# 0x9B 0xF050 # 
# 0x9C 0xF051 # 
# 0x9D 0xF052 # 
# 0x9E 0xF048 # 
# 0x9F 0xE0B0 # 
# 0xA0 0xE0B2 # 
# 0xA1 0xE0B4 # 
# 0xA2 0xE0B6 # 
# 0xA3 0xE0B8 # 
# 0xA4 0xE0BA # 
# 0xA5 0xE0BC # 
# 0xA6 0xE0BE # 
# 0xA7 0x2581 # ▁
# 0xA8 0x2582 # ▂
# 0xA9 0x2583 # ▃
# 0xAA 0x2584 # ▄
# 0xAB 0x2585 # ▅
# 0xAC 0x2586 # ▆
# 0xAD 0x2587 # ▇
# 0xAE 0x2588 # █
# 0xAF 0x2589 # ▉
# 0xB0 0x258A # ▊
# 0xB1 0x258B # ▋
# 0xB2 0x258C # ▌
# 0xB3 0x258D # ▍
# 0xB4 0x258E # ▎
# 0xB5 0x258F # ▏
# 0xB6 0x0295 # ʕ
# 0xB7 0x00B7 # ·
# 0xB8 0x1D25 # ᴥ
# 0xB9 0x0294 # ʔ
# 0xBA 0x2596 # ▖
# 0xBB 0x2597 # ▗
# 0xBC 0x2598 # ▘
# 0xBD 0x2599 # ▙
# 0xBE 0x259A # ▚
# 0xBF 0x259B # ▛
# 0xC0 0x259C # ▜
# 0xC1 0x259D # ▝
# 0xC2 0x259E # ▞
# 0xC3 0x259F # ▟
# 0xC4 0x2190 # ←
# 0xC5 0x2191 # ↑
# 0xC6 0x2192 # →
# 0xC7 0x2193 # ↓
# 0xC8 0x2B60 # ⭠
# 0xC9 0x2B61 # ⭡
# 0xCA 0x2B62 # ⭢
# 0xCB 0x2B63 # ⭣
# 0xCC 0x2B80 # ⮀
# 0xCD 0x2B81 # ⮁
# 0xCE 0x2B82 # ⮂
# 0xCF 0x2B83 # ⮃
# 0xD0 0xF049 # 
# 0xD1 0xF04A # 
# 0xD2 0x23F3 # ⏳
# 0xD3 0xF07B # 
# 0xD4 0xF07C # 
# 0xD5 0xF114 # 
# 0xD6 0xF115 # 
# 0xD7 0xF250 # 
# 0xD8 0xF251 # 
# 0xD9 0xF253 # 
# 0xDA 0xF254 # 
# 0xDB 0xF461 # 
# 0xDC 0xF016 # 
# 0xDD 0xF401 # 
# 0xDE 0x1F52E # 🔮
# 0xDF 0xF2DB # 
# 0xE0 0xF008 # 
# 0xE1 0x25C7 # ◇
# 0xE2 0x25C8 # ◈
# 0xE3 0x1F311 # 🌑
# 0xE4 0x1F312 # 🌒
# 0xE5 0x1F313 # 🌓
# 0xE6 0x1F314 # 🌔
# 0xE7 0x1F315 # 🌕
# 0xE8 0x1F316 # 🌖
# 0xE9 0x1F317 # 🌗
# 0xEA 0x1F318 # 🌘
# 0xEB 0xF04C # 
# 0xEC 0x2714 # ✔
# 0xED 0x2718 # ✘
# 0xEE 0x25C6 # ◆
# 0xEF 0xF15D # 
# 0xF0 0xF15E # 
# 0xF1 0xF071 # 
# 0xF2 0xF449 # 
# 0xF3 0xF529 # 
# 0xF4 0xF658 # 
# 0xF5 0xF659 # 
# 0xF6 0x1f381 # 🎁
# 0xF7 0xf05a # 
# 0xF8 0xf06a # 
# 0xF9 0xf834 # 
# 0xFA 0xf835 # 
# 0xFB 0x2690 # ⚐
# 0xFC 0x2691 # ⚑
# 0xFD 0xf8d7 # 
# 0xFE 0xf0e7 # 
# 0xFF 0xf7d9 # 
0x00 0x00 #
0x01 0x0295 # ʕ
0x02 0x00B7 # ·
0x03 0x1D25 # ᴥ
0x04 0x0294 # ʔ
0x05 0x2661 # ♡
0x06 0x2665 # ♥
0x07 0x2726 # ✦
0x08 0x25C7 # ◇
0x09 0x25C6 # ◆
0x0a 0x272D # ✭
0x0b 0xF005 # 
0x0c 0x2728 # ✨
0x0d 0x000d # CR
0x0e 0x2518 # ┘
0x0f 0x2514 # └
0x10 0x250c # ┌
0x11 0x2510 # ┐
0x12 0x2500 # ─
0x13 0x2502 # │
0x14 0x2524 # ┤
0x15 0x2534 # ┴
0x16 0x251C # ├
0x17 0x252C # ┬
0x18 0x253C # ┼
0x19 0x2571 #
0x1a 0x2572 # ╲
0x1b 0x2573 #
0x1c 0x2591 # ░
0x1d 0x2592 # ▒
0x1e 0x2593 # ▓
0x1f 0x2588 # █
0x20 0x0020 #
0x21 0x0021 # !
0x22 0x0022 # "
0x23 0x0023 # #
0x24 0x0024 # $
0x25 0x0025 # %
0x26 0x0026 # &
0x27 0x0027 # '
0x28 0x0028 # (
0x29 0x0029 # )
0x2a 0x002A # *
0x2b 0x002B # +
0x2c 0x002C # ,
0x2d 0x002D # -
0x2e 0x002E # .
0x2f 0x002F # /
0x30 0x0030 # 0
0x31 0x0031 # 1
0x32 0x0032 # 2
0x33 0x0033 # 3
0x34 0x0034 # 4
0x35 0x0035 # 5
0x36 0x0036 # 6
0x37 0x0037 # 7
0x38 0x0038 # 8
0x39 0x0039 # 9
0x3a 0x003A # :
0x3b 0x003B # ;
0x3c 0x003C # <
0x3d 0x003D # =
0x3e 0x003E # >
0x3f 0x003F # ?
0x40 0x0040 # @
0x41 0x0041 # A
0x42 0x0042 # B
0x43 0x0043 # C
0x44 0x0044 # D
0x45 0x0045 # E
0x46 0x0046 # F
0x47 0x0047 # G
0x48 0x0048 # H
0x49 0x0049 # I
0x4a 0x004A # J
0x4b 0x004B # K
0x4c 0x004C # L
0x4d 0x004D # M
0x4e 0x004E # N
0x4f 0x004F # O
0x50 0x0050 # P
0x51 0x0051 # Q
0x52 0x0052 # R
0x53 0x0053 # S
0x54 0x0054 # T
0x55 0x0055 # U
0x56 0x0056 # V
0x57 0x0057 # W
0x58 0x0058 # X
0x59 0x0059 # Y
0x5a 0x005A # Z
0x5b 0x005B # [
0x5c 0x005C # \
0x5d 0x005D # ]
0x5e 0x005E # ^
0x5f 0x005F # _
0x60 0x0060 # `
0x61 0x0061 # a
0x62 0x0062 # b
0x63 0x0063 # c
0x64 0x0064 # d
0x65 0x0065 # e
0x66 0x0066 # f
0x67 0x0067 # g
0x68 0x0068 # h
0x69 0x0069 # i
0x6a 0x006A # j
0x6b 0x006B # k
0x6c 0x006C # l
0x6d 0x006D # m
0x6e 0x006E # n
0x6f 0x006F # o
0x70 0x0070 # p
0x71 0x0071 # q
0x72 0x0072 # r
0x73 0x0073 # s
0x74 0x0074 # t
0x75 0x0075 # u
0x76 0x0076 # v
0x77 0x0077 # w
0x78 0x0078 # x
0x79 0x0079 # y
0x7a 0x007A # z
0x7b 0x007B # {
0x7c 0x007C # |
0x7d 0x007D # }
0x7e 0x007E # ~
0x7f 0x256F # ╯
0x80 0x2570 # ╰
0x81 0x256D # ╭
0x82 0x256E # ╮
0x83 0x255D # ╝
0x84 0x255a # ╚
0x85 0x2554 # ╔
0x86 0x2557 # ╗
0x87 0x2550 # ═
0x88 0x2551 # ║
0x89 0x2563 # ╣
0x8a 0x2569 # ╩
0x8b 0x2560 # ╠
0x8c 0x2566 # ╦
0x8d 0x256C # ╬
0x8e 0xE0B8 # 
0x8f 0xE0BA # 
0x90 0xE0BC # 
0x91 0xE0BE # 
0x92 0xE0B2 # 
0x93 0xE0B0 # 
0x94 0xE0B6 # 
0x95 0xE0B4 # 
0x96 0x2596 # ▖
0x97 0x2597 # ▗
0x98 0x2598 # ▘
0x99 0x2599 # ▙
0x9a 0x259A # ▚
0x9b 0x259B # ▛
0x9c 0x259C # ▜
0x9d 0x259D # ▝
0x9e 0x259E # ▞
0x9f 0x259F # ▟
0xa0 0x2581 # ▁
0xa1 0x2582 # ▂
0xa2 0x2583 # ▃
0xa3 0x2584 # ▄
0xa4 0x2585 # ▅
0xa5 0x2586 # ▆
0xa6 0x2587 # ▇
0xa7 0x2589 # ▉
0xa8 0x258A # ▊
0xa9 0x258B # ▋
0xaa 0x258C # ▌
0xab 0x258D # ▍
0xac 0x258E # ▎
0xad 0x258F # ▏
0xae 0x1F311 # 🌑
0xaf 0x1F312 # 🌒
0xb0 0x1F313 # 🌓
0xb1 0x1F314 # 🌔
0xb2 0x1F315 # 🌕
0xb3 0x1F316 # 🌖
0xb4 0x1F317 # 🌗
0xb5 0x1F318 # 🌘
0xb6 0xF254 # 
0xb7 0xF251 # 
0xb8 0x23F3 # ⏳
0xb9 0xF253 # 
0xba 0xF250 # 
0xbb 0x2190 # ←
0xbc 0x2191 # ↑
0xbd 0x2192 # →
0xbe 0x2193 # ↓
0xbf 0x2B60 # ⭠
0xc0 0x2B61 # ⭡
0xc1 0x2B62 # ⭢
0xc2 0x2B63 # ⭣
0xc3 0x2B80 # ⮀
0xc4 0x2B81 # ⮁
0xc5 0x2B82 # ⮂
0xc6 0x2B83 # ⮃
0xc7 0xF049 # 
0xc8 0xF04A # 
0xc9 0xF048 # 
0xca 0xF04B # 
0xcb 0xF04C # 
0xcc 0xF04D # 
0xcd 0xF052 # 
0xce 0xF051 # 
0xcf 0xF04E # 
0xd0 0xF050 # 
0xd1 0xFC5D # ﱝ
0xd2 0xF026 # 
0xd3 0xF027 # 
0xd4 0xF028 # 
0xd5 0xFA80 # 婢
0xd6 0xFC5C # ﱜ
0xd7 0xFC5B # ﱛ
0xd8 0x2669 # ♩
0xd9 0x266A # ♪
0xda 0x266B # ♫
0xdb 0x266C # ♬
0xdc 0xF016 # 
0xdd 0xF07B # 
0xde 0xF07C # 
0xdf 0xF114 # 
0xe0 0xF115 # 
0xe1 0xF15D # 
0xe2 0xF15E # 
0xe3 0xF529 # 
0xe4 0xF071 # 
0xe5 0xF449 # 
0xe6 0xf05a # 
0xe7 0xF659 # 
0xe8 0xF658 # 
0xe9 0xf835 # 
0xea 0xf834 # 
0xeb 0x2690 # ⚐
0xec 0x2691 # ⚑
0xed 0xf7d9 # 
0xee 0x2714 # ✔
0xef 0x2718 # ✘
0xf0 0xf0e7 # 
0xf1 0xF2DB # 
0xf2 0xF008 # 
0xf3 0xF461 # 
0xf4 0x2B50 # ⭐
0xf5 0xF401 # 
0xf6 0x1F52E # 🔮
0xf7 0x1f381 # 🎁
0xf8 0xf8d7 # 
0xf9 0xF0AC # 
0xfa 0x25C8 # ◈
0xfb 0x2640 # ♀
0xfc 0x2642 # ♂
0xfd 0x26A2 # ⚢
0xfe 0x26A3 # ⚣
0xff 0x26A5 # ⚥
+5
View File
@@ -0,0 +1,5 @@
#! /bin/sh
fontforge -lang=ff -c 'Open($1); LoadEncodingFile($2, "george"); Reencode("george"); Generate($3)' Cozette.sfd georgeencoding.txt cozette.bdf
sed -i'' -e 's/FONTBOUNDINGBOX 11 13 0 -3/FONTBOUNDINGBOX 8 13 0 -3/' *.bdf
mv cozette-13.bdf cozette.bdf
Generated
+112
View File
@@ -0,0 +1,112 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1698420672,
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
"owner": "nmattia",
"repo": "naersk",
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
"type": "github"
},
"original": {
"owner": "nmattia",
"ref": "master",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1712696601,
"narHash": "sha256-puFPFSa/RC83JilUgB48/VL387eu2QN066Jv6X971LY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "062fc6cf99d809921ecef47317752fc92468e6ae",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1712608508,
"narHash": "sha256-vMZ5603yU0wxgyQeHJryOI+O61yrX2AHwY6LOFyV1gM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4cba8b53da471aea2ab2b0c1f30a81e7c451f4b6",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"naersk": "naersk",
"nixpkgs": "nixpkgs_2",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+62
View File
@@ -0,0 +1,62 @@
{
inputs = {
naersk.url = "github:nmattia/naersk/master";
# This must be the stable nixpkgs if you're running the app on a
# stable NixOS install. Mixing EGL library versions doesn't work.
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
utils.url = "github:numtide/flake-utils";
flake-compat = {
url = github:edolstra/flake-compat;
flake = false;
};
};
outputs = { self, nixpkgs, utils, naersk, ... }:
utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
naersk-lib = pkgs.callPackage naersk { };
libPath = with pkgs; lib.makeLibraryPath [
libxkbcommon
wayland
xorg.libX11
xorg.libXcursor
xorg.libXi
xorg.libXrandr
];
in
{
defaultPackage = naersk-lib.buildPackage {
src = ./.;
doCheck = true;
pname = "georgeemu";
nativeBuildInputs = [ pkgs.makeWrapper ];
buildInputs = with pkgs; [
xorg.libxcb
];
postInstall = ''
wrapProgram "$out/bin/georgeemu" --prefix LD_LIBRARY_PATH : "${libPath}"
'';
};
defaultApp = utils.lib.mkApp {
drv = self.defaultPackage."${system}";
};
devShell = with pkgs; mkShell {
buildInputs = [
cargo
cargo-insta
pre-commit
rust-analyzer
rustPackages.clippy
rustc
rustfmt
xorg.libxcb
];
RUST_SRC_PATH = rustPlatform.rustLibSrc;
LD_LIBRARY_PATH = libPath;
};
});
}
+3
View File
@@ -0,0 +1,3 @@
char_rom = "./src/roms/cozette.rom"
rom = "./src/roms/keyboard_sys.rom"
screen = "Window"
+11
View File
@@ -0,0 +1,11 @@
.org $00
.byte $80
.org $8000
reset:
bbr7 $00, reset
.org $fffc
.word reset
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+238
View File
@@ -0,0 +1,238 @@
; .setcpu "65C02"
.include "./macro.inc"
.org $8000
n = $01 ; temporary storage for data stack operations
temp = $20 ; scratchpad page
cursor = $300
cursor_x = cursor
cursor_y = cursor + 1
rand_index = $200
reset:
sei
ldx #0; initialize data stack pointer
init:
cli
main:
jsr print
jsr rand_draw
jmp main
newline: ; sets cursor to start of next line
stz cursor_x
lda cursor_y
cmp #28
bne .end
stz cursor_y
rts
.end:
inc cursor_y
rts
text:
.byte 1,2,3,2,4
.asciiz "- george loves u <3"
random_y:
.byte 25,12,0,20,4,25,5,13
.byte 20, 1, 1, 22, 12, 19, 19, 19
.byte 9, 0, 4, 18, 13, 14, 4, 16
.byte 8, 17, 21, 14, 23, 21, 9, 0
.byte 14, 10, 14, 2, 26, 18, 15, 23
.byte 12, 2, 5, 4, 25, 20, 27, 4
.byte 28, 21, 3, 22, 11, 25, 2, 25
.byte 13, 17, 17, 24, 8, 8, 20, 21
.byte 11, 24, 27, 25, 8, 12, 7, 0
.byte 27, 12, 19, 27, 10, 3, 19, 2
.byte 2, 23, 22, 5, 26, 28, 4, 16
.byte 18, 7, 10, 9, 6, 19, 9, 2
.byte 14, 8, 14, 18, 18, 2, 13, 0
.byte 15, 26, 3, 23, 17, 12, 18, 11
.byte 4, 16, 17, 22, 9, 25, 3, 15
.byte 28, 3, 6, 14, 25, 5, 21, 8
.byte 15, 18, 15, 5, 28, 6, 15, 4
.byte 10, 1, 16, 24, 6, 9, 22, 3
.byte 17, 18, 10, 19, 27, 11, 22, 16
.byte 22, 17, 15, 6, 23, 11, 11, 11
.byte 4, 15, 5, 25, 19, 1, 8, 26
.byte 21, 20, 17, 27, 11, 3, 11, 20
.byte 15, 28, 0, 6, 14, 23, 20, 21
.byte 17, 20, 16, 15, 19, 6, 21, 19
.byte 15, 27, 1, 22, 7, 0, 5, 2
.byte 14, 24, 15, 4, 20, 16, 1, 14
.byte 4, 16, 4, 8, 13, 26, 3, 9
.byte 12, 25, 5, 0, 7, 17, 14, 20
.byte 2, 26, 2, 27, 18, 23, 5, 8
.byte 4, 21, 10, 11, 28, 22, 6, 6
.byte 10, 13, 23, 12, 20, 28, 20, 1
.byte 27, 19, 25, 6, 1, 10, 1
random_x:
.byte 42, 59, 11, 5, 18, 0, 26, 1
.byte 61, 16, 1, 51, 36, 47, 23, 1
.byte 16, 50, 46, 4, 55, 31, 15, 2
.byte 45, 21, 59, 53, 15, 43, 0, 2
.byte 64, 31, 38, 41, 25, 12, 12, 3
.byte 30, 13, 64, 44, 21, 8, 48, 3
.byte 46, 1, 2, 33, 4, 32, 59, 28
.byte 4, 24, 58, 53, 21, 41, 30, 2
.byte 56, 53, 31, 10, 42, 12, 9, 54
.byte 14, 14, 24, 29, 43, 60, 54, 26
.byte 5, 53, 17, 55, 27, 46, 31, 3
.byte 26, 44, 63, 30, 10, 34, 62, 48
.byte 42, 47, 51, 7, 55, 32, 14, 21
.byte 15, 26, 52, 37, 48, 0, 13, 2
.byte 50, 20, 35, 32, 8, 41, 2, 24
.byte 18, 9, 52, 22, 52, 12, 19, 32
.byte 29, 46, 34, 58, 54, 51, 43, 57
.byte 62, 10, 12, 57, 36, 39, 4, 30
.byte 38, 9, 30, 32, 33, 57, 3, 25
.byte 21, 36, 59, 30, 19, 39, 9, 60
.byte 34, 50, 52, 37, 34, 42, 3, 33
.byte 40, 19, 2, 26, 10, 38, 46, 30
.byte 3, 1, 19, 16, 26, 58, 42, 49
.byte 63, 1, 63, 41, 0, 21, 41, 19
.byte 21, 45, 44, 52, 20, 5, 11, 64
.byte 1, 62, 16, 5, 5, 8, 58, 56
.byte 16, 26, 6, 37, 19, 16, 25, 29
.byte 64, 59, 16, 6, 41, 28, 8, 51
.byte 54, 5, 19, 28, 13, 38, 52, 35
.byte 42, 13, 34, 33, 61, 61, 7, 27
.byte 38, 33, 9, 57, 10, 30, 8, 4
.byte 46, 3, 39, 46, 62, 20, 48, 7
; increments the cursor line by line, looping to (0, 0) after (63, 28)
inc_cursor:
lda cursor_x
cmp #63
beq .newline
inc cursor_x
rts
.newline:
lda cursor_y
cmp #28
beq .newscreen
stz cursor_x
inc cursor_y
rts
.newscreen:
stz cursor_y
stz cursor_x
rts
; zeroes out the display, resets cursor to 0,0
clear:
lda #0
ldy #0
.loop:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne .loop
stz cursor
stz cursor + 1
rts
; prints string from cursor position, stopping at end of string or at 256 chars, whichever comes first
; $6000 + (64*Y) + X
; THIS WILL WRITE OUT OF BOUNDS IF THE CURSOR IS OUT OF BOUNDS/STRING IS TOO LONG
; TODO: figure out a simple way of writing arbitrary length strings
print:
jsr cursor_addr
ldy #0
; y_overflow = temp + 5
.loop:
lda text, y
beq .end
sta (temp), y
iny
bra .loop
.end:
rts
; calculates real vram address from cursor (x, y)
cursor_addr:
stz temp
stz temp + 1
lda cursor_y
beq .add_x ; if y's zero just add x
.y_mult:
; multiply by 64
clc
asl
rol temp + 1
asl
rol temp + 1
asl
rol temp + 1
asl
rol temp + 1
asl
rol temp + 1
asl
rol temp + 1
sta temp
.add_x:
clc
lda cursor_x
adc temp
sta temp
lda #0
adc temp + 1
sta temp + 1
clc
lda #$60
adc temp + 1
sta temp + 1
rts
rand_draw:
ldx rand_index
lda random_x, x
sta cursor_x
lda random_y, x
sta cursor_y
jsr cursor_addr
inc rand_index
rts
isr: ; interrupt service routine
pha
phx
phy
; jsr irq
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr
BIN
View File
Binary file not shown.
+189
View File
@@ -0,0 +1,189 @@
; .setcpu "65C02"
.include "./macro.inc"
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates
n = $01 ; temporary storage for data stack operations
key_row = $200 ; used for character lookup when key pressed
key_col = $201
cursor = $202
char_buffer = $300 ; 256 byte character buffer
kb_row = $4400 ; keyboard hardware register
kb_row_cache = $203 ; cache
.org $8000
reset:
sei
ldx #0; initialize data stack pointer
initdisplay:
lda #0
ldy #0
cleardisplay:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne cleardisplay
cli
print_test:
lda #0
sta key_row
lda #5
sta key_col
push_coords #5, #5
main:
; jsr printtext
; key_zero:
; stz keyboard_cache, x
; dex
; bpl key_zero
; fim:
; cli
; bra fim
jsr print
; jsr print
stp
jmp main
; keyboard: ; reads keyboard registers and stores the column and row of the first key found
; ; TODO: make this routine store up to 8 indices (for 8 key rollover)
; ldy #0
; .check_row: ; loop through each row
; lda kb_row, y
; beq .skip_row ; if row has no key pressed, skip checking which key
; ; jmp key_down
; sta kb_row_cache, y ; if key pressed, cache it
; lda kb_row, y
; cmp kb_row_cache, y ; has key changed?
; beq key_down
; .skip_row:
; iny
; cpy #5
; bne .check_row
; rts
; key_down: ; a is loaded with the row byte
; phy
; sty key_row ; store character row
; ldy #0
; .find_col: ; test each row bit, store column if key pressed
; lsr ; test bit 7
; bcs store_col ; if unset, don't go store character columnb
; .skip:
; iny
; cpy #8
; bne .find_col ; loop until we've checked each bit
; rts
; store_col:
; sty key_col
; jsr print
; rts
print: ; x y -- prints the key indexed with key_col and key_row at position x, y
keymap_index:
push
lda key_col
stz 1, x
sta 0, x
push
lda #8
stz 1, x
sta 0, x
push
lda key_row
stz 1, x
sta 0, x
jsr mult
jsr plus
lda 0, x
tay
lda keymap, y
push
sta 0, x
stz 1, x
jsr draw_char
rts
keymap:
.byte "?outrew?"
.byte "?piygsq?"
.byte "a??khvd?"
.byte "42ljbfz?"
.byte "31?mncx?"
.byte "????? m"
draw:
; push_coords #0, #0
; push_char #$00
; jsr draw_char
rts
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
lda 0, x ; load a with character to draw
pop ; and pop it off the stack
jsr get_char_address ; calculate where to put the character in memory
sta (0, x) ; store a at the address pointed to on the stack
rts
get_char_address: ; gets vram address for a character at (x, y),
; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first
;.byte 64
;.byte 0
pha
lda #64
push ; doing this instead until `push_lit` is fixed
sta 0, x
stz 1, x
jsr mult ; multiply 64 with y (n2)
jsr plus ; add result with x (n1)
;jsr push_lit ; push vram address onto the stack
;.byte $00
;.byte $60
lda #$60
push
sta 1, x
stz 0, x
jsr plus ; add vram start address to result
pla
rts
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
jsr get_char_address
isr: ; interrupt service routine
pha
phx
phy
; jsr keyboard
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr
BIN
View File
Binary file not shown.
+124
View File
@@ -0,0 +1,124 @@
; .setcpu "65C02"
.include "./macro.inc"
.org $8000
n = $01 ; temporary storage for data stack operations
key_buffer = $200
reset:
sei
ldx #0; initialize data stack pointer
initdisplay:
lda #0
ldy #0
cleardisplay:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne cleardisplay
; cli
main:
jsr read_keys
lda key_buffer
sta $6000
; lda $4400
; sta $6000
jmp main
; first, let's do the simplest case of just the letter a being pressed
; 1. check row 2 ($4402, where the a key is) for pressed keys
; 2. if any keys are pressed, check if the key is a (bit 0)
; 3. if the key is not a, store zero in the key buffer
; 4. if the key is a, store the ascii for a in the key buffer
; 5. return
; so u don't have to scroll: key_buffer = $200
; key buffer just shows the current state of the keyboard
; it is not a queue of keys to print
read_keys:
lda $4402 ; check row 2
beq check_key ; if there're any keys, check which
clear_buffer:
stz key_buffer
rts
check_key: ; if there is a key pressed, check if it's a
ror
bmi store_key
rts
store_key:
; in a sec we'll set up ascii table lookup,
; for now let's just load the ascii byte for a
lda #$61
sta key_buffer
rts
draw:
push_coords #0, #0
push_char #$af
jsr draw_char
rts
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
lda 0, x ; load a with character to draw
pop ; and pop it off the stack
jsr get_char_address ; calculate where to put the character in memory
sta (0, x) ; store a at the address pointed to on the stack
rts
get_char_address: ; gets vram address for a character at (x, y),
; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first
;.byte 64
;.byte 0
pha
lda #64
push ; doing this instead until `push_lit` is fixed
sta 0, x
stz 1, x
jsr mult ; multiply 64 with y (n2)
jsr plus ; add result with x (n1)
;jsr push_lit ; push vram address onto the stack
;.byte $00
;.byte $60
lda #$60
push
sta 1, x
stz 0, x
jsr plus ; add vram start address to result
pla
rts
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
jsr get_char_address
irq:
rts
isr: ; interrupt service routine
pha
phx
phy
jsr irq
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr
BIN
View File
Binary file not shown.
+215
View File
@@ -0,0 +1,215 @@
; .setcpu "65C02"
.include "./macro.inc"
.org $8000
n = $01 ; temporary storage for data stack operations
temp = $20 ; scratchpad page
str_ptr = $30
cursor = $300
cursor_x = cursor
cursor_y = cursor + 1
char_buf = $302
char_buf_index = char_buf + 8
reset:
sei
ldx #0; initialize data stack pointer
init:
lda #$31
sta str_ptr
lda #$80
sta str_ptr + 1
jsr clear
lda #0
sta cursor_x
lda #0
sta cursor_y
cli
main:
jsr print
jmp main
newline: ; sets cursor to start of next line
stz cursor_x
lda cursor_y
cmp #28
bne .end
stz cursor_y
rts
.end:
inc cursor_y
rts
text:
.asciiz "hello <3"
; increments the cursor line by line, looping to (0, 0) after (63, 28)
inc_cursor:
lda cursor_x
cmp #63
beq .newline
inc cursor_x
rts
.newline:
lda cursor_y
cmp #28
beq .newscreen
stz cursor_x
inc cursor_y
rts
.newscreen:
stz cursor_y
stz cursor_x
rts
; zeroes out the display, resets cursor to 0,0
clear:
lda #0
ldy #0
.loop:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne .loop
stz cursor
stz cursor + 1
rts
; prints string from cursor position, stopping at end of string or at 256 chars, whichever comes first
; $6000 + (64*Y) + X
; THIS WILL WRITE OUT OF BOUNDS IF THE CURSOR IS OUT OF BOUNDS/STRING IS TOO LONG
; TODO: figure out a simple way of writing arbitrary length strings
; and
print:
jsr cursor_addr
ldy #0
; y_overflow = temp + 5
.loop:
lda (str_ptr), y
beq .end
sta (temp), y
iny
bra .loop
.end:
rts
; calculates real vram address from cursor (x, y)
cursor_addr:
stz temp
stz temp + 1
lda cursor_y
beq .add_x ; if y's zero just add x
.y_mult:
; multiply by 64
clc
asl
rol temp + 1
asl
rol temp + 1
asl
rol temp + 1
asl
rol temp + 1
asl
rol temp + 1
asl
rol temp + 1
sta temp
.add_x:
clc
lda cursor_x
adc temp
sta temp
lda #0
adc temp + 1
sta temp + 1
clc
lda #$60
adc temp + 1
sta temp + 1
rts
; print_text:
; lda text,y
; beq .end
; sta $6000, y
; iny
; bra print_text
; .end:
; ldy #0
; rts
; draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
; lda 0, x ; load a with character to draw
; pop ; and pop it off the stack
; jsr get_char_address ; calculate where to put the character in memory
; sta (0, x) ; store a at the address pointed to on the stack
; rts
; get_char_address: ; gets vram address for a character at (x, y),
; ; (n1: x n2: y -- n: $6000 + x + (64 * y))
; ;jsr push_lit ; push 64 onto stack, low byte first
; ;.byte 64
; ;.byte 0
; pha
; lda #64
; push ; doing this instead until `push_lit` is fixed
; sta 0, x
; stz 1, x
; jsr mult ; multiply 64 with y (n2)
; jsr plus ; add result with x (n1)
; ;jsr push_lit ; push vram address onto the stack
; ;.byte $00
; ;.byte $60
; lda #$60
; push
; sta 1, x
; stz 0, x
; jsr plus ; add vram start address to result
; pla
; rts
; fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
; jsr get_char_address
isr: ; interrupt service routine
pha
phx
phy
; jsr irq
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr
Binary file not shown.
+62
View File
@@ -0,0 +1,62 @@
.macro breakpoint ; $02 isn't a valid instruction, the emulator will see this and halt, dump memory contents
.byte $02
.endm
.macro pop ; drops a data stack cell
inx
inx
.endm
.macro pop2 ; drops 2 data stack cells
inx
inx
inx
inx
.endm
.macro push ; push a data stack cell
dex
dex
.endm
.macro push2 ; push 2 data stack cells
dex
dex
dex
dex
.endm
.macro push_char, char; pushes an ascii character code onto the stack
lda \char
push
sta 0, x ; char low byte
stz 1, x ; char high byte
.endm
.macro push_coords, coord_x, coord_y ; push a set of (x,y) coordinates onto the data stack
lda \coord_x
push
sta 0, x ; low byte
stz 1,x ; high byte is zero
lda \coord_y
push
sta 0,x ; same here
stz 1,x
.endm
.macro to_r ; pop the top of the stack off and save it in the return (hardware) stack: (n -- )
lda 1, x
pha
lda 0, x
pha
pop
.endm
.macro from_r ; pop the top of the return stack off and put it on the data stack: ( -- n)
push
pla
sta 0, x
pla
sta 1, x
.endm
+81
View File
@@ -0,0 +1,81 @@
; --- Data Stack --- ;
; on this channel we love garth wilson: https://wilsonminesco.com/stacks/StackOps.ASM
; data stack is built up of 2-byte cells
; TODO: this is broken, jumping here does nothing to the stack and skips several instructions, could be an emulator problem tho
; push_lit: ; this bad boy lets you inline a literal (low byte first) right after `jsr push_lit` and put it on the stack, once again, on this channel we love garth wilson
; push2
; phx
; tsx
; txa
; tay
; plx
; lda $102, y
; sta 0, x
; clc
; adc #2
; sta $102, y
; lda $103, y
; sta 1, x
; adc #0
; sta $103, y
; fetch:
; lda (0, x)
; pha
; inc 0, x
; bne .1
; inc 1, x
; .1:
; lda (0, x)
; bra put
; push
; put:
; sta 1, x
; pla
; sta 0, x
; rts
plus: ; add: (n1 n2 -- n1+n2)
clc
lda 0, x
adc 2, x
sta 2, x
lda 1, x
adc 3, x
sta 3, x
pop
rts
mult: ; multiply: (n1 n2 -- n1*n2), frankly, i don't know how this works, but TODO: will try to figure it out later
phy
stz n
ldy #0
.1:
lsr 3, x
ror 2, x
bcc .2
clc
lda n
adc 0, x
sta n
tya
adc 1, x
tay
.2:
asl 0, x
rol 1, x
lda 2, x
ora 3, x
bne .1
lda n
sta 2, x
sty 3, x
pop
ply
rts
+88
View File
@@ -0,0 +1,88 @@
; .setcpu "65C02"
.include "./macro.inc"
.org $8000
n = $01 ; temporary storage for data stack operations
reset:
sei
ldx #0; initialize data stack pointer
initdisplay:
lda #0
ldy #0
cleardisplay:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne cleardisplay
cli
main:
jsr draw
jmp main
draw:
push_coords #0, #0
push_char #$af
jsr draw_char
rts
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
lda 0, x ; load a with character to draw
pop ; and pop it off the stack
jsr get_char_address ; calculate where to put the character in memory
sta (0, x) ; store a at the address pointed to on the stack
rts
get_char_address: ; gets vram address for a character at (x, y),
; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first
;.byte 64
;.byte 0
pha
lda #64
push ; doing this instead until `push_lit` is fixed
sta 0, x
stz 1, x
jsr mult ; multiply 64 with y (n2)
jsr plus ; add result with x (n1)
;jsr push_lit ; push vram address onto the stack
;.byte $00
;.byte $60
lda #$60
push
sta 1, x
stz 0, x
jsr plus ; add vram start address to result
pla
rts
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
jsr get_char_address
isr: ; interrupt service routine
pha
phx
phy
; jsr irq
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr
BIN
View File
Binary file not shown.
+173
View File
@@ -0,0 +1,173 @@
; .setcpu "65C02"
.include "./macro.inc"
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates
n = $01 ; temporary storage for data stack operations
key_row = $200 ; used for character lookup when key pressed
key_col = $201
cursor = $202
char_buffer = $300 ; 256 byte character buffer
kb_row = $4400 ; keyboard hardware register
kb_row_cache = $203 ; cache
.org $8000
reset:
sei
ldx #0; initialize data stack pointer
jmp main
initdisplay:
lda #20
ldy #0
cleardisplay:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne cleardisplay
cli
main:
; jsr keyboard
; key_zero:
; stz keyboard_cache, x
; dex
; bpl key_zero
; fim:
; cli
; bra fim
; jsr kitty_keys
lda #9
sta $6000
jmp main
not_keyboard:
ldy #0
.check_row: ; loop through each row
lda kb_row, y
beq .skip_row ; if row has no key pressed, skip checking which key
sta kb_row_cache, y ; if key pressed, cache it
lda kb_row, y
cmp kb_row_cache, y ; has key changed?
beq key_down
.skip_row:
iny
cpy #5
bne .check_row
rts
key_down: ; a is loaded with the row byte
phy
sty key_row ; store character row
ldy #0
.find_col: ; test each row bit, store column if key pressed
lsr ; test bit 7
bcs store_col ; if unset, don't go store character columnb
.skip:
iny
cpy #8
bne .find_col ; loop until we've checked each bit
store_col:
sty key_col
keymap_index:
push
lda key_col
stz 1, x
sta 0, x
push
lda #8
stz 1, x
sta 0, x
push
lda key_row
stz 1, x
sta 0, x
jsr mult
jsr plus
lda 0, x
tay
print: ; we've stored the character position, now let's
lda keymap, y
ldy cursor
sta $6000, y
inc cursor
ply
rts
keymap:
.byte "?outrew?"
.byte "?piygsq?"
.byte "a??khvd?"
.byte "42ljbfz?"
.byte "31?mncx?"
.byte "????? m"
; draw:
; ; push_coords #0, #0
; ; push_char #$00
; ; jsr draw_char
; rts
; draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
; lda 0, x ; load a with character to draw
; pop ; and pop it off the stack
; jsr get_char_address ; calculate where to put the character in memory
; sta (0, x) ; store a at the address pointed to on the stack
; rts
; get_char_address: ; gets vram address for a character at (x, y),
; ; (n1: x n2: y -- n: $6000 + x + (64 * y))
; ;jsr push_lit ; push 64 onto stack, low byte first
; ;.byte 64
; ;.byte 0
; pha
; lda #64
; push ; doing this instead until `push_lit` is fixed
; sta 0, x
; stz 1, x
; jsr mult ; multiply 64 with y (n2)
; jsr plus ; add result with x (n1)
; ;jsr push_lit ; push vram address onto the stack
; ;.byte $00
; ;.byte $60
; lda #$60
; push
; sta 1, x
; stz 0, x
; jsr plus ; add vram start address to result
; pla
; rts
; fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
; jsr get_char_address
isr: ; interrupt service routine
pha
phx
phy
; jsr irq
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr
BIN
View File
Binary file not shown.
+187
View File
@@ -0,0 +1,187 @@
; .setcpu "65C02"
.include "./macro.inc"
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates
n = $01 ; temporary storage for data stack operations
key_row = $200 ; used for character lookup when key pressed
key_col = $201
cursor = $202
char_buffer = $300 ; 256 byte character buffer
kb_row = $4400 ; keyboard hardware register
kb_row_cache = $203 ; cache
.org $8000
reset:
sei
ldx #0; initialize data stack pointer
initdisplay:
lda #0
ldy #0
cleardisplay:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne cleardisplay
cli
print_test:
lda #0
sta key_row
lda #5
sta key_col
push_coords #5, #5
main:
; jsr printtext
; key_zero:
; stz keyboard_cache, x
; dex
; bpl key_zero
; fim:
; cli
; bra fim
jsr print
; jsr print
jmp main
; keyboard: ; reads keyboard registers and stores the column and row of the first key found
; ; TODO: make this routine store up to 8 indices (for 8 key rollover)
; ldy #0
; .check_row: ; loop through each row
; lda kb_row, y
; beq .skip_row ; if row has no key pressed, skip checking which key
; ; jmp key_down
; sta kb_row_cache, y ; if key pressed, cache it
; lda kb_row, y
; cmp kb_row_cache, y ; has key changed?
; beq key_down
; .skip_row:
; iny
; cpy #5
; bne .check_row
; rts
; key_down: ; a is loaded with the row byte
; phy
; sty key_row ; store character row
; ldy #0
; .find_col: ; test each row bit, store column if key pressed
; lsr ; test bit 7
; bcs store_col ; if unset, don't go store character columnb
; .skip:
; iny
; cpy #8
; bne .find_col ; loop until we've checked each bit
; rts
; store_col:
; sty key_col
; jsr print
; rts
print: ; x y -- prints the key indexed with key_col and key_row at position x, y
keymap_index:
push
lda key_col
stz 1, x
sta 0, x
push
lda #8
stz 1, x
sta 0, x
push
lda key_row
stz 1, x
sta 0, x
jsr mult
jsr plus
lda 0, x
tay
lda keymap, y
push
sta 0, x
stz 1, x
jsr draw_char
rts
keymap:
.byte "?outrew?"
.byte "?piygsq?"
.byte "a??khvd?"
.byte "42ljbfz?"
.byte "31?mncx?"
.byte "????? m"
draw:
; push_coords #0, #0
; push_char #$00
; jsr draw_char
rts
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
lda 0, x ; load a with character to draw
pop ; and pop it off the stack
jsr get_char_address ; calculate where to put the character in memory
sta (0, x) ; store a at the address pointed to on the stack
rts
get_char_address: ; gets vram address for a character at (x, y),
; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first
;.byte 64
;.byte 0
pha
lda #64
push ; doing this instead until `push_lit` is fixed
sta 0, x
stz 1, x
jsr mult ; multiply 64 with y (n2)
jsr plus ; add result with x (n1)
;jsr push_lit ; push vram address onto the stack
;.byte $00
;.byte $60
lda #$60
push
sta 1, x
stz 0, x
jsr plus ; add vram start address to result
pla
rts
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
jsr get_char_address
isr: ; interrupt service routine
pha
phx
phy
; jsr keyboard
ply
plx
pla
rti
.include "math.inc"
.org $fffc
.word reset
.word isr
Executable
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
if [ $# -eq 0 ]; then
echo "Provide the name of the rom/asm file to run"
exit 1
fi
set -e
vasm6502_oldstyle roms/$1.asm -dotdir -wdc02 -ldots -Fbin -o roms/$1.rom;
cargo run -- rom "roms/$1.rom";
# hexdump -C ./cpu_dump.bin;
+179
View File
@@ -0,0 +1,179 @@
use std::{
env,
fs::File,
io::{ErrorKind, Read},
process::exit,
};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Default, Debug)]
pub enum ScreenType {
Terminal,
#[default]
Window,
}
pub struct Config {
pub rom: Option<String>,
pub screen: ScreenType,
pub char_rom: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
struct ConfigBuilder {
rom: Option<String>,
char_rom: Option<String>,
screen: ScreenType,
}
impl ConfigBuilder {
fn new() -> Self {
Self {
rom: None,
char_rom: None,
screen: ScreenType::default(),
}
}
fn build(self) -> Config {
let rom = match self.rom {
Some(rom) => Some(rom),
None => {
println!("no rom was provided :(");
exit(1);
}
};
let char_rom = self.char_rom;
let screen = self.screen;
Config {
rom,
screen,
char_rom,
}
}
}
fn help(command: Option<String>) {
let executable: String = env::args().next().unwrap();
if let Some(command) = command {
match &command as &str {
"rom" | "--rom" | "-r" => {
println!("{executable} {command} <path>\n\nload a rom/binary from path");
exit(0);
}
"screen" | "--screen" | "-s" => {
println!("{executable} {command} <path>\n\nload a rom/binary from path");
exit(0);
}
"help" | "--help" | "-h" => {
println!("{executable} {command} <command>\n\nshow help info for a given command");
exit(0);
}
_ => {
println!(
"{command:?} isn't a valid command!\n\nuse `{executable} help` to see all valid commands",
);
exit(1);
}
}
} else {
println!("ʕ·ᴥ·ʔ- {executable} is an emulator for george:");
println!("https://git.augustkline.com/august/george\n");
println!("commands:");
println!(" help, -h, --help <command>: print help info for any command");
println!(" rom, -r, --rom <path>: load a rom/binary from path");
println!(" screen, -s, --screen <type>: use the \"terminal\" or \"window\" display type");
println!("\nconfiguration:");
println!(" george-emu searches for a `george.toml` in the current directory.\n in `george.toml` you can specify a path for the character rom\n using the key `char_rom` and the main rom/binary with the key `rom`");
println!("\ndebugging:");
println!(" you can pipe in a rom for george to evaluate.");
println!(" she'll read it until she reaches a breakpoint (`0x02`) or `stp` instruction,");
println!(" then will dump her memory to stdout <3");
exit(0);
}
}
pub fn get_input() -> Config {
let executable: String = env::args().next().unwrap();
let mut config = ConfigBuilder::new();
let len = env::args().len();
if len == 1 {
config = match File::open("./george.toml") {
Ok(mut file) => {
let mut string = String::new();
file.read_to_string(&mut string).unwrap();
toml::from_str(string.as_str()).unwrap()
}
Err(_) => {
println!("couldn't find a `george.toml` in the current directory!");
println!("\nuse `{executable} --help` to see all config options");
exit(1);
}
};
return config.build();
}
let mut args = env::args().skip(1);
while let Some(arg) = args.next() {
match &arg[..] {
"--help" | "-h" | "help" => help(args.next()),
"--rom" | "-r" | "rom" => {
if let Some(path) = args.next() {
match std::fs::File::open(&path) {
Ok(_) => {
config.rom = Some(path);
}
Err(error) => {
match error.kind() {
ErrorKind::NotFound => {
println!("couldn't find the rom at {path:?}");
}
ErrorKind::PermissionDenied => {
println!(
"didn't have sufficient permissions to open the rom at {path:?}"
);
}
_ => {
println!("something went wrong! try again in a moment? really not sure why you're getting this error");
}
}
exit(1);
}
};
} else {
println!("no rom specified!");
exit(1);
}
}
"--screen" | "-s" | "screen" => {
let kind = args.next();
match kind {
Some(kind) => match &kind as &str {
"terminal" | "Terminal" | "TERMINAL" | "t" | "term" => {}
"window" | "Window" | "WINDOW" | "w" | "win" => {
config.screen = ScreenType::Window
}
_ => {
println!("{kind:?} isn't a valid screen type!\n\nuse \"{executable} --help screen\" to see all valid screen types");
exit(1);
}
},
None => {
println!("no screen type was provided!\n\nuse \"{executable} --help screen\" to see all valid screen types");
exit(1);
}
}
}
_ => {
println!("{arg:?} isn't a valid command!\n\nuse \"{executable} help\" to see all valid commands");
exit(1);
}
}
}
config.build()
}
+76
View File
@@ -0,0 +1,76 @@
use std::io::{self, stdin, IsTerminal};
#[cfg(not(target_arch = "wasm32"))]
mod cli;
#[cfg(not(target_arch = "wasm32"))]
use cli::get_input;
use georgeemu::memory::{Mem, MemHandle};
#[cfg(not(target_arch = "wasm32"))]
use georgeemu::GeorgeEmu;
#[cfg(not(target_arch = "wasm32"))]
use minifb::{Scale, ScaleMode, Window, WindowOptions};
use std::{fs::File, io::Read};
#[cfg(not(target_arch = "wasm32"))]
fn main() -> io::Result<()> {
use std::io::{stdout, Write};
use georgeemu::cpu::Cpu;
let mut input = stdin();
if !input.is_terminal() {
let mut bytes = [0u8; 0x8000];
input.read_exact(&mut bytes).unwrap();
let memory = make_memory(&bytes);
let mut cpu = Cpu::new(memory);
let final_output = cpu.run_to_end();
let _ = stdout().write_all(&final_output);
Ok(())
} else {
let window = Window::new(
"ʕ·ᴥ·ʔ-☆",
512,
380,
WindowOptions {
resize: true,
borderless: true,
title: true,
transparency: false,
scale: Scale::FitScreen,
scale_mode: ScaleMode::AspectRatioStretch,
topmost: false,
none: true,
},
)
.unwrap();
let config = get_input();
let rom = match config.rom {
Some(path) => {
let mut file = File::open(path).unwrap();
let mut bin = vec![0; 0x8000];
file.read_exact(&mut bin).unwrap();
bin.try_into().unwrap()
}
None => *include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/roms/george.rom")),
};
let mut emu = GeorgeEmu::builder().rom(rom).window(window).build();
emu.run();
Ok(())
}
}
#[cfg(not(target_arch = "wasm32"))]
fn make_memory(bytes: &[u8]) -> MemHandle {
let mut memory = Mem::new();
memory.load_bytes(bytes);
MemHandle::new(memory)
}
#[cfg(target_arch = "wasm32")]
fn main() {}
+296
View File
@@ -0,0 +1,296 @@
use crate::instructions::get_instruction;
use crate::memory::{MemHandle, MemoryReader, MemoryWriter};
use std::fmt::Display;
use std::sync::mpsc::{channel, Receiver, Sender};
#[cfg(not(target_arch = "wasm32"))]
use std::thread::sleep;
#[cfg(not(target_arch = "wasm32"))]
use std::time::Duration;
#[derive(Clone, Copy)]
pub enum StatusFlag {
Negative = 0b1000_0000,
Overflow = 0b0100_0000,
Brk = 0b0011_0000,
//BrkIrq = 0b0010_0000,
Decimal = 0b0000_1000,
IrqDisable = 0b0000_0100,
Zero = 0b000_0010,
Carry = 0b0000_0001,
}
#[derive(Clone, Debug)]
pub struct CpuController(Sender<CpuControl>);
#[derive(Clone, Copy)]
pub enum CpuControl {
Irq,
Nmi,
Toggle,
Data,
Cycle,
}
impl CpuController {
pub fn new(sender: Sender<CpuControl>) -> Self {
Self(sender)
}
pub fn irq(&self) {
let _ = self.0.send(CpuControl::Irq);
}
pub fn nmi(&self) {
let _ = self.0.send(CpuControl::Nmi);
}
pub fn toggle(&self) {
let _ = self.0.send(CpuControl::Toggle);
}
pub fn cycle(&self) {
let _ = self.0.send(CpuControl::Cycle);
}
pub fn data(&self) {
let _ = self.0.send(CpuControl::Data);
}
}
#[derive(Debug)]
pub struct CpuReceiver(Receiver<CpuControl>);
impl CpuReceiver {
pub fn new(receiver: Receiver<CpuControl>) -> Self {
Self(receiver)
}
}
#[derive(Debug)]
pub struct Cpu {
pub a: u8, // Accumulator Register
pub x: u8, // X Register
pub y: u8, // Y Register
pub pc: u16, // Program Counter
pub s: u8, // Stack Pointer
pub p: u8, // Status Register
pub irq: bool,
pub nmi: bool,
pub memory: MemHandle,
pub pending_cycles: usize,
pub debug: bool,
receiver: Option<CpuReceiver>,
stopped: bool,
cycle: bool,
}
impl Display for Cpu {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{},{}", self.a, self.x)
}
}
impl MemoryReader for Cpu {
fn read(&self, address: u16) -> u8 {
self.memory.read(address)
}
}
impl MemoryWriter for Cpu {
fn write(&self, address: u16, data: u8) {
self.memory.write(address, data);
}
}
impl Cpu {
pub fn new(memory: MemHandle) -> Self {
// reset the cpu on initialization so we don't
// scream and cry for two days trying to figure
// out why george isn't working <3
let low_byte = memory.read(0xFFFC);
let high_byte = memory.read(0xFFFD);
let pc = (high_byte as u16) << 8 | (low_byte as u16);
Cpu {
a: 0x00,
x: 0x00,
y: 0x00,
pc,
s: 0xFF,
p: 0b0010_0100,
irq: false,
nmi: false,
receiver: None,
memory,
debug: false,
stopped: false,
pending_cycles: 0,
cycle: false,
}
}
pub fn with_receiver(mut self, receiver: CpuReceiver) -> Self {
self.receiver = Some(receiver);
self
}
pub fn new_with_control(memory: MemHandle) -> (Self, CpuController) {
let (tx, rx) = channel::<CpuControl>();
let controller = CpuController(tx);
let receiver = CpuReceiver(rx);
let cpu = Cpu::new(memory).with_receiver(receiver);
(cpu, controller)
}
pub fn reset(&mut self) {
let reset_vector_pointer = self.read_word(0xFFFC);
self.pc = reset_vector_pointer;
self.a = 0;
self.pending_cycles = 0;
}
pub fn read_word(&self, address: u16) -> u16 {
let low_byte = self.read(address);
let high_byte = self.read(address.wrapping_add(0x1));
(high_byte as u16) << 8 | (low_byte as u16)
}
fn stack_addr(&self) -> u16 {
// Dunno if this is necessary, i just don't like adding the 0x0100 every time
0x0100 + self.s as u16
}
pub fn push_stack(&mut self, data: u8) {
self.s = self.s.wrapping_sub(0x1);
self.write(self.stack_addr(), data);
}
pub fn push_stack_word(&mut self, address: u16) {
self.s = self.s.wrapping_sub(0x1);
self.write(self.stack_addr(), address.to_le_bytes()[1]); // Upper byte first
self.s = self.s.wrapping_sub(0x1);
self.write(self.stack_addr(), address.to_le_bytes()[0]); // Lower byte second
}
pub fn pop_stack(&mut self) -> u8 {
let byte = self.read(self.stack_addr());
self.s = self.s.wrapping_add(0x1);
byte
}
pub fn pop_stack_word(&mut self) -> u16 {
let low_byte = self.pop_stack();
let high_byte = self.pop_stack();
((high_byte as u16) << 8) + low_byte as u16
}
pub fn set_flag(&mut self, flag: StatusFlag, value: bool) {
self.p &= !(flag as u8);
if value {
self.p |= flag as u8
}
}
pub fn get_flag(&self, flag: StatusFlag) -> bool {
(self.p & flag as u8) > 0
}
pub fn is_negative(&self, value: u8) -> bool {
value & 0b1000_0000 == 0b1000_0000
}
pub fn wait_for_interrupt(&self) {
unimplemented!()
}
pub fn interrupt(&mut self) {
self.irq = false;
self.push_stack_word(self.pc);
self.push_stack(self.p);
self.set_flag(StatusFlag::IrqDisable, true);
self.pc = self.read_word(0xFFFE);
}
pub fn toggle_stopped(&mut self) {
self.stopped = !self.stopped;
}
fn receive_control(&mut self) {
match &self.receiver {
Some(receiver) => {
let control = match receiver.0.try_recv() {
Ok(control) => control,
Err(_error) => return, // most of the time we won't have any impending control
// messages, just return if that's the case
};
match control {
CpuControl::Nmi => self.nmi = true,
CpuControl::Irq => self.irq = true,
CpuControl::Toggle => {
self.stopped = !self.stopped;
}
CpuControl::Cycle => self.cycle = true,
CpuControl::Data => {
// self
// .state_tx
// .send(CpuState {
// a: self.a.clone(), // Accumulator Register
// x: self.x.clone(), // X Register
// y: self.y.clone(), // Y Register
// pc: self.pc.clone(), // Program Counter
// s: self.s.clone(), // Stack Pointer
// p: self.p.clone(), // Status Register
// irq: self.irq.clone(),
// nmi: self.nmi.clone(),
// })
// .unwrap(),
}
}
}
None => {}
}
}
pub fn cycle(&mut self) {
self.receive_control();
if self.stopped & !self.cycle {
self.set_flag(StatusFlag::IrqDisable, true);
return;
}
self.cycle = false;
// can't sleep in wasm
#[cfg(not(target_arch = "wasm32"))]
while self.pending_cycles != 0 {
// roughly cycle-accurate timing
sleep(Duration::from_nanos(500));
self.pending_cycles -= 1;
}
if !self.get_flag(StatusFlag::IrqDisable) && self.irq {
self.interrupt();
}
let opcode = self.read(self.pc);
let instruction = get_instruction(opcode);
instruction.call(self);
}
pub fn stop(&mut self) {
self.stopped = true;
}
pub fn breakpoint(&mut self) {
if self.debug {
self.stop();
}
}
fn quick_cycle(&mut self) {
if !self.get_flag(StatusFlag::IrqDisable) && self.irq {
self.interrupt();
}
let opcode = self.read(self.pc);
let instruction = get_instruction(opcode);
instruction.call(self);
}
pub fn run_to_end(&mut self) -> [u8; 0x10000] {
self.quick_cycle();
while self.pending_cycles != 0 {
let opcode = self.read(self.pc);
let instruction = get_instruction(opcode);
match instruction.name {
"stp" | "breakpoint" => return self.memory.dump(),
_ => self.quick_cycle(),
}
}
unreachable!()
}
}
-9
View File
@@ -1,9 +0,0 @@
.setcpu "65C02"
.segment "CODE"
main:
LDA #$25
LDY #$FF
STY $7000
STY $7001
STY $7002
JMP main
-10
View File
@@ -1,10 +0,0 @@
MEMORY {
RAM: start = $0000, size = $4000, type = rw, fill = true;
CTRL: start = $4000, size = $2000, type = rw, fill = true;
VRAM: start = $6000, size = $8000, type = rw, fill = true;
ROM: start = $E000, size = $2000, type = ro, fill = true;
}
SEGMENTS {
CODE: load = "RAM", type = rw;
}
+2805
View File
File diff suppressed because it is too large Load Diff
+256
View File
@@ -0,0 +1,256 @@
use minifb::InputCallback;
use minifb::Key as MKey;
#[cfg(not(target_arch = "wasm32"))]
use termion::event::Key;
use crate::memory::{MemHandle, MemoryWriter};
#[derive(Debug, Clone)]
pub struct Keyboard {
memory: MemHandle,
}
impl Keyboard {
pub fn new(memory: MemHandle) -> Self {
Self { memory }
}
#[cfg(not(target_arch = "wasm32"))]
pub fn clear_keys(&self) {
self.memory.write(0x4400, 0x00);
self.memory.write(0x4401, 0x00);
self.memory.write(0x4402, 0x00);
self.memory.write(0x4403, 0x00);
self.memory.write(0x4404, 0x00);
self.memory.write(0x4405, 0x00);
}
#[cfg(not(target_arch = "wasm32"))]
pub fn read_keys(&self, key: Key) {
let mut row0 = 0;
let mut row1 = 0;
let mut row2 = 0;
let mut row3 = 0;
let mut row4 = 0;
let mut row5 = 0;
// these are just so the match statements are easier to read lol
macro_rules! set_shift {
($char:expr) => {
match $char {
'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M'
| 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y'
| 'Z' | '!' | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '(' | ')' | '~'
| '_' | '+' | '|' | '}' | '{' | '"' | ':' | '?' | '>' | '<' => {
row2 ^= 0b1000_0000
}
_ => {}
}
};
}
macro_rules! set_plain {
($char:expr) => {
match $char {
'w' | 'W' => row0 ^= 0b0100_0000,
'e' | 'E' => row0 ^= 0b0010_0000,
'r' | 'R' => row0 ^= 0b0001_0000,
't' | 'T' => row0 ^= 0b0000_1000,
'u' | 'U' => row0 ^= 0b0000_0100,
'o' | 'O' => row0 ^= 0b0000_0010,
'q' | 'Q' => row1 ^= 0b0100_0000,
's' | 'S' => row1 ^= 0b0010_0000,
'g' | 'G' => row1 ^= 0b0001_0000,
'y' | 'Y' => row1 ^= 0b0000_1000,
'i' | 'I' => row1 ^= 0b0000_0100,
'p' | 'P' => row1 ^= 0b0000_0010,
'd' | 'D' => row2 ^= 0b0100_0000,
'v' | 'V' => row2 ^= 0b0010_0000,
'h' | 'H' => row2 ^= 0b0001_0000,
'k' | 'K' => row2 ^= 0b0000_1000,
'\\' | '|' => row2 ^= 0b0000_0100,
'/' | '?' => row2 ^= 0b0000_0010,
'a' | 'A' => row2 ^= 0b0000_0001,
'z' | 'Z' => row3 ^= 0b0100_0000,
'f' | 'F' => row3 ^= 0b0010_0000,
'b' | 'B' => row3 ^= 0b0001_0000,
'j' | 'J' => row3 ^= 0b0000_1000,
'l' | 'L' => row3 ^= 0b0000_0100,
'2' | '@' => row3 ^= 0b0000_0010,
'4' | '$' => row3 ^= 0b0000_0001,
'x' | 'X' => row4 ^= 0b0100_0000,
'c' | 'C' => row4 ^= 0b0010_0000,
'n' | 'N' => row4 ^= 0b0001_0000,
'm' | 'M' => row4 ^= 0b0000_1000,
',' | '<' => row4 ^= 0b0000_0100,
'1' | '!' => row4 ^= 0b0000_0010,
'3' | '#' => row4 ^= 0b0000_0001,
' ' => row5 ^= 0b0100_0000,
_ => {}
}
};
}
match key {
Key::Esc => row0 ^= 0b1000_0000,
Key::Backspace => row0 ^= 0b0000_0001,
Key::Char('\t') => row1 ^= 0b1000_0000,
Key::Char('\n') | Key::Char('\r') => row1 ^= 0b0000_0001,
Key::Char(char) => {
set_shift!(char);
set_plain!(char);
}
Key::ShiftLeft | Key::ShiftUp | Key::ShiftDown | Key::ShiftRight => row2 ^= 0b1000_0000,
Key::Ctrl(char) => {
set_shift!(char);
set_plain!(char);
row3 ^= 0b1000_0000;
}
Key::CtrlUp | Key::CtrlLeft | Key::CtrlDown | Key::CtrlRight => {
row3 ^= 0b1000_0000;
}
Key::Alt(char) => {
set_plain!(char);
set_shift!(char);
row4 ^= 0b1000_0000;
}
Key::AltUp | Key::AltLeft | Key::AltDown | Key::AltRight => {
row4 ^= 0b1000_0000;
}
_ => {}
}
self.memory.write(0x4400, row0);
self.memory.write(0x4401, row1);
self.memory.write(0x4402, row2);
self.memory.write(0x4403, row3);
self.memory.write(0x4404, row4);
self.memory.write(0x4405, row5);
// keeping this list around to make future changes easier
// match key.code {
// KeyCode::Esc => row0 ^= 0b1000_0000,
// KeyCode::Char('w') => row0 ^= 0b0100_0000,
// KeyCode::Char('e') => row0 ^= 0b0010_0000,
// KeyCode::Char('r') => row0 ^= 0b0001_0000,
// KeyCode::Char('t') => row0 ^= 0b0000_1000,
// KeyCode::Char('u') => row0 ^= 0b0000_0100,
// KeyCode::Char('o') => row0 ^= 0b0000_0010,
// KeyCode::Backspace => row0 ^= 0b0000_0001,
// KeyCode::Tab => row1 ^= 0b1000_0000,
// KeyCode::Char('q') => row1 ^= 0b0100_0000,
// KeyCode::Char('s') => row1 ^= 0b0010_0000,
// KeyCode::Char('g') => row1 ^= 0b0001_0000,
// KeyCode::Char('y') => row1 ^= 0b0000_1000,
// KeyCode::Char('i') => row1 ^= 0b0000_0100,
// KeyCode::Char('p') => row1 ^= 0b0000_0010,
// KeyCode::Enter => row1 ^= 0b0000_0001,
// KeyCode::Char('d') => row2 ^= 0b0100_0000,
// KeyCode::Char('v') => row2 ^= 0b0010_0000,
// KeyCode::Char('h') => row2 ^= 0b0001_0000,
// KeyCode::Char('k') => row2 ^= 0b0000_1000,
// KeyCode::Char('\'') => row2 ^= 0b0000_0100,
// KeyCode::Char('/') => row2 ^= 0b0000_0010,
// KeyCode::Char('a') => row2 ^= 0b0000_0001,
// KeyCode::Char('z') => row3 ^= 0b0100_0000,
// KeyCode::Char('f') => row3 ^= 0b0010_0000,
// KeyCode::Char('b') => row3 ^= 0b0001_0000,
// KeyCode::Char('j') => row3 ^= 0b0000_1000,
// KeyCode::Char('l') => row3 ^= 0b0000_0100,
// KeyCode::Char('2') => row3 ^= 0b0000_0010,
// KeyCode::Char('4') => row3 ^= 0b0000_0001,
// KeyCode::Char('x') => row4 ^= 0b0100_0000,
// KeyCode::Char('c') => row4 ^= 0b0010_0000,
// KeyCode::Char('n') => row4 ^= 0b0001_0000,
// KeyCode::Char('m') => row4 ^= 0b0000_1000,
// KeyCode::Char(',') => row4 ^= 0b0000_0100,
// KeyCode::Char('1') => row4 ^= 0b0000_0010,
// KeyCode::Char('3') => row4 ^= 0b0000_0001,
// KeyCode::Char(' ') => row5 ^= 0b0100_0000,
// _ => {
// row0 = 0;
// row1 = 0;
// row2 = 0;
// row3 = 0;
// row4 = 0;
// row5 = 0;
// }
// };
}
}
impl MemoryWriter for Keyboard {
fn write(&self, address: u16, data: u8) {
if data != 0x00 {
// println!("wrote {:02x} to address {:04x}", data, address);
}
self.memory.write(address, data);
}
}
impl InputCallback for Keyboard {
fn add_char(&mut self, _uni_char: u32) {}
fn set_key_state(&mut self, key: MKey, _state: bool) {
let mut row0 = 0;
let mut row1 = 0;
let mut row2 = 0;
let mut row3 = 0;
let mut row4 = 0;
let mut row5 = 0;
match key {
MKey::Escape => row0 ^= 0b1000_0000,
MKey::W => row0 ^= 0b0100_0000,
MKey::E => row0 ^= 0b0010_0000,
MKey::R => row0 ^= 0b0001_0000,
MKey::T => row0 ^= 0b0000_1000,
MKey::U => row0 ^= 0b0000_0100,
MKey::O => row0 ^= 0b0000_0010,
MKey::Backspace => row0 ^= 0b0000_0001,
MKey::Tab => row1 ^= 0b1000_0000,
MKey::Q => row1 ^= 0b0100_0000,
MKey::S => row1 ^= 0b0010_0000,
MKey::G => row1 ^= 0b0001_0000,
MKey::Y => row1 ^= 0b0000_1000,
MKey::I => row1 ^= 0b0000_0100,
MKey::P => row1 ^= 0b0000_0010,
MKey::Enter => row1 ^= 0b0000_0001,
MKey::LeftShift | MKey::RightShift => row2 ^= 0b1000_0000,
MKey::D => row2 ^= 0b0100_0000,
MKey::V => row2 ^= 0b0010_0000,
MKey::H => row2 ^= 0b0001_0000,
MKey::K => row2 ^= 0b0000_1000,
MKey::Apostrophe => row2 ^= 0b0000_0100,
MKey::Slash => row2 ^= 0b0000_0010,
MKey::A => row2 ^= 0b0000_0001,
MKey::LeftCtrl | MKey::RightCtrl => row3 ^= 0b1000_0000,
MKey::Z => row3 ^= 0b0100_0000,
MKey::F => row3 ^= 0b0010_0000,
MKey::B => row3 ^= 0b0001_0000,
MKey::J => row3 ^= 0b0000_1000,
MKey::L => row3 ^= 0b0000_0100,
MKey::Key2 => row3 ^= 0b0000_0010,
MKey::Key4 => row3 ^= 0b0000_0001,
MKey::LeftAlt | MKey::RightAlt => row4 ^= 0b1000_0000,
MKey::X => row4 ^= 0b0100_0000,
MKey::C => row4 ^= 0b0010_0000,
MKey::N => row4 ^= 0b0001_0000,
MKey::M => row4 ^= 0b0000_1000,
MKey::Comma => row4 ^= 0b0000_0100,
MKey::Key1 => row4 ^= 0b0000_0010,
MKey::Key3 => row4 ^= 0b0000_0001,
MKey::LeftSuper => row5 ^= 0b1000_0000,
MKey::Space => row5 ^= 0b0100_0000,
MKey::RightSuper => row5 ^= 0b0010_0000,
_ => {}
};
self.memory.write(0x4400, row0);
self.memory.write(0x4401, row1);
self.memory.write(0x4402, row2);
self.memory.write(0x4403, row3);
self.memory.write(0x4404, row4);
self.memory.write(0x4405, row5);
}
}
+204
View File
@@ -0,0 +1,204 @@
pub mod cpu;
mod instructions;
mod keyboard;
pub mod memory;
mod platform;
pub mod video;
use crate::cpu::Cpu;
use crate::keyboard::Keyboard;
use crate::memory::Mem;
use crate::video::Renderer;
use cpu::CpuController;
use memory::MemHandle;
use minifb::Window;
#[cfg(not(target_arch = "wasm32"))]
use platform::native as imp;
#[cfg(target_arch = "wasm32")]
use platform::wasm as imp;
pub struct GeorgeEmu(imp::GeorgeEmu);
impl GeorgeEmu {
pub fn builder() -> GeorgeEmuBuilder<NoRom, NoWindow> {
GeorgeEmuBuilder::new()
}
pub fn draw(&mut self) {
self.0.draw()
}
pub fn cycle(&mut self) {
self.0.cycle()
}
#[cfg(not(target_arch = "wasm32"))]
pub fn run(&mut self) {
self.0.run()
}
}
pub struct SomeRom();
pub struct NoRom();
pub struct SomeWindow(Window);
pub struct NoWindow();
pub struct GeorgeEmuBuilder<Rom, Window> {
rom: Rom,
cpu: Option<Cpu>,
keyboard: Option<Keyboard>,
renderer: Option<Renderer>,
cpu_controller: Option<CpuController>,
memory_handle: Option<MemHandle>,
window: Window,
}
impl GeorgeEmuBuilder<NoRom, NoWindow> {
fn new() -> Self {
GeorgeEmuBuilder {
rom: NoRom(),
cpu: None,
keyboard: None,
renderer: None,
cpu_controller: None,
memory_handle: None,
window: NoWindow(),
}
}
pub fn rom(self, rom: [u8; 0x8000]) -> GeorgeEmuBuilder<SomeRom, NoWindow> {
let mut memory = Mem::new();
memory.load_bytes(&rom);
let memory_handle = MemHandle::new(memory);
let (cpu, cpu_controller) = Cpu::new_with_control(memory_handle.clone());
let Self {
renderer,
keyboard,
window,
..
} = self;
GeorgeEmuBuilder {
cpu: Some(cpu),
rom: SomeRom(),
keyboard,
renderer,
cpu_controller: Some(cpu_controller),
memory_handle: Some(memory_handle),
window,
}
}
}
impl GeorgeEmuBuilder<SomeRom, NoWindow> {
/// Adds a minifb window to the emulator
pub fn window(self, mut window: Window) -> GeorgeEmuBuilder<SomeRom, SomeWindow> {
let Self {
rom,
cpu,
keyboard,
ref cpu_controller,
ref memory_handle,
..
} = self;
let renderer = Renderer::new(
self.cpu_controller.clone().unwrap(),
self.memory_handle.clone().unwrap(),
);
window.set_input_callback(Box::new(Keyboard::new(
self.memory_handle.as_ref().unwrap().clone(),
)));
GeorgeEmuBuilder {
cpu,
rom,
keyboard,
renderer: Some(renderer),
cpu_controller: cpu_controller.clone(),
memory_handle: memory_handle.clone(),
window: SomeWindow(window),
}
}
/// Enables debug printing and breakpoint triggering
pub fn debug(mut self) -> Self {
self.cpu.as_mut().unwrap().debug = true;
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn build(self) -> GeorgeEmu {
let keyboard = Keyboard::new(self.memory_handle.clone().unwrap());
GeorgeEmu(imp::GeorgeEmu::new(
self.cpu.unwrap(),
self.renderer.unwrap(),
Some(keyboard),
None,
))
}
}
impl GeorgeEmuBuilder<NoRom, SomeWindow> {
pub fn rom(self, rom: [u8; 0x8000]) -> GeorgeEmuBuilder<SomeRom, SomeWindow> {
let mut memory = Mem::new();
memory.load_bytes(&rom);
let memory_handle = MemHandle::new(memory);
let (cpu, cpu_controller) = Cpu::new_with_control(memory_handle.clone());
let Self {
renderer,
keyboard,
window,
..
} = self;
GeorgeEmuBuilder {
cpu: Some(cpu),
rom: SomeRom(),
keyboard,
renderer,
cpu_controller: Some(cpu_controller),
memory_handle: Some(memory_handle),
window,
}
}
/// Enables debug printing and breakpoint triggering
pub fn debug(mut self) -> Self {
self.cpu.as_mut().unwrap().debug = true;
self
}
}
impl GeorgeEmuBuilder<SomeRom, SomeWindow> {
/// Enables debug printing and breakpoint triggering
pub fn debug(mut self) -> Self {
self.cpu.as_mut().unwrap().debug = true;
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn build(self) -> GeorgeEmu {
let keyboard = Keyboard::new(self.memory_handle.clone().unwrap());
GeorgeEmu(imp::GeorgeEmu::new(
self.cpu.unwrap(),
self.renderer.unwrap(),
None,
Some(self.window.0),
))
}
#[cfg(target_arch = "wasm32")]
pub fn build(self) -> GeorgeEmu {
GeorgeEmu(imp::GeorgeEmu::new(
self.cpu.unwrap(),
self.renderer.unwrap(),
self.window.0,
))
}
}
-3994
View File
File diff suppressed because it is too large Load Diff
+122
View File
@@ -0,0 +1,122 @@
use anyhow::{bail, Result};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::{fs::File, io::Read};
#[derive(Clone, Debug)]
pub struct MemHandle(Arc<Mutex<Mem>>);
impl MemHandle {
pub fn new(memory: Mem) -> Self {
Self(Arc::new(Mutex::new(memory)))
}
pub fn read(&self, address: u16) -> u8 {
let memory = self.0.try_lock().unwrap();
memory.read(address)
}
pub fn write(&self, address: u16, data: u8) {
let mut memory = self.0.try_lock().unwrap();
memory.write(address, data);
}
pub fn dump(&self) -> [u8; 0x10000] {
let memory = self.0.lock().unwrap();
memory.dump()
}
pub fn poke(&self, address: u16) {
let memory = self.0.lock().unwrap();
memory.poke(address)
}
}
pub trait MemoryReader {
fn read(&self, address: u16) -> u8;
}
pub trait MemoryWriter {
fn write(&self, address: u16, data: u8);
}
#[derive(Debug, Clone, Copy)]
// This always feels wrong instead of 0xFFFF,
// but remember it's the number of elements,
// not the maximum index. 0x0000 to 0xFFFF addresses
// 0x10000 elements
pub struct Mem([u8; 0x10000]);
impl Default for Mem {
fn default() -> Self {
let bytes = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/roms/george.rom"));
let padding = [0; 0x8000];
let mem: [u8; 0x10000] = {
let mut rom: [u8; 0x10000] = [0; 0x10000];
let (one, two) = rom.split_at_mut(padding.len());
one.copy_from_slice(&padding);
two.copy_from_slice(bytes);
rom
};
Self(mem)
}
}
impl Mem {
pub fn new() -> Self {
Self([0; 0x10000])
}
pub fn dump(&self) -> [u8; 0x10000] {
self.0
}
pub fn read(&self, address: u16) -> u8 {
self.0[address as usize]
}
pub fn write(&mut self, address: u16, data: u8) {
self.0[address as usize] = data;
}
pub fn load_rom<P>(&mut self, rom: P) -> Result<()>
where
P: AsRef<Path>,
{
let rom = match File::open(rom) {
Ok(rom) => rom,
Err(_) => bail!("rom could not be opened!"),
};
let bytes = rom.bytes();
for (address, byte) in bytes.enumerate() {
// println!("{address}");
match byte {
Ok(value) => self.write(address as u16 + 0x8000, value),
Err(_) => {
bail!("Loading rom: couldn't write byte {:#04x}", address);
}
}
}
Ok(())
}
pub fn poke(&self, address: u16) {
println!("{:02x}", self.read(address));
}
pub fn load_bytes(&mut self, bytes: &[u8]) {
for (address, byte) in bytes.iter().enumerate() {
self.write(address as u16 + 0x8000, *byte)
}
}
pub fn read_from_bin(&mut self, f: File) -> Result<()> {
let bytes = f.bytes();
for (address, byte) in bytes.enumerate() {
match byte {
Ok(value) => self.write(address as u16 + 0x8000, value),
Err(_) => {
bail!("couldn't write byte {:#04x}", address);
}
}
}
Ok(())
}
}
+894
View File
@@ -0,0 +1,894 @@
vec![
Instruction::Valid(ValidInstruction {
opcode: Opcode::BRK(AddressingMode::Implied),
cycles: 7,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ORA(AddressingMode::ZeroPageIndexedIndirect),
cycles: 6,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x02 }),
Instruction::Invalid(InvalidInstruction { opcode: 0x03 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::TSB(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ORA(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ASL(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::RMB0(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::PHP(AddressingMode::Stack),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ORA(AddressingMode::Immediate),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ASL(AddressingMode::Accumulator),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x0b }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::TSB(AddressingMode::AbsoluteA),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ORA(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ASL(AddressingMode::AbsoluteA),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBR0(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BPL(AddressingMode::ProgramCounterRelative),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ORA(AddressingMode::AbsoluteIndexedWithY),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ORA(AddressingMode::ZeroPageIndirect),
cycles: 5,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x13 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::TRB(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ORA(AddressingMode::ZeroPageIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ASL(AddressingMode::ZeroPageIndexedWithX),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::RMB1(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CLC(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ORA(AddressingMode::AbsoluteIndexedWithY),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::INC(AddressingMode::Accumulator),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x1b }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::TRB(AddressingMode::AbsoluteA),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ORA(AddressingMode::AbsoluteIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ASL(AddressingMode::AbsoluteIndexedWithX),
cycles: 7,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBR1(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::JSR(AddressingMode::AbsoluteA),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::AND(AddressingMode::ZeroPageIndexedIndirect),
cycles: 6,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x22 }),
Instruction::Invalid(InvalidInstruction { opcode: 0x23 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BIT(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::AND(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ROL(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::RMB2(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::PLP(AddressingMode::Stack),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::AND(AddressingMode::Immediate),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ROL(AddressingMode::Accumulator),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x2b }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BIT(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::AND(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ROL(AddressingMode::AbsoluteA),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBR2(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BMI(AddressingMode::ProgramCounterRelative),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::AND(AddressingMode::ZeroPageIndirectIndexedWithY),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::AND(AddressingMode::ZeroPageIndirect),
cycles: 5,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x33 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BIT(AddressingMode::ZeroPageIndexedWithX),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::AND(AddressingMode::ZeroPageIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ROL(AddressingMode::ZeroPageIndexedWithX),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::RMB3(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SEC(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::AND(AddressingMode::AbsoluteIndexedWithY),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::DEC(AddressingMode::Accumulator),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x3b }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BIT(AddressingMode::AbsoluteIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::AND(AddressingMode::AbsoluteIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ROL(AddressingMode::AbsoluteIndexedWithX),
cycles: 7,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBR3(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::RTI(AddressingMode::Implied),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::EOR(AddressingMode::ZeroPageIndexedIndirect),
cycles: 6,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x42 }),
Instruction::Invalid(InvalidInstruction { opcode: 0x43 }),
Instruction::Invalid(InvalidInstruction { opcode: 0x44 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::EOR(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LSR(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::RMB4(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::PHA(AddressingMode::Stack),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::EOR(AddressingMode::Immediate),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LSR(AddressingMode::Accumulator),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x4b }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::JMP(AddressingMode::AbsoluteA),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::EOR(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LSR(AddressingMode::AbsoluteA),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBR4(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BVC(AddressingMode::ProgramCounterRelative),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::EOR(AddressingMode::ZeroPageIndirectIndexedWithY),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::EOR(AddressingMode::ZeroPageIndirect),
cycles: 5,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x53 }),
Instruction::Invalid(InvalidInstruction { opcode: 0x54 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::EOR(AddressingMode::ZeroPageIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LSR(AddressingMode::ZeroPageIndexedWithX),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::RMB5(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CLI(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::EOR(AddressingMode::AbsoluteIndexedWithY),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::PHY(AddressingMode::Stack),
cycles: 3,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x5b }),
Instruction::Invalid(InvalidInstruction { opcode: 0x5c }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::EOR(AddressingMode::AbsoluteIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LSR(AddressingMode::AbsoluteIndexedWithX),
cycles: 7,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBR5(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::RTS(AddressingMode::Stack),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ADC(AddressingMode::ZeroPageIndexedIndirect),
cycles: 6,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x62 }),
Instruction::Invalid(InvalidInstruction { opcode: 0x63 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STZ(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ADC(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ROR(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::RMB6(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::PLA(AddressingMode::Stack),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ADC(AddressingMode::Immediate),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ROR(AddressingMode::Accumulator),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x6b }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::JMP(AddressingMode::AbsoluteIndirect),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ADC(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ROR(AddressingMode::AbsoluteA),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBR6(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BVS(AddressingMode::ProgramCounterRelative),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ADC(AddressingMode::ZeroPageIndirectIndexedWithY),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ADC(AddressingMode::ZeroPageIndirect),
cycles: 5,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x73 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STZ(AddressingMode::ZeroPageIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ADC(AddressingMode::ZeroPageIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ROR(AddressingMode::ZeroPageIndexedWithX),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::RMB7(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SEI(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ADC(AddressingMode::AbsoluteIndexedWithY),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::PLY(AddressingMode::Stack),
cycles: 4,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x7b }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::JMP(AddressingMode::AbsoluteIndexedIndirect),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ADC(AddressingMode::AbsoluteIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::ROR(AddressingMode::AbsoluteIndexedWithX),
cycles: 7,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBR7(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BRA(AddressingMode::ProgramCounterRelative),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STA(AddressingMode::ZeroPageIndexedIndirect),
cycles: 6,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x82 }),
Instruction::Invalid(InvalidInstruction { opcode: 0x83 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STY(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STA(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STX(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SMB0(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::DEY(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BIT(AddressingMode::Immediate),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::TXA(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x8b }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STY(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STA(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STX(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBS0(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BCC(AddressingMode::ProgramCounterRelative),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STA(AddressingMode::ZeroPageIndirectIndexedWithY),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STA(AddressingMode::ZeroPageIndirect),
cycles: 5,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x93 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STY(AddressingMode::ZeroPageIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STA(AddressingMode::ZeroPageIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STX(AddressingMode::ZeroPageIndexedWithY),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SMB1(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::TYA(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STA(AddressingMode::AbsoluteIndexedWithY),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::TXS(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0x9b }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STZ(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STA(AddressingMode::AbsoluteIndexedWithX),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STZ(AddressingMode::AbsoluteIndexedWithX),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBS1(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDY(AddressingMode::Immediate),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDA(AddressingMode::ZeroPageIndexedIndirect),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDX(AddressingMode::Immediate),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0xa3 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDY(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDA(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDX(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SMB2(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::TAY(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDA(AddressingMode::Immediate),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::TAX(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0xab }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDY(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDA(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDX(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBS2(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BCS(AddressingMode::ProgramCounterRelative),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDA(AddressingMode::ZeroPageIndirectIndexedWithY),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDA(AddressingMode::ZeroPageIndirect),
cycles: 5, // Unsure, see https://cx16.dk/65c02/reference.html#LDA
}),
Instruction::Invalid(InvalidInstruction { opcode: 0xb3 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDY(AddressingMode::ZeroPageIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDA(AddressingMode::ZeroPageIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDX(AddressingMode::ZeroPageIndexedWithY),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SMB3(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CLV(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDA(AddressingMode::AbsoluteIndexedWithY),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::TSX(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0xbb }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDY(AddressingMode::AbsoluteIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDA(AddressingMode::AbsoluteIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::LDX(AddressingMode::AbsoluteIndexedWithY),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBS3(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CPY(AddressingMode::Immediate),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CMP(AddressingMode::ZeroPageIndexedIndirect),
cycles: 6,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0xc2 }),
Instruction::Invalid(InvalidInstruction { opcode: 0xc3 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CPY(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CMP(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::DEC(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SMB4(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::INY(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CMP(AddressingMode::Immediate),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::DEX(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::WAI(AddressingMode::Implied),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CPY(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CMP(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::DEC(AddressingMode::AbsoluteA),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBS4(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BNE(AddressingMode::ProgramCounterRelative),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CMP(AddressingMode::ZeroPageIndirectIndexedWithY),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CMP(AddressingMode::ZeroPageIndirect),
cycles: 5, // Unsure, look here: https://cx16.dk/65c02/reference.html#CMP
}),
Instruction::Invalid(InvalidInstruction { opcode: 0xd3 }),
Instruction::Invalid(InvalidInstruction { opcode: 0xd4 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CMP(AddressingMode::ZeroPageIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::DEC(AddressingMode::ZeroPageIndexedWithX),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SMB5(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CLD(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CMP(AddressingMode::AbsoluteIndexedWithY),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::PHX(AddressingMode::Stack),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::STP(AddressingMode::Implied),
cycles: 3,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0xdc }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CMP(AddressingMode::AbsoluteIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::DEC(AddressingMode::AbsoluteIndexedWithX),
cycles: 7,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBS5(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CPX(AddressingMode::Immediate),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SBC(AddressingMode::ZeroPageIndexedIndirect),
cycles: 6,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0xe2 }),
Instruction::Invalid(InvalidInstruction { opcode: 0xe3 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CPX(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SBC(AddressingMode::ZeroPage),
cycles: 3,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::INC(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SMB6(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::INX(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SBC(AddressingMode::Immediate),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::NOP(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0xeb }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::CPX(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SBC(AddressingMode::AbsoluteA),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::INC(AddressingMode::AbsoluteA),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBS6(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BEQ(AddressingMode::ProgramCounterRelative),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SBC(AddressingMode::ZeroPageIndirectIndexedWithY),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SBC(AddressingMode::ZeroPageIndirect),
cycles: 5,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0xf3 }),
Instruction::Invalid(InvalidInstruction { opcode: 0xf4 }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SBC(AddressingMode::ZeroPageIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::INC(AddressingMode::ZeroPageIndexedWithX),
cycles: 6,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SMB7(AddressingMode::ZeroPage),
cycles: 5,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SED(AddressingMode::Implied),
cycles: 2,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SBC(AddressingMode::AbsoluteIndexedWithY),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::PLX(AddressingMode::Stack),
cycles: 4,
}),
Instruction::Invalid(InvalidInstruction { opcode: 0xfb }),
Instruction::Invalid(InvalidInstruction { opcode: 0xfc }),
Instruction::Valid(ValidInstruction {
opcode: Opcode::SBC(AddressingMode::AbsoluteIndexedWithX),
cycles: 4,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::INC(AddressingMode::AbsoluteIndexedWithX),
cycles: 7,
}),
Instruction::Valid(ValidInstruction {
opcode: Opcode::BBS7(AddressingMode::ProgramCounterRelativeTest),
cycles: 4,
}),
]
+4
View File
@@ -0,0 +1,4 @@
#[cfg(not(target_arch = "wasm32"))]
pub mod native;
#[cfg(target_arch = "wasm32")]
pub mod wasm;
+101
View File
@@ -0,0 +1,101 @@
use minifb::Window;
use std::{
io::stdout,
process::exit,
thread::sleep,
time::{Duration, Instant},
};
use termion::{
async_stdin, clear,
cursor::{self, Goto},
event::Key,
input::TermRead,
raw::IntoRawMode,
};
use crate::{cpu::Cpu, keyboard::Keyboard, video::Renderer};
#[derive(Debug)]
pub struct GeorgeEmu {
cpu: Cpu,
renderer: Renderer,
input: Option<Keyboard>,
window: Option<Window>,
}
impl GeorgeEmu {
pub fn new(
mut cpu: Cpu,
renderer: Renderer,
input: Option<Keyboard>,
window: Option<Window>,
) -> Self {
cpu.reset();
Self {
cpu,
input,
renderer,
window,
}
}
pub fn draw(&mut self) {
self.renderer.render(self.window.as_mut())
}
pub fn cycle(&mut self) {
self.cpu.cycle()
}
pub fn run(&mut self) {
if self.window.is_none() {
let _ = stdout().into_raw_mode();
print!("{}{}", cursor::Hide, clear::All,);
}
let clock_interval = Duration::from_nanos(500);
let frame_interval = Duration::from_millis(16);
let mut next_clock = Instant::now() + clock_interval;
let mut next_frame = Instant::now() + frame_interval;
loop {
let now = Instant::now();
if now >= next_clock {
self.cpu.cycle();
next_clock += clock_interval;
}
if now >= next_frame {
if self.window.is_none() {
self.handle_input();
}
self.draw();
next_frame += frame_interval;
}
let next_run = std::cmp::min(next_clock, next_frame);
let sleep_dur = next_run - now;
sleep(sleep_dur)
}
}
fn handle_input(&mut self) {
let mut stdin = async_stdin().keys();
if let Some(Ok(key)) = stdin.next() {
match key {
Key::Char('q') => {
print!("{}{}{}", clear::All, cursor::Show, Goto(1, 1));
exit(0);
}
Key::Char('`') => self.cpu.toggle_stopped(),
Key::Char('\n') => self.cpu.cycle(),
Key::Char('i') => self.cpu.interrupt(),
_ => {
self.input.as_mut().unwrap().read_keys(key);
}
}
} else {
self.input.as_mut().unwrap().clear_keys();
}
}
}
+29
View File
@@ -0,0 +1,29 @@
use minifb::Window;
use crate::{cpu::Cpu, video::Renderer};
#[derive(Debug)]
pub struct GeorgeEmu {
cpu: Cpu,
renderer: Renderer,
window: Window,
}
impl GeorgeEmu {
pub fn new(mut cpu: Cpu, renderer: Renderer, window: Window) -> Self {
cpu.reset();
Self {
cpu,
renderer,
window,
}
}
pub fn draw(&mut self) {
self.renderer.render(Some(&mut self.window));
}
pub fn cycle(&mut self) {
self.cpu.cycle()
}
}
+137
View File
@@ -0,0 +1,137 @@
#[cfg(not(target_arch = "wasm32"))]
use std::io::{self, Write};
use std::{env, fs::File, io::Read, path::Path};
#[cfg(not(target_arch = "wasm32"))]
use termion::{
color::{self, Bg, Fg},
cursor::Goto,
};
use crate::{
cpu::CpuController,
memory::{MemHandle, MemoryReader},
};
use minifb::Window;
#[cfg(not(target_arch = "wasm32"))]
const FG_COLOR: u32 = 0xFFCC00;
#[cfg(not(target_arch = "wasm32"))]
const BG_COLOR: u32 = 0x110500;
// Wasm colors are ABGR
#[cfg(target_arch = "wasm32")]
const FG_COLOR: u32 = 0xFF00CCFF;
#[cfg(target_arch = "wasm32")]
const BG_COLOR: u32 = 0xFF000511;
const WIDTH: usize = 512;
const HEIGHT: usize = 380;
// pub fn get_char_bin<P>(char_rom: Option<P>) -> [u8; 0x8000]
// where
// P: AsRef<Path>,
// {
// match char_rom {
// Some(path) => {
// let mut file = File::open(path).unwrap();
// let mut bin = vec![0; 0x8000];
// file.read_exact(&mut bin).unwrap();
// // println!("reading char rom");
// bin.try_into().unwrap()
// }
// None => *include_bytes!("./roms/cozette.rom"),
// }
// }
const CHAR_ROM: &[u8; 0x8000] = include_bytes!(concat!(env!("OUT_DIR"), "/cozette.rom"));
#[cfg(not(target_arch = "wasm32"))]
const ASCII_LOOKUP: [&str; 256] = [
" ", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "奔", "", "", "", "", "", "", " ", "!", "\"", "#",
"$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6",
"7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\",
"]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "ʕ", "·", "", "ʔ", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "🔮", "", "",
"", "", "🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "", "", "", "", "", "", "",
"", "", "", "", "🎁", "", "", "", "", "", "", "", "", "",
];
#[derive(Debug)]
pub struct Renderer {
memory: MemHandle,
controller: CpuController,
}
impl Renderer {
pub fn new(controller: CpuController, memory: MemHandle) -> Self {
Self { memory, controller }
}
pub fn render(&self, window: Option<&mut Window>) {
match window {
Some(window) => {
// the rest of this function is arcane wizardry
// based on the specifics of george's weird
// display and characters... don't fuck around w it
let mut i = 0;
let mut buffer = [0; WIDTH * HEIGHT];
for char_row in 0..29 {
for char_col in 0..64 {
let ascii = self.read(0x6000 + i);
i += 1;
for row in 0..13 {
let byte = CHAR_ROM[ascii as usize + (row * 0x100)];
for bit_index in (0..8).rev() {
let buffer_index =
((char_row) * 13 + (row)) * 512 + (char_col * 8 + bit_index);
if (byte << bit_index) & 0x80 == 0x80 {
buffer[buffer_index] = FG_COLOR;
} else {
buffer[buffer_index] = BG_COLOR;
}
}
}
}
}
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();
}
None => {
#[cfg(not(target_arch = "wasm32"))]
{
let mut stdout = io::stdout();
let mut i = 0;
for char_row in 0..29 {
for char_col in 0..64 {
let ascii = self.read(0x6000 + i);
i += 1;
let char = ASCII_LOOKUP[ascii as usize];
let _ = write!(
// FG_COLOR = 0xFFCC00
// BG_COLOR = 0x110500
stdout,
"{}{}{}{char}",
Goto(char_col + 1, char_row + 1),
Fg(color::Rgb(0xFF, 0xCC, 0x00)),
Bg(color::Rgb(0x11, 0x05, 0x00))
);
}
}
}
}
}
self.controller.irq();
}
}
impl MemoryReader for Renderer {
fn read(&self, address: u16) -> u8 {
self.memory.read(address)
}
}