Compare commits

..

2 Commits

19 changed files with 1146 additions and 818 deletions

325
Cargo.lock generated
View File

@ -14,31 +14,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "bdf"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f550a6818e6f42ccd5883f44e45fff4f68415a0d09abdc81e7d1d78e0780af14"
dependencies = [
"bit-set",
"thiserror",
]
[[package]]
name = "bit-set"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84527c7b0452f22545cc010e72d366a435561d2b28b978035550b3778c4d428d"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -70,28 +45,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossterm"
version = "0.27.0"
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"bitflags 2.5.0",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
"cfg-if",
"wasm-bindgen",
]
[[package]]
@ -122,7 +82,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys",
]
[[package]]
@ -225,12 +185,12 @@ name = "georgeemu"
version = "0.1.0"
dependencies = [
"anyhow",
"bdf",
"crossterm",
"console_error_panic_hook",
"minifb",
"serde",
"termion",
"toml",
"web-sys",
]
[[package]]
@ -262,10 +222,16 @@ dependencies = [
]
[[package]]
name = "js-sys"
version = "0.3.69"
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
dependencies = [
"wasm-bindgen",
]
@ -289,7 +255,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
dependencies = [
"cfg-if",
"windows-targets 0.52.5",
"windows-targets",
]
[[package]]
@ -300,7 +266,7 @@ checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607"
dependencies = [
"bitflags 2.5.0",
"libc",
"redox_syscall 0.4.1",
"redox_syscall",
]
[[package]]
@ -309,16 +275,6 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.21"
@ -343,10 +299,10 @@ dependencies = [
[[package]]
name = "minifb"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c470a74618b43cd182c21b3dc1e6123501249f3bad9a0085e95d1304ca2478"
source = "git+https://github.com/augustkline/rust_minifb#2e2fdcf1d692c8c3d827a221a66569d81c73f99a"
dependencies = [
"cc",
"console_error_panic_hook",
"dlib",
"futures",
"instant",
@ -358,26 +314,16 @@ dependencies = [
"serde",
"serde_derive",
"tempfile",
"wasm-bindgen",
"wasm-bindgen-futures",
"wayland-client",
"wayland-cursor",
"wayland-protocols",
"web-sys",
"winapi",
"x11-dl",
]
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "nix"
version = "0.24.3"
@ -414,29 +360,6 @@ dependencies = [
"sdl2-sys",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.2",
"smallvec",
"windows-targets 0.52.5",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
@ -488,15 +411,6 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
dependencies = [
"bitflags 2.5.0",
]
[[package]]
name = "redox_termios"
version = "0.1.3"
@ -513,21 +427,21 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sdl2"
version = "0.35.2"
@ -571,6 +485,18 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.6"
@ -580,36 +506,6 @@ dependencies = [
"serde",
]
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.9"
@ -645,7 +541,7 @@ dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys 0.52.0",
"windows-sys",
]
[[package]]
@ -660,26 +556,6 @@ dependencies = [
"redox_termios",
]
[[package]]
name = "thiserror"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml"
version = "0.8.14"
@ -726,27 +602,24 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
dependencies = [
"cfg-if",
"once_cell",
"serde",
"serde_json",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
dependencies = [
"bumpalo",
"log",
@ -771,9 +644,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -781,9 +654,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [
"proc-macro2",
"quote",
@ -794,9 +667,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
[[package]]
name = "wayland-client"
@ -873,9 +746,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.69"
version = "0.3.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -903,37 +776,13 @@ 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.5",
]
[[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",
"windows-targets",
]
[[package]]
@ -942,46 +791,28 @@ version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.5",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.5",
"windows_x86_64_gnu 0.52.5",
"windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc 0.52.5",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[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.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[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.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[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.5"
@ -994,48 +825,24 @@ version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[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.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[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.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[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.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[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.5"

View File

@ -3,15 +3,27 @@ name = "georgeemu"
version = "0.1.0"
edition = "2021"
[[bin]]
path = "src/bin/main.rs"
name = "georgeemu"
[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
[dependencies]
anyhow = "1.0.81"
bdf = "0.6.0"
crossterm = "0.27.0"
minifb = "0.27.0"
# minifb = "0.25.0"
# ratatui = "0.26.3"
minifb = { git = "https://github.com/augustkline/rust_minifb" }
serde = { version = "1.0.197", features = ["serde_derive", "derive"] }
web-sys = "0.3.70"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
minifb = { git = "https://github.com/augustkline/rust_minifb", features = [
"web",
] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
termion = "4.0.2"
toml = "0.8.12"
toml = { version = "0.8.12" }

View File

@ -1,3 +1,3 @@
char_rom = "./src/roms/cozette.rom"
rom = "./src/roms/george.rom"
rom = "./src/roms/keyboard_sys.rom"
screen = "Window"

View File

@ -7,7 +7,18 @@ use std::{
use serde::{Deserialize, Serialize};
use crate::video::ScreenType;
#[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 {
@ -27,7 +38,7 @@ impl ConfigBuilder {
fn build(self) -> Config {
let rom = match self.rom {
Some(rom) => rom,
Some(rom) => Some(rom),
None => {
println!("no rom was provided :(");
exit(1);
@ -44,12 +55,6 @@ impl ConfigBuilder {
}
}
pub struct Config {
pub rom: String,
pub screen: ScreenType,
pub char_rom: Option<String>,
}
fn help(command: Option<String>) {
let executable: String = env::args().next().unwrap();
if let Some(command) = command {
@ -101,6 +106,7 @@ pub fn get_input() -> Config {
}
Err(_) => {
println!("couldn't find a `george.toml` in the current directory!");
println!("\nuse `{executable} --help` to see all config options");
exit(1);
}
};

49
src/bin/main.rs Normal file
View File

@ -0,0 +1,49 @@
#[cfg(not(target_arch = "wasm32"))]
mod cli;
#[cfg(not(target_arch = "wasm32"))]
use cli::get_input;
#[cfg(not(target_arch = "wasm32"))]
use georgeemu::GeorgeEmu;
#[cfg(not(target_arch = "wasm32"))]
use minifb::{Scale, ScaleMode, Window, WindowOptions};
#[cfg(not(target_arch = "wasm32"))]
fn main() {
use std::{fs::File, io::Read};
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();
// println!("reading char rom");
bin.try_into().unwrap()
}
None => *include_bytes!("../roms/george.rom"),
};
let mut emu = GeorgeEmu::builder().rom(rom).window(window).build();
emu.run();
}
#[cfg(target_arch = "wasm32")]
fn main() {}

View File

@ -2,11 +2,11 @@ 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;
use termion::cursor::Goto;
#[derive(Clone, Copy)]
pub enum StatusFlag {
Negative = 0b1000_0000,
@ -19,9 +19,10 @@ pub enum StatusFlag {
Carry = 0b0000_0001,
}
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct CpuController(Sender<CpuControl>);
#[derive(Clone, Copy)]
pub enum CpuControl {
Irq,
Nmi,
@ -35,22 +36,23 @@ impl CpuController {
Self(sender)
}
pub fn irq(&self) {
self.0.send(CpuControl::Irq);
let _ = self.0.send(CpuControl::Irq);
}
pub fn nmi(&self) {
self.0.send(CpuControl::Nmi);
let _ = self.0.send(CpuControl::Nmi);
}
pub fn toggle(&self) {
self.0.send(CpuControl::Toggle);
let _ = self.0.send(CpuControl::Toggle);
}
pub fn cycle(&self) {
self.0.send(CpuControl::Cycle);
let _ = self.0.send(CpuControl::Cycle);
}
pub fn data(&self) {
self.0.send(CpuControl::Data);
let _ = self.0.send(CpuControl::Data);
}
}
#[derive(Debug)]
pub struct CpuReceiver(Receiver<CpuControl>);
impl CpuReceiver {
pub fn new(receiver: Receiver<CpuControl>) -> Self {
@ -58,6 +60,7 @@ impl CpuReceiver {
}
}
#[derive(Debug)]
pub struct Cpu {
pub a: u8, // Accumulator Register
pub x: u8, // X Register
@ -69,10 +72,10 @@ pub struct Cpu {
pub nmi: bool,
pub memory: MemHandle,
pub pending_cycles: usize,
pub debug: bool,
receiver: Option<CpuReceiver>,
stopped: bool,
cycle: bool, // cycle_count: usize,
// state_tx: Sender<CpuState>,
cycle: bool,
}
impl Display for Cpu {
@ -94,7 +97,6 @@ impl MemoryWriter for Cpu {
impl Cpu {
pub fn new(memory: MemHandle) -> Self {
// pub fn new(memory: MemHandle) -> Self {
Cpu {
a: 0x00,
x: 0x00,
@ -106,6 +108,7 @@ impl Cpu {
nmi: false,
receiver: None,
memory,
debug: false,
stopped: false,
pending_cycles: 0,
cycle: false, // cycle_count: 0,
@ -179,13 +182,6 @@ impl Cpu {
value & 0b1000_0000 == 0b1000_0000
}
// pub fn execute(&mut self) {
// self.cycle();
// while self.pending_cycles != 0 {
// self.cycle();
// }
// }
pub fn wait_for_interrupt(&self) {
unimplemented!()
}
@ -198,6 +194,10 @@ impl Cpu {
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) => {
@ -213,7 +213,8 @@ impl Cpu {
self.stopped = !self.stopped;
}
CpuControl::Cycle => self.cycle = true,
CpuControl::Data => {} // self
CpuControl::Data => {
// self
// .state_tx
// .send(CpuState {
// a: self.a.clone(), // Accumulator Register
@ -228,6 +229,7 @@ impl Cpu {
// .unwrap(),
}
}
}
None => {}
}
}
@ -241,6 +243,8 @@ impl Cpu {
}
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));
@ -252,45 +256,13 @@ impl Cpu {
let opcode = self.read(self.pc);
let instruction = get_instruction(opcode);
instruction.call(self);
// println!("a: {a:#04x}, x: {x:#04x}, y: {y:#04x}, pc: {pc:#06x}, sp: {s:#04x}, sr: {p:#010b}, irq: {irq:?}, nmi: {nmi:?}", a = self.a, x = self.x, y = self.y, pc = self.pc, s = self.s, p = self.p, irq = self.irq, nmi = self.nmi);
// println!(
// "Instruction: {:?}, {:#04x}",
// valid_instruction.opcode, opcode
// );
// println!("");
}
pub fn stop(&mut self) {
self.stopped = true;
}
pub fn breakpoint(&mut self) {
// println!("a: {a:#04x}, x: {x:#04x}, y: {y:#04x}, pc: {pc:#06x}, sp: {s:#04x}, sr: {p:#010b}, irq: {irq:?}, nmi: {nmi:?}", a = self.a, x = self.x, y = self.y, pc = self.pc, s = self.s, p = self.p, irq = self.irq, nmi = self.nmi);
// println!(
// "Instruction: {:?}, {:#04x}",
// valid_instruction.opcode, opcode
// );
// println!("");
// println!(
// "{}{:#04x}: {:#02x}, {:#04x}: {:#02x}",
// Goto(1, 35),
// 0x20,
// self.read(0x20),
// 0x21,
// self.read(0x21)
// );
// println!(
// "{}str_ptr {:#04x}: {:#02x}, {:#04x}: {:#02x}",
// Goto(1, 36),
// 0x30,
// self.read(0x30),
// 0x31,
// self.read(0x31)
// );
// println!(
// "{}cursor - x: {:#02x}, y: {:#02x}",
// Goto(1, 37),
// self.read(0x300),
// self.read(0x301)
// );
if self.debug {
self.stop();
}
}
}

View File

@ -1,67 +0,0 @@
#[derive(Debug)]
pub enum GeorgeErrorKind {
Memory(MemoryError),
Execution(ExecutionError),
AddrMode(AddressingModeError),
Mapping(MappingError),
}
impl From<MemoryError> for GeorgeErrorKind {
fn from(value: MemoryError) -> Self {
Self::Memory(value)
}
}
impl From<AddressingModeError> for GeorgeErrorKind {
fn from(value: AddressingModeError) -> Self {
Self::AddrMode(value)
}
}
impl From<ExecutionError> for GeorgeErrorKind {
fn from(value: ExecutionError) -> Self {
Self::Execution(value)
}
}
#[derive(Debug)]
pub enum ExecutionError {
InvalidInstruction,
InterruptsDisabled,
StackOverflow,
MemoryError(MemoryError),
}
#[derive(Debug)]
pub enum AddressingModeError {
IncompatibleAddrMode,
}
impl From<ExecutionError> for AddressingModeError {
fn from(_value: ExecutionError) -> Self {
Self::IncompatibleAddrMode
}
}
impl From<MemoryError> for ExecutionError {
fn from(error: MemoryError) -> Self {
ExecutionError::MemoryError(error)
}
}
#[derive(Debug)]
pub struct GeorgeError {
pub desc: String,
pub kind: GeorgeErrorKind,
}
#[derive(Debug)]
pub enum MemoryError {
Unmapped,
NoDataAtAddress,
Unwritable,
}
#[derive(Debug)]
pub enum MappingError {
RegionOccupied,
InvalidPage,
}

View File

@ -1,10 +1,9 @@
#![allow(clippy::upper_case_acronyms)]
use core::panic;
use std::process::exit;
use termion::cursor::Goto;
use termion::{clear, color};
#[cfg(not(target_arch = "wasm32"))]
use termion::{clear, color, cursor::Goto};
#[cfg(target_arch = "wasm32")]
use web_sys::console;
use crate::cpu::{Cpu, StatusFlag};
use crate::memory::{MemoryReader, MemoryWriter};
@ -23,47 +22,13 @@ pub struct Instruction<'a> {
impl Instruction<'_> {
pub fn call(&self, cpu: &mut Cpu) {
// TODO: add flag to print these
// print!("{}{}a: {a:#04x}, x: {x:#04x}, y: {y:#04x}, pc: {pc:#06x}, sp: {s:#04x}, sr: {p:#010b}, irq: {irq:?}, nmi: {nmi:?}", Goto(0, 31),clear::UntilNewline, a = cpu.a, x = cpu.x, y = cpu.y, pc = cpu.pc, s = cpu.s, p = cpu.p, irq = cpu.irq, nmi = cpu.nmi);
// print!(
// "{}instruction: {:?}, pc: {:#04x}",
// Goto(0, 31),
// self.name,
// cpu.pc
// );
// print!(
// "{}{}instruction: {:?}, mode: {:?}",
// Goto(0, 32),
// clear::UntilNewline,
// self.name,
// self.addr_mode
// );
// print!(
// "{}{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {}{}{:02x}{}{} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
// Goto(0, 33),
// cpu.read(cpu.pc.wrapping_sub(8)),
// cpu.read(cpu.pc.wrapping_sub(7)),
// cpu.read(cpu.pc.wrapping_sub(6)),
// cpu.read(cpu.pc.wrapping_sub(5)),
// cpu.read(cpu.pc.wrapping_sub(4)),
// cpu.read(cpu.pc.wrapping_sub(3)),
// cpu.read(cpu.pc.wrapping_sub(2)),
// cpu.read(cpu.pc.wrapping_sub(1)),
// color::Bg(color::Rgb(0xFF, 0xCC, 0x00)),
// color::Fg(color::Rgb(0x11, 0x05, 0x00)),
// cpu.read(cpu.pc),
// color::Fg(color::Rgb(0xFF, 0xCC, 0x00)),
// color::Bg(color::Rgb(0x11, 0x05, 0x00)),
// cpu.read(cpu.pc.wrapping_add(1)),
// cpu.read(cpu.pc.wrapping_add(2)),
// cpu.read(cpu.pc.wrapping_add(3)),
// cpu.read(cpu.pc.wrapping_add(4)),
// cpu.read(cpu.pc.wrapping_add(5)),
// cpu.read(cpu.pc.wrapping_add(6)),
// cpu.read(cpu.pc.wrapping_add(7)),
// cpu.read(cpu.pc.wrapping_add(8)),
// );
if cpu.debug {
self.debug(cpu);
}
cpu.pc = cpu.pc.wrapping_add(1); // read instruction byte
#[allow(clippy::single_match)]
match self.instr_fn {
// existence of instr_fn means this is a valid instruction
Some(instr_fn) => {
@ -81,16 +46,119 @@ impl Instruction<'_> {
} // None for address_fn implies it's implied (lol)/stack
}
}
None => {
panic!(
"An invalid instruction was called at {:04x}, with opcode {:02x}",
None =>
{
#[cfg(not(target_arch = "wasm32"))]
if cpu.debug {
println!(
"{}An invalid instruction was called at {:04x}, with opcode {:02x}",
Goto(0, 35),
cpu.pc,
cpu.read(cpu.pc)
);
cpu.breakpoint();
}
}
}
}
fn debug(&self, cpu: &Cpu) {
#[cfg(not(target_arch = "wasm32"))]
{
// cpu state
print!("{}{}a: {a:#04x}, x: {x:#04x}, y: {y:#04x}, pc: {pc:#06x}, sp: {s:#04x}, sr: {p:#010b}, irq: {irq:?}, nmi: {nmi:?}", Goto(0, 31),clear::UntilNewline, a = cpu.a, x = cpu.x, y = cpu.y, pc = cpu.pc, s = cpu.s, p = cpu.p, irq = cpu.irq, nmi = cpu.nmi);
print!(
"{}instruction: {:?}, pc: {:#04x}",
Goto(0, 31),
self.name,
cpu.pc
);
// current instruction and addressing mode
print!(
"{}{}instruction: {:?}, mode: {:?}",
Goto(0, 32),
clear::UntilNewline,
self.name,
self.addr_mode
);
// local memory
print!(
"{}{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {}{}{:02x}{}{} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
Goto(0, 33),
cpu.read(cpu.pc.wrapping_sub(8)),
cpu.read(cpu.pc.wrapping_sub(7)),
cpu.read(cpu.pc.wrapping_sub(6)),
cpu.read(cpu.pc.wrapping_sub(5)),
cpu.read(cpu.pc.wrapping_sub(4)),
cpu.read(cpu.pc.wrapping_sub(3)),
cpu.read(cpu.pc.wrapping_sub(2)),
cpu.read(cpu.pc.wrapping_sub(1)),
color::Bg(color::Rgb(0xFF, 0xCC, 0x00)),
color::Fg(color::Rgb(0x11, 0x05, 0x00)),
cpu.read(cpu.pc),
color::Fg(color::Rgb(0xFF, 0xCC, 0x00)),
color::Bg(color::Rgb(0x11, 0x05, 0x00)),
cpu.read(cpu.pc.wrapping_add(1)),
cpu.read(cpu.pc.wrapping_add(2)),
cpu.read(cpu.pc.wrapping_add(3)),
cpu.read(cpu.pc.wrapping_add(4)),
cpu.read(cpu.pc.wrapping_add(5)),
cpu.read(cpu.pc.wrapping_add(6)),
cpu.read(cpu.pc.wrapping_add(7)),
cpu.read(cpu.pc.wrapping_add(8)),
);
// scratchpad
print!(
"{} {:02x} {:02x} {:02x}",
Goto(0, 34),
cpu.read(0x200),
cpu.read(0x201),
cpu.read(0x202)
)
} // idk if we should panic here, if we don't it's undefined behavior, which
// might actually be what we want in the cases where it's an undocumented
// 6502 instruction, but otherwise we should panic prolly, for now i'm just
// gonna panic
}
#[cfg(target_arch = "wasm32")]
{
let cpu_state = format!("a: {a:#04x}, x: {x:#04x}, y: {y:#04x}, pc: {pc:#06x}, sp: {s:#04x}, sr: {p:#010b}, irq: {irq:?}, nmi: {nmi:?}", a = cpu.a, x = cpu.x, y = cpu.y, pc = cpu.pc, s = cpu.s, p = cpu.p, irq = cpu.irq, nmi = cpu.nmi);
console::log_1(&cpu_state.into());
let curr_instr = format!("instruction: {:?}, mode: {:?}", self.name, self.addr_mode);
console::log_1(&curr_instr.into());
let local_mem = format!(
"{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} %c{:02x}%c {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
cpu.read(cpu.pc.wrapping_sub(8)),
cpu.read(cpu.pc.wrapping_sub(7)),
cpu.read(cpu.pc.wrapping_sub(6)),
cpu.read(cpu.pc.wrapping_sub(5)),
cpu.read(cpu.pc.wrapping_sub(4)),
cpu.read(cpu.pc.wrapping_sub(3)),
cpu.read(cpu.pc.wrapping_sub(2)),
cpu.read(cpu.pc.wrapping_sub(1)),
cpu.read(cpu.pc),
cpu.read(cpu.pc.wrapping_add(1)),
cpu.read(cpu.pc.wrapping_add(2)),
cpu.read(cpu.pc.wrapping_add(3)),
cpu.read(cpu.pc.wrapping_add(4)),
cpu.read(cpu.pc.wrapping_add(5)),
cpu.read(cpu.pc.wrapping_add(6)),
cpu.read(cpu.pc.wrapping_add(7)),
cpu.read(cpu.pc.wrapping_add(8)),
);
console::log_3(
&local_mem.into(),
&"background: black; color: white".into(),
&"background: unset; color: unset".into(),
);
let scratchpad = format!(
"scratchpad: {:02x} {:02x} {:02x}",
cpu.read(0x200),
cpu.read(0x201),
cpu.read(0x202)
);
console::log_1(&scratchpad.into());
}
}
}
@ -209,7 +277,6 @@ fn zero_page_indirect_indexed_with_y(
let address = cpu.read_word(zp as u16);
let address: u16 = address.wrapping_add(cpu.y as u16);
cpu.pc = cpu.pc.wrapping_add(1);
println!("{}indirect addr: {:#04x}", Goto(1, 39), address);
address
}
@ -255,7 +322,7 @@ fn branch(cpu: &mut Cpu, condition: bool, address: u16) {
cpu.pc = cpu.pc.wrapping_add(1);
}
fn brkpt(cpu: &mut Cpu, address: Option<u16>) {
fn breakpoint(cpu: &mut Cpu, _address: Option<u16>) {
cpu.breakpoint()
}
@ -264,7 +331,7 @@ fn adc(cpu: &mut Cpu, address: Option<u16>) {
let byte = cpu.read(address);
let carry = cpu.get_flag(StatusFlag::Carry);
let result = cpu.a as u16 + byte as u16 + carry as u16;
cpu.set_flag(StatusFlag::Carry, result > u16::from(u8::max_value()));
cpu.set_flag(StatusFlag::Carry, result > u16::from(u8::MAX));
cpu.set_flag(StatusFlag::Zero, result as u8 == 0);
cpu.set_flag(
StatusFlag::Overflow,
@ -786,7 +853,7 @@ fn sbc(cpu: &mut Cpu, address: Option<u16>) {
let byte = cpu.read(address);
let carry = cpu.get_flag(StatusFlag::Carry);
let result = cpu.a as u16 + byte as u16 + !carry as u16;
cpu.set_flag(StatusFlag::Carry, result > u16::from(u8::max_value()));
cpu.set_flag(StatusFlag::Carry, result > u16::from(u8::MAX));
cpu.set_flag(StatusFlag::Zero, result as u8 == 0);
cpu.set_flag(
StatusFlag::Overflow,
@ -948,7 +1015,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 7,
name: "brk",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(ora),
@ -958,18 +1025,18 @@ pub const OPCODES: [Instruction; 256] = [
addr_mode: "zero_page_indexed_indirect",
},
Instruction {
instr_fn: Some(brkpt),
instr_fn: Some(breakpoint),
address_fn: None,
cycles: 0,
name: "brkpt",
addr_mode: "none",
name: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(tsb),
@ -1004,7 +1071,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 3,
name: "php",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(ora),
@ -1025,7 +1092,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(tsb),
@ -1081,7 +1148,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(trb),
@ -1116,7 +1183,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "clc",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(ora),
@ -1137,7 +1204,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(trb),
@ -1186,14 +1253,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(bit),
@ -1228,7 +1295,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 4,
name: "plp",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(and),
@ -1249,7 +1316,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(bit),
@ -1305,7 +1372,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(bit),
@ -1340,7 +1407,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "sec",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(and),
@ -1361,7 +1428,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(bit),
@ -1396,7 +1463,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 6,
name: "rti",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(eor),
@ -1410,21 +1477,21 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(eor),
@ -1452,7 +1519,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 3,
name: "pha",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(eor),
@ -1473,7 +1540,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(jmp),
@ -1529,14 +1596,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(eor),
@ -1564,7 +1631,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "cli",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(eor),
@ -1578,21 +1645,21 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 3,
name: "phy",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(eor),
@ -1620,7 +1687,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 6,
name: "rts",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(adc),
@ -1634,14 +1701,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(stz),
@ -1676,7 +1743,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 4,
name: "pla",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(adc),
@ -1697,7 +1764,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(jmp),
@ -1753,7 +1820,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(stz),
@ -1788,7 +1855,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "sei",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(adc),
@ -1802,14 +1869,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 4,
name: "ply",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(jmp),
@ -1858,14 +1925,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(sty),
@ -1900,7 +1967,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "dey",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(bit),
@ -1914,14 +1981,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "txa",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(sty),
@ -1977,7 +2044,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(sty),
@ -2012,7 +2079,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "tya",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(sta),
@ -2026,14 +2093,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "txs",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(stz),
@ -2089,7 +2156,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(ldy),
@ -2124,7 +2191,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "tay",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(lda),
@ -2138,14 +2205,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "tax",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(ldy),
@ -2201,7 +2268,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(ldy),
@ -2236,7 +2303,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "clv",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(lda),
@ -2250,14 +2317,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "tsx",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(ldy),
@ -2306,14 +2373,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(cpy),
@ -2348,7 +2415,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "iny",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(cmp),
@ -2362,14 +2429,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "dex",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(wai),
address_fn: None,
cycles: 3,
name: "wai",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(cpy),
@ -2425,14 +2492,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(cmp),
@ -2460,7 +2527,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "cld",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(cmp),
@ -2474,21 +2541,21 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 3,
name: "phx",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(stp),
address_fn: None,
cycles: 3,
name: "stp",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(cmp),
@ -2530,14 +2597,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(cpx),
@ -2572,7 +2639,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "inx",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(sbc),
@ -2586,14 +2653,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "nop",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(cpx),
@ -2649,14 +2716,14 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(sbc),
@ -2684,7 +2751,7 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 2,
name: "sed",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(sbc),
@ -2698,21 +2765,21 @@ pub const OPCODES: [Instruction; 256] = [
address_fn: None,
cycles: 4,
name: "plx",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: None,
address_fn: None,
cycles: 0,
name: "none",
addr_mode: "none",
addr_mode: "implied",
},
Instruction {
instr_fn: Some(sbc),

View File

@ -1,9 +1,12 @@
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,
}
@ -13,6 +16,7 @@ impl Keyboard {
Self { memory }
}
#[cfg(not(target_arch = "wasm32"))]
pub fn clear_keys(&self) {
self.memory.write(0x4400, 0x00);
self.memory.write(0x4401, 0x00);
@ -22,6 +26,7 @@ impl Keyboard {
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;

252
src/lib.rs Normal file
View File

@ -0,0 +1,252 @@
pub mod cpu;
mod instructions;
mod keyboard;
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,
}
}
// pub fn window(self, mut window: Window) -> GeorgeEmuBuilder<NoRom, SomeWindow> {
// let Self {
// rom,
// cpu,
// ref cpu_controller,
// ref memory_handle,
// keyboard,
// ..
// } = 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),
// }
// }
}
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
}
#[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,
))
}
#[cfg(target_arch = "wasm32")]
pub fn build(self) -> GeorgeEmu {
GeorgeEmu(imp::GeorgeEmu::new(
self.cpu.unwrap(),
self.renderer.unwrap(),
self.window.0,
))
}
}
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 {
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,
))
}
}

View File

@ -1,74 +0,0 @@
mod cli;
mod cpu;
mod error;
mod instructions;
mod keyboard;
mod memory;
mod types;
mod video;
use crate::cpu::Cpu;
use crate::keyboard::Keyboard;
use crate::memory::Mem;
use crate::video::Screen;
use cli::get_input;
use crossterm::cursor;
use memory::MemHandle;
use std::io::stdout;
use std::thread::{self, sleep};
use std::time::Duration;
use termion::cursor::Goto;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::{async_stdin, clear};
fn main() {
let _stdout = stdout().into_raw_mode();
let config = get_input();
let mut memory = Mem::new();
let _ = memory.load_rom(&config.rom);
let shared_memory = MemHandle::new(memory);
let screen_memory = shared_memory.clone();
let cpu_memory = shared_memory.clone();
let keyboard_memory = shared_memory.clone();
let keyboard = Keyboard::new(keyboard_memory);
let (mut cpu, cpu_controller) = Cpu::new_with_control(cpu_memory);
cpu.reset();
thread::spawn(move || loop {
cpu.cycle();
});
let screen_remote = cpu_controller.clone();
let mut screen = Screen::new(&config, screen_remote, screen_memory);
let mut stdin = async_stdin().keys();
print!("{}{}", cursor::Hide, clear::All,);
loop {
screen.draw();
if let Some(Ok(key)) = stdin.next() {
keyboard.read_keys(key);
match key {
Key::Char('q') => {
break;
}
Key::Char('`') => cpu_controller.toggle(),
Key::Char('\n') => cpu_controller.cycle(),
Key::Char('i') => cpu_controller.irq(),
_ => {}
}
} else {
keyboard.clear_keys();
}
sleep(Duration::from_millis(16));
}
print!("{}{}{}", clear::All, cursor::Show, Goto(1, 1));
}

View File

@ -1,10 +1,6 @@
use anyhow::{bail, Result};
use core::panic;
use std::cell::RefCell;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::exit;
use std::rc::Rc;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::{fs::File, io::Read};
@ -16,11 +12,11 @@ impl MemHandle {
Self(Arc::new(Mutex::new(memory)))
}
pub fn read(&self, address: u16) -> u8 {
let memory = self.0.lock().unwrap();
let memory = self.0.try_lock().unwrap();
memory.read(address)
}
pub fn write(&self, address: u16, data: u8) {
let mut memory = self.0.lock().unwrap();
let mut memory = self.0.try_lock().unwrap();
memory.write(address, data);
}
@ -51,13 +47,23 @@ pub struct Mem([u8; 0x10000]);
impl Default for Mem {
fn default() -> Self {
Self([0; 0x10000])
let bytes = include_bytes!("./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 {
Mem::default()
Self([0; 0x10000])
}
pub fn dump(&self, path: PathBuf) -> io::Result<()> {
let mut outfile = File::create(path)?;
@ -97,16 +103,22 @@ impl Mem {
pub fn poke(&self, address: u16) {
println!("{:02x}", self.read(address));
}
//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, value),
// Err(_) => {
// bail!("couldn't write byte {:#04x}", address);
// }
// }
// }
// Ok(())
//}
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(())
}
}

4
src/platform/mod.rs Normal file
View File

@ -0,0 +1,4 @@
#[cfg(not(target_arch = "wasm32"))]
pub mod native;
#[cfg(target_arch = "wasm32")]
pub mod wasm;

101
src/platform/native.rs Normal file
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
src/platform/wasm.rs Normal file
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()
}
}

237
src/roms/demo.asm Normal file
View File

@ -0,0 +1,237 @@
; .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:
.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
src/roms/demo.rom Normal file

Binary file not shown.

View File

@ -1,4 +0,0 @@
use std::{u16, u8};
pub type Byte = u8;
pub type Word = u16;

View File

@ -1,133 +1,52 @@
use minifb::{Scale, ScaleMode, Window, WindowOptions};
use serde::{Deserialize, Serialize};
#[cfg(not(target_arch = "wasm32"))]
use std::io::{self, Write};
#[cfg(not(target_arch = "wasm32"))]
use termion::{
color::{self, Bg, Color, Fg},
color::{self, Bg, Fg},
cursor::Goto,
screen::IntoAlternateScreen,
};
use crate::{
cli::Config,
cpu::CpuController,
keyboard::Keyboard,
memory::{MemHandle, MemoryReader},
types::{Byte, Word},
};
use std::{
fs::File,
io::{self, Read, Write},
path::Path,
process::exit,
thread::sleep,
time::{Duration, Instant},
};
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"),
}
}
// 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"),
// }
// }
trait Renderer {
fn render(&mut self) {}
}
const CHAR_ROM: &[u8; 0x8000] = include_bytes!("./roms/cozette.rom");
#[derive(Serialize, Deserialize, Debug, Default)]
pub enum ScreenType {
Window,
#[default]
Terminal,
}
pub struct WindowRenderer {
char_rom: [u8; 0x8000],
window: Window,
memory: MemHandle,
}
impl WindowRenderer {
pub fn new<P>(memory: MemHandle, char_rom: Option<P>, window: Window) -> Self
where
P: AsRef<Path>,
{
let char_rom = get_char_bin(char_rom);
Self {
memory,
window,
char_rom,
}
}
}
impl Renderer for WindowRenderer {
fn render(&mut self) {
// 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; 512 * 380];
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 = self.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;
}
}
}
}
}
self.window
.update_with_buffer(&buffer, WIDTH, HEIGHT)
.unwrap();
}
}
impl MemoryReader for WindowRenderer {
fn read(&self, address: Word) -> Byte {
self.memory.read(address)
}
}
pub struct TerminalRenderer {
memory: MemHandle,
}
impl TerminalRenderer {
pub fn new(memory: MemHandle) -> Self {
Self { memory }
}
}
impl MemoryReader for TerminalRenderer {
fn read(&self, address: Word) -> Byte {
self.memory.read(address)
}
}
const ASCII_LOOPUP: [&str; 256] = [
#[cfg(not(target_arch = "wasm32"))]
const ASCII_LOOKUP: [&str; 256] = [
" ", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "奔", "", "", "", "", "", "", " ", "!", "\"", "#",
"$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6",
@ -144,15 +63,55 @@ const ASCII_LOOPUP: [&str; 256] = [
"", "", "", "", "🎁", "", "", "", "", "", "", "", "", "",
];
impl Renderer for TerminalRenderer {
fn render(&mut self) {
#[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_LOOPUP[ascii as usize];
let char = ASCII_LOOKUP[ascii as usize];
let _ = write!(
// FG_COLOR = 0xFFCC00
// BG_COLOR = 0x110500
@ -166,52 +125,13 @@ impl Renderer for TerminalRenderer {
}
}
}
pub struct Screen {
renderer: Box<dyn Renderer>,
controller: CpuController,
}
impl Screen {
pub fn new(config: &Config, controller: CpuController, memory: MemHandle) -> Self {
let renderer: Box<dyn Renderer> = match config.screen {
ScreenType::Window => {
let mut 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();
window.set_input_callback(Box::new(Keyboard::new(memory.clone())));
Box::new(WindowRenderer::new(memory, config.char_rom.clone(), window))
}
ScreenType::Terminal => Box::new(TerminalRenderer::new(memory)),
};
Self {
renderer,
controller,
}
}
pub fn draw(&mut self) {
self.controller.irq();
self.renderer.render();
}
}
// pub fn run(&mut self) {
// loop {
// // sleep(Duration::from_millis(16));
// self.draw();
// }
// }
impl MemoryReader for Renderer {
fn read(&self, address: u16) -> u8 {
self.memory.read(address)
}
}