Big refactor! Better handling of wasm/native targets, and cute demo :)
This commit is contained in:
parent
8ac0cbc57b
commit
952b79cf91
|
@ -190,6 +190,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"termion",
|
"termion",
|
||||||
"toml",
|
"toml",
|
||||||
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -228,9 +229,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.69"
|
version = "0.3.70"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
@ -298,6 +299,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minifb"
|
name = "minifb"
|
||||||
version = "0.27.0"
|
version = "0.27.0"
|
||||||
|
source = "git+https://github.com/augustkline/rust_minifb#2e2fdcf1d692c8c3d827a221a66569d81c73f99a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
|
@ -602,11 +604,12 @@ checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.92"
|
version = "0.2.93"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
|
@ -614,9 +617,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.92"
|
version = "0.2.93"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
|
@ -641,9 +644,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.92"
|
version = "0.2.93"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
|
@ -651,9 +654,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.92"
|
version = "0.2.93"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -664,9 +667,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.92"
|
version = "0.2.93"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-client"
|
name = "wayland-client"
|
||||||
|
@ -743,9 +746,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.69"
|
version = "0.3.70"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
|
checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
16
Cargo.toml
16
Cargo.toml
|
@ -3,16 +3,10 @@ name = "georgeemu"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
|
||||||
debug = []
|
|
||||||
term = []
|
|
||||||
web = ["minifb/web"]
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
path = "src/bin/main.rs"
|
path = "src/bin/main.rs"
|
||||||
name = "georgeemu"
|
name = "georgeemu"
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.lib]
|
[target.'cfg(target_arch = "wasm32")'.lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
@ -20,11 +14,15 @@ crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.81"
|
anyhow = "1.0.81"
|
||||||
console_error_panic_hook = "0.1.7"
|
minifb = { git = "https://github.com/augustkline/rust_minifb" }
|
||||||
# minifb = { git = "https://github.com/augustkline/rust_minifb" }
|
|
||||||
minifb = { path = "/Users/august/projects/rust_minifb_fork/" }
|
|
||||||
serde = { version = "1.0.197", features = ["serde_derive", "derive"] }
|
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]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
termion = "4.0.2"
|
termion = "4.0.2"
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
char_rom = "./src/roms/cozette.rom"
|
char_rom = "./src/roms/cozette.rom"
|
||||||
rom = "./src/roms/demo.rom"
|
rom = "./src/roms/keyboard_sys.rom"
|
||||||
screen = "Window"
|
screen = "Window"
|
||||||
|
|
2
run.sh
2
run.sh
|
@ -8,5 +8,5 @@ fi
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
vasm6502_oldstyle ./src/roms/$1.asm -dotdir -wdc02 -ldots -Fbin -o ./src/roms/$1.rom;
|
vasm6502_oldstyle ./src/roms/$1.asm -dotdir -wdc02 -ldots -Fbin -o ./src/roms/$1.rom;
|
||||||
cargo run --features debug -- rom "./src/roms/$1.rom";
|
cargo run -- rom "./src/roms/$1.rom";
|
||||||
# hexdump -C ./cpu_dump.bin;
|
# hexdump -C ./cpu_dump.bin;
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
use std::{
|
use std::{
|
||||||
default, env,
|
env,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{ErrorKind, Read},
|
io::{ErrorKind, Read},
|
||||||
process::exit,
|
process::exit,
|
||||||
};
|
};
|
||||||
|
|
||||||
use georgeemu::{Config, ScreenType};
|
|
||||||
use serde::{Deserialize, Serialize};
|
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)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
struct ConfigBuilder {
|
struct ConfigBuilder {
|
||||||
rom: Option<String>,
|
rom: Option<String>,
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
mod cli;
|
mod cli;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use cli::get_input;
|
use cli::get_input;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use georgeemu::GeorgeEmu;
|
use georgeemu::GeorgeEmu;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use minifb::{Scale, ScaleMode, Window, WindowOptions};
|
use minifb::{Scale, ScaleMode, Window, WindowOptions};
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut window = Window::new(
|
use std::{fs::File, io::Read};
|
||||||
|
|
||||||
|
let window = Window::new(
|
||||||
"ʕ·ᴥ·ʔ-☆",
|
"ʕ·ᴥ·ʔ-☆",
|
||||||
512,
|
512,
|
||||||
380,
|
380,
|
||||||
|
@ -21,13 +28,22 @@ fn main() {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let config = get_input();
|
let config = get_input();
|
||||||
#[cfg(not(feature = "term"))]
|
let rom = match config.rom {
|
||||||
let mut emu = GeorgeEmu::builder()
|
Some(path) => {
|
||||||
.window(&mut window)
|
let mut file = File::open(path).unwrap();
|
||||||
.rom("/Users/august/projects/george-emu/src/roms/demo.rom")
|
let mut bin = vec![0; 0x8000];
|
||||||
.build();
|
file.read_exact(&mut bin).unwrap();
|
||||||
#[cfg(feature = "term")]
|
// println!("reading char rom");
|
||||||
let mut emu = GeorgeEmu::builder().build();
|
bin.try_into().unwrap()
|
||||||
|
}
|
||||||
|
None => *include_bytes!("../roms/george.rom"),
|
||||||
|
};
|
||||||
|
let mut emu = GeorgeEmu::builder().rom(rom).window(window).build();
|
||||||
|
|
||||||
emu.run();
|
emu.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn main() {}
|
||||||
|
|
40
src/cpu.rs
40
src/cpu.rs
|
@ -2,7 +2,9 @@ use crate::instructions::get_instruction;
|
||||||
use crate::memory::{MemHandle, MemoryReader, MemoryWriter};
|
use crate::memory::{MemHandle, MemoryReader, MemoryWriter};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -20,6 +22,7 @@ pub enum StatusFlag {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CpuController(Sender<CpuControl>);
|
pub struct CpuController(Sender<CpuControl>);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum CpuControl {
|
pub enum CpuControl {
|
||||||
Irq,
|
Irq,
|
||||||
Nmi,
|
Nmi,
|
||||||
|
@ -33,19 +36,19 @@ impl CpuController {
|
||||||
Self(sender)
|
Self(sender)
|
||||||
}
|
}
|
||||||
pub fn irq(&self) {
|
pub fn irq(&self) {
|
||||||
self.0.send(CpuControl::Irq);
|
let _ = self.0.send(CpuControl::Irq);
|
||||||
}
|
}
|
||||||
pub fn nmi(&self) {
|
pub fn nmi(&self) {
|
||||||
self.0.send(CpuControl::Nmi);
|
let _ = self.0.send(CpuControl::Nmi);
|
||||||
}
|
}
|
||||||
pub fn toggle(&self) {
|
pub fn toggle(&self) {
|
||||||
self.0.send(CpuControl::Toggle);
|
let _ = self.0.send(CpuControl::Toggle);
|
||||||
}
|
}
|
||||||
pub fn cycle(&self) {
|
pub fn cycle(&self) {
|
||||||
self.0.send(CpuControl::Cycle);
|
let _ = self.0.send(CpuControl::Cycle);
|
||||||
}
|
}
|
||||||
pub fn data(&self) {
|
pub fn data(&self) {
|
||||||
self.0.send(CpuControl::Data);
|
let _ = self.0.send(CpuControl::Data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +72,7 @@ pub struct Cpu {
|
||||||
pub nmi: bool,
|
pub nmi: bool,
|
||||||
pub memory: MemHandle,
|
pub memory: MemHandle,
|
||||||
pub pending_cycles: usize,
|
pub pending_cycles: usize,
|
||||||
|
pub debug: bool,
|
||||||
receiver: Option<CpuReceiver>,
|
receiver: Option<CpuReceiver>,
|
||||||
stopped: bool,
|
stopped: bool,
|
||||||
cycle: bool,
|
cycle: bool,
|
||||||
|
@ -104,6 +108,7 @@ impl Cpu {
|
||||||
nmi: false,
|
nmi: false,
|
||||||
receiver: None,
|
receiver: None,
|
||||||
memory,
|
memory,
|
||||||
|
debug: false,
|
||||||
stopped: false,
|
stopped: false,
|
||||||
pending_cycles: 0,
|
pending_cycles: 0,
|
||||||
cycle: false, // cycle_count: 0,
|
cycle: false, // cycle_count: 0,
|
||||||
|
@ -230,23 +235,24 @@ impl Cpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cycle(&mut self) {
|
pub fn cycle(&mut self) {
|
||||||
// self.receive_control();
|
self.receive_control();
|
||||||
|
|
||||||
// if self.stopped & !self.cycle {
|
if self.stopped & !self.cycle {
|
||||||
// self.set_flag(StatusFlag::IrqDisable, true);
|
self.set_flag(StatusFlag::IrqDisable, true);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// self.cycle = false;
|
self.cycle = false;
|
||||||
|
|
||||||
|
// can't sleep in wasm
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
while self.pending_cycles != 0 {
|
while self.pending_cycles != 0 {
|
||||||
// roughly cycle-accurate timing
|
// roughly cycle-accurate timing
|
||||||
sleep(Duration::from_nanos(500));
|
sleep(Duration::from_nanos(500));
|
||||||
self.pending_cycles -= 1;
|
self.pending_cycles -= 1;
|
||||||
}
|
}
|
||||||
// if !self.get_flag(StatusFlag::IrqDisable) && self.irq {
|
if !self.get_flag(StatusFlag::IrqDisable) && self.irq {
|
||||||
// self.interrupt();
|
self.interrupt();
|
||||||
// }
|
}
|
||||||
let opcode = self.read(self.pc);
|
let opcode = self.read(self.pc);
|
||||||
let instruction = get_instruction(opcode);
|
let instruction = get_instruction(opcode);
|
||||||
instruction.call(self);
|
instruction.call(self);
|
||||||
|
@ -254,9 +260,9 @@ impl Cpu {
|
||||||
pub fn stop(&mut self) {
|
pub fn stop(&mut self) {
|
||||||
self.stopped = true;
|
self.stopped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
pub fn breakpoint(&mut self) {
|
pub fn breakpoint(&mut self) {
|
||||||
self.stop();
|
if self.debug {
|
||||||
|
self.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
67
src/error.rs
67
src/error.rs
|
@ -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,
|
|
||||||
}
|
|
|
@ -1,7 +1,9 @@
|
||||||
#![allow(clippy::upper_case_acronyms)]
|
#![allow(clippy::upper_case_acronyms)]
|
||||||
|
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use termion::{clear, color, cursor::Goto};
|
use termion::{clear, color, cursor::Goto};
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
use web_sys::console;
|
||||||
|
|
||||||
use crate::cpu::{Cpu, StatusFlag};
|
use crate::cpu::{Cpu, StatusFlag};
|
||||||
use crate::memory::{MemoryReader, MemoryWriter};
|
use crate::memory::{MemoryReader, MemoryWriter};
|
||||||
|
@ -20,8 +22,9 @@ pub struct Instruction<'a> {
|
||||||
|
|
||||||
impl Instruction<'_> {
|
impl Instruction<'_> {
|
||||||
pub fn call(&self, cpu: &mut Cpu) {
|
pub fn call(&self, cpu: &mut Cpu) {
|
||||||
#[cfg(feature = "debug")]
|
if cpu.debug {
|
||||||
self.debug(cpu);
|
self.debug(cpu);
|
||||||
|
}
|
||||||
|
|
||||||
cpu.pc = cpu.pc.wrapping_add(1); // read instruction byte
|
cpu.pc = cpu.pc.wrapping_add(1); // read instruction byte
|
||||||
|
|
||||||
|
@ -43,9 +46,10 @@ impl Instruction<'_> {
|
||||||
} // None for address_fn implies it's implied (lol)/stack
|
} // None for address_fn implies it's implied (lol)/stack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None =>
|
||||||
#[cfg(feature = "debug")]
|
{
|
||||||
{
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
if cpu.debug {
|
||||||
println!(
|
println!(
|
||||||
"{}An invalid instruction was called at {:04x}, with opcode {:02x}",
|
"{}An invalid instruction was called at {:04x}, with opcode {:02x}",
|
||||||
Goto(0, 35),
|
Goto(0, 35),
|
||||||
|
@ -58,60 +62,104 @@ impl Instruction<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
fn debug(&self, cpu: &Cpu) {
|
fn debug(&self, cpu: &Cpu) {
|
||||||
// cpu state
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
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!(
|
// cpu state
|
||||||
"{}instruction: {:?}, pc: {:#04x}",
|
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);
|
||||||
Goto(0, 31),
|
print!(
|
||||||
self.name,
|
"{}instruction: {:?}, pc: {:#04x}",
|
||||||
cpu.pc
|
Goto(0, 31),
|
||||||
);
|
self.name,
|
||||||
|
cpu.pc
|
||||||
|
);
|
||||||
|
|
||||||
// current instruction and addressing mode
|
// current instruction and addressing mode
|
||||||
print!(
|
print!(
|
||||||
"{}{}instruction: {:?}, mode: {:?}",
|
"{}{}instruction: {:?}, mode: {:?}",
|
||||||
Goto(0, 32),
|
Goto(0, 32),
|
||||||
clear::UntilNewline,
|
clear::UntilNewline,
|
||||||
self.name,
|
self.name,
|
||||||
self.addr_mode
|
self.addr_mode
|
||||||
);
|
);
|
||||||
|
|
||||||
// local memory
|
// local memory
|
||||||
print!(
|
print!(
|
||||||
"{}{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {}{}{:02x}{}{} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
|
"{}{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {}{}{:02x}{}{} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
|
||||||
Goto(0, 33),
|
Goto(0, 33),
|
||||||
cpu.read(cpu.pc.wrapping_sub(8)),
|
cpu.read(cpu.pc.wrapping_sub(8)),
|
||||||
cpu.read(cpu.pc.wrapping_sub(7)),
|
cpu.read(cpu.pc.wrapping_sub(7)),
|
||||||
cpu.read(cpu.pc.wrapping_sub(6)),
|
cpu.read(cpu.pc.wrapping_sub(6)),
|
||||||
cpu.read(cpu.pc.wrapping_sub(5)),
|
cpu.read(cpu.pc.wrapping_sub(5)),
|
||||||
cpu.read(cpu.pc.wrapping_sub(4)),
|
cpu.read(cpu.pc.wrapping_sub(4)),
|
||||||
cpu.read(cpu.pc.wrapping_sub(3)),
|
cpu.read(cpu.pc.wrapping_sub(3)),
|
||||||
cpu.read(cpu.pc.wrapping_sub(2)),
|
cpu.read(cpu.pc.wrapping_sub(2)),
|
||||||
cpu.read(cpu.pc.wrapping_sub(1)),
|
cpu.read(cpu.pc.wrapping_sub(1)),
|
||||||
color::Bg(color::Rgb(0xFF, 0xCC, 0x00)),
|
color::Bg(color::Rgb(0xFF, 0xCC, 0x00)),
|
||||||
color::Fg(color::Rgb(0x11, 0x05, 0x00)),
|
color::Fg(color::Rgb(0x11, 0x05, 0x00)),
|
||||||
cpu.read(cpu.pc),
|
cpu.read(cpu.pc),
|
||||||
color::Fg(color::Rgb(0xFF, 0xCC, 0x00)),
|
color::Fg(color::Rgb(0xFF, 0xCC, 0x00)),
|
||||||
color::Bg(color::Rgb(0x11, 0x05, 0x00)),
|
color::Bg(color::Rgb(0x11, 0x05, 0x00)),
|
||||||
cpu.read(cpu.pc.wrapping_add(1)),
|
cpu.read(cpu.pc.wrapping_add(1)),
|
||||||
cpu.read(cpu.pc.wrapping_add(2)),
|
cpu.read(cpu.pc.wrapping_add(2)),
|
||||||
cpu.read(cpu.pc.wrapping_add(3)),
|
cpu.read(cpu.pc.wrapping_add(3)),
|
||||||
cpu.read(cpu.pc.wrapping_add(4)),
|
cpu.read(cpu.pc.wrapping_add(4)),
|
||||||
cpu.read(cpu.pc.wrapping_add(5)),
|
cpu.read(cpu.pc.wrapping_add(5)),
|
||||||
cpu.read(cpu.pc.wrapping_add(6)),
|
cpu.read(cpu.pc.wrapping_add(6)),
|
||||||
cpu.read(cpu.pc.wrapping_add(7)),
|
cpu.read(cpu.pc.wrapping_add(7)),
|
||||||
cpu.read(cpu.pc.wrapping_add(8)),
|
cpu.read(cpu.pc.wrapping_add(8)),
|
||||||
);
|
);
|
||||||
// scratchpad
|
// scratchpad
|
||||||
print!(
|
print!(
|
||||||
"{} {:02x} {:02x} {:02x}",
|
"{} {:02x} {:02x} {:02x}",
|
||||||
Goto(0, 34),
|
Goto(0, 34),
|
||||||
cpu.read(0x200),
|
cpu.read(0x200),
|
||||||
cpu.read(0x201),
|
cpu.read(0x201),
|
||||||
cpu.read(0x202)
|
cpu.read(0x202)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
#[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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,7 +322,6 @@ fn branch(cpu: &mut Cpu, condition: bool, address: u16) {
|
||||||
cpu.pc = cpu.pc.wrapping_add(1);
|
cpu.pc = cpu.pc.wrapping_add(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
fn breakpoint(cpu: &mut Cpu, _address: Option<u16>) {
|
fn breakpoint(cpu: &mut Cpu, _address: Option<u16>) {
|
||||||
cpu.breakpoint()
|
cpu.breakpoint()
|
||||||
}
|
}
|
||||||
|
@ -978,16 +1025,10 @@ pub const OPCODES: [Instruction; 256] = [
|
||||||
addr_mode: "zero_page_indexed_indirect",
|
addr_mode: "zero_page_indexed_indirect",
|
||||||
},
|
},
|
||||||
Instruction {
|
Instruction {
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
instr_fn: Some(breakpoint),
|
instr_fn: Some(breakpoint),
|
||||||
#[cfg(not(feature = "debug"))]
|
|
||||||
instr_fn: None,
|
|
||||||
address_fn: None,
|
address_fn: None,
|
||||||
cycles: 0,
|
cycles: 0,
|
||||||
#[cfg(not(feature = "debug"))]
|
|
||||||
name: "none",
|
name: "none",
|
||||||
#[cfg(feature = "debug")]
|
|
||||||
name: "breakpoint",
|
|
||||||
addr_mode: "implied",
|
addr_mode: "implied",
|
||||||
},
|
},
|
||||||
Instruction {
|
Instruction {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use minifb::InputCallback;
|
use minifb::InputCallback;
|
||||||
use minifb::Key as MKey;
|
use minifb::Key as MKey;
|
||||||
|
|
||||||
#[cfg(feature = "term")]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use termion::event::Key;
|
use termion::event::Key;
|
||||||
|
|
||||||
use crate::memory::{MemHandle, MemoryWriter};
|
use crate::memory::{MemHandle, MemoryWriter};
|
||||||
|
@ -16,6 +16,7 @@ impl Keyboard {
|
||||||
Self { memory }
|
Self { memory }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn clear_keys(&self) {
|
pub fn clear_keys(&self) {
|
||||||
self.memory.write(0x4400, 0x00);
|
self.memory.write(0x4400, 0x00);
|
||||||
self.memory.write(0x4401, 0x00);
|
self.memory.write(0x4401, 0x00);
|
||||||
|
@ -25,7 +26,7 @@ impl Keyboard {
|
||||||
self.memory.write(0x4405, 0x00);
|
self.memory.write(0x4405, 0x00);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "term")]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn read_keys(&self, key: Key) {
|
pub fn read_keys(&self, key: Key) {
|
||||||
let mut row0 = 0;
|
let mut row0 = 0;
|
||||||
let mut row1 = 0;
|
let mut row1 = 0;
|
||||||
|
|
465
src/lib.rs
465
src/lib.rs
|
@ -1,8 +1,8 @@
|
||||||
pub mod cpu;
|
pub mod cpu;
|
||||||
pub mod error;
|
mod instructions;
|
||||||
pub mod instructions;
|
mod keyboard;
|
||||||
pub mod keyboard;
|
mod memory;
|
||||||
pub mod memory;
|
mod platform;
|
||||||
pub mod video;
|
pub mod video;
|
||||||
|
|
||||||
use crate::cpu::Cpu;
|
use crate::cpu::Cpu;
|
||||||
|
@ -10,262 +10,243 @@ use crate::keyboard::Keyboard;
|
||||||
use crate::memory::Mem;
|
use crate::memory::Mem;
|
||||||
use crate::video::Renderer;
|
use crate::video::Renderer;
|
||||||
|
|
||||||
use core::panic;
|
|
||||||
use cpu::CpuController;
|
use cpu::CpuController;
|
||||||
use memory::MemHandle;
|
use memory::MemHandle;
|
||||||
use minifb::Window;
|
use minifb::Window;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Read;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::thread::sleep;
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
#[cfg(any(feature = "term", feature = "debug"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use std::{io::stdout, process::exit};
|
use platform::native as imp;
|
||||||
#[cfg(any(feature = "term", feature = "debug"))]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use termion::{
|
use platform::wasm as imp;
|
||||||
async_stdin, clear,
|
|
||||||
cursor::{self, Goto},
|
|
||||||
event::Key,
|
|
||||||
input::TermRead,
|
|
||||||
raw::IntoRawMode,
|
|
||||||
AsyncReader,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
pub struct GeorgeEmu(imp::GeorgeEmu);
|
||||||
pub enum ScreenType {
|
|
||||||
Terminal,
|
|
||||||
#[default]
|
|
||||||
Window,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Config {
|
impl GeorgeEmu {
|
||||||
pub rom: Option<String>,
|
pub fn builder() -> GeorgeEmuBuilder<NoRom, NoWindow> {
|
||||||
pub screen: ScreenType,
|
|
||||||
pub char_rom: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GeorgeEmuBuilder<'a> {
|
|
||||||
cpu: Cpu,
|
|
||||||
renderer: Option<Renderer>,
|
|
||||||
input: Option<Keyboard>,
|
|
||||||
config: Option<Config>,
|
|
||||||
cpu_controller: CpuController,
|
|
||||||
memory_handle: MemHandle,
|
|
||||||
memory: Mem,
|
|
||||||
window: Option<&'a mut Window>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> GeorgeEmuBuilder<'a> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let mut memory = Mem::new();
|
|
||||||
// TODO: once the memory goes to the handle you can't load a rom,
|
|
||||||
// so the method that loads a rom needs to have the handle uninitialized first,
|
|
||||||
// so make everything here be None and set everything at the end
|
|
||||||
memory.load_bytes(include_bytes!(
|
|
||||||
"/Users/august/projects/george-emu/src/roms/demo.rom"
|
|
||||||
));
|
|
||||||
let memory_handle = MemHandle::new(memory);
|
|
||||||
let cpu_memory = memory_handle.clone();
|
|
||||||
let (cpu, cpu_controller) = Cpu::new_with_control(cpu_memory);
|
|
||||||
let input = None;
|
|
||||||
let config = None;
|
|
||||||
let renderer = None;
|
|
||||||
let window = None;
|
|
||||||
GeorgeEmuBuilder {
|
|
||||||
cpu,
|
|
||||||
cpu_controller,
|
|
||||||
memory,
|
|
||||||
memory_handle,
|
|
||||||
config,
|
|
||||||
renderer,
|
|
||||||
window,
|
|
||||||
input,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn config(mut self, config: Config) -> Self {
|
|
||||||
self.config = Some(config);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a keyboard input to the emulator
|
|
||||||
pub fn keyboard(mut self) -> Self {
|
|
||||||
let input_memory = self.memory_handle.clone();
|
|
||||||
let keyboard = Keyboard::new(input_memory);
|
|
||||||
self.input = Some(keyboard);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a minifb window to the emulator
|
|
||||||
#[cfg(not(feature = "term"))]
|
|
||||||
pub fn window(mut self, window: &'a mut Window) -> Self {
|
|
||||||
let renderer = Renderer::new(self.cpu_controller.clone(), self.memory_handle.clone());
|
|
||||||
self.renderer = Some(renderer);
|
|
||||||
self.window = Some(window);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rom<P>(mut self, path: P) -> Self
|
|
||||||
where
|
|
||||||
P: AsRef<Path> + Debug + Copy,
|
|
||||||
{
|
|
||||||
let rom = match File::open(path) {
|
|
||||||
Ok(file) => {
|
|
||||||
file
|
|
||||||
// let mut bin = vec![0; 0x8000];
|
|
||||||
// file.read_exact(&mut bin).unwrap();
|
|
||||||
// // println!("reading char rom");
|
|
||||||
// &bin
|
|
||||||
}
|
|
||||||
Err(error) => panic!("Couldn't read file at {path:?}: {error:?}"),
|
|
||||||
};
|
|
||||||
self.memory.read_from_bin(rom).unwrap();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(mut self) -> GeorgeEmu<'a> {
|
|
||||||
#[cfg(not(any(feature = "term", feature = "web")))]
|
|
||||||
{
|
|
||||||
match self.window {
|
|
||||||
Some(ref mut window) => {
|
|
||||||
if let Some(ref input) = self.input {
|
|
||||||
window.set_input_callback(Box::new(input.clone()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => panic!(
|
|
||||||
"Tried to call `GeorgeEmuBuilder::build()` before `GeorgeEmuBuilder::window()`!"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
GeorgeEmu::new(self.cpu, self.renderer.unwrap(), self.input, self.window)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "term")]
|
|
||||||
{
|
|
||||||
let renderer = Renderer::new(self.cpu_controller.clone(), self.memory_handle.clone());
|
|
||||||
GeorgeEmu::new(self.cpu, renderer, self.input, self.window)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "web")]
|
|
||||||
{
|
|
||||||
let renderer = Renderer::new(self.cpu_controller.clone(), self.memory_handle.clone());
|
|
||||||
GeorgeEmu::new(self.cpu, renderer, self.input, self.window)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct GeorgeEmu<'a> {
|
|
||||||
pub cpu: Cpu,
|
|
||||||
pub renderer: Renderer,
|
|
||||||
// in the future these might be set with feature flags,
|
|
||||||
// but this is simpler
|
|
||||||
input: Option<Keyboard>,
|
|
||||||
pub window: Option<&'a mut Window>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> GeorgeEmu<'a> {
|
|
||||||
pub fn builder() -> GeorgeEmuBuilder<'a> {
|
|
||||||
GeorgeEmuBuilder::new()
|
GeorgeEmuBuilder::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(
|
pub fn draw(&mut self) {
|
||||||
mut cpu: Cpu,
|
self.0.draw()
|
||||||
renderer: Renderer,
|
}
|
||||||
input: Option<Keyboard>,
|
|
||||||
window: Option<&'a mut Window>,
|
pub fn cycle(&mut self) {
|
||||||
) -> Self {
|
self.0.cycle()
|
||||||
cpu.reset();
|
}
|
||||||
Self {
|
|
||||||
cpu,
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
input,
|
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,
|
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,
|
window,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
/// Enables debug printing and breakpoint triggering
|
||||||
#[cfg(any(feature = "term", feature = "debug"))]
|
pub fn debug(mut self) -> Self {
|
||||||
{
|
self.cpu.as_mut().unwrap().debug = true;
|
||||||
let _ = stdout().into_raw_mode();
|
self
|
||||||
print!("{}{}", cursor::Hide, clear::All,);
|
|
||||||
}
|
|
||||||
#[cfg(any(feature = "term", feature = "debug"))]
|
|
||||||
let mut stdin = async_stdin();
|
|
||||||
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 {
|
|
||||||
#[cfg(any(feature = "term", feature = "debug"))]
|
|
||||||
self.handle_input(&mut stdin);
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn draw(&mut self) {
|
pub fn build(self) -> GeorgeEmu {
|
||||||
#[cfg(feature = "term")]
|
let keyboard = Keyboard::new(self.memory_handle.clone().unwrap());
|
||||||
self.renderer.render();
|
GeorgeEmu(imp::GeorgeEmu::new(
|
||||||
#[cfg(not(feature = "term"))]
|
self.cpu.unwrap(),
|
||||||
match self.window {
|
self.renderer.unwrap(),
|
||||||
Some(ref mut window) => self.renderer.render(window),
|
Some(keyboard),
|
||||||
None => todo!(),
|
None,
|
||||||
}
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn draw(&self, window: &mut Window) {
|
pub fn build(self) -> GeorgeEmu {
|
||||||
#[cfg(feature = "term")]
|
GeorgeEmu(imp::GeorgeEmu::new(
|
||||||
self.renderer.render();
|
self.cpu.unwrap(),
|
||||||
#[cfg(not(feature = "term"))]
|
self.renderer.unwrap(),
|
||||||
self.renderer.render(window);
|
self.window.0,
|
||||||
}
|
))
|
||||||
|
}
|
||||||
#[cfg(any(feature = "term", feature = "debug"))]
|
}
|
||||||
fn handle_input(&mut self, stdin: &mut AsyncReader) {
|
|
||||||
match &self.input {
|
impl GeorgeEmuBuilder<SomeRom, SomeWindow> {
|
||||||
Some(input) => {
|
/// Enables debug printing and breakpoint triggering
|
||||||
let mut stdin = stdin.keys();
|
pub fn debug(mut self) -> Self {
|
||||||
if let Some(Ok(key)) = stdin.next() {
|
self.cpu.as_mut().unwrap().debug = true;
|
||||||
match key {
|
self
|
||||||
Key::Char('q') => {
|
}
|
||||||
print!("{}{}{}", clear::All, cursor::Show, Goto(1, 1));
|
|
||||||
exit(0);
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
}
|
pub fn build(self) -> GeorgeEmu {
|
||||||
Key::Char('`') => self.cpu.toggle_stopped(),
|
GeorgeEmu(imp::GeorgeEmu::new(
|
||||||
Key::Char('\n') => self.cpu.cycle(),
|
self.cpu.unwrap(),
|
||||||
Key::Char('i') => self.cpu.interrupt(),
|
self.renderer.unwrap(),
|
||||||
_ => {
|
None,
|
||||||
#[cfg(feature = "term")]
|
Some(self.window.0),
|
||||||
input.read_keys(key);
|
))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[cfg(feature = "term")]
|
pub fn build(self) -> GeorgeEmu {
|
||||||
input.clear_keys();
|
GeorgeEmu(imp::GeorgeEmu::new(
|
||||||
}
|
self.cpu.unwrap(),
|
||||||
}
|
self.renderer.unwrap(),
|
||||||
None => {}
|
self.window.0,
|
||||||
}
|
))
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cycle(&mut self) {
|
|
||||||
self.cpu.cycle()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::panic;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
@ -46,21 +45,21 @@ pub trait MemoryWriter {
|
||||||
// 0x10000 elements
|
// 0x10000 elements
|
||||||
pub struct Mem([u8; 0x10000]);
|
pub struct Mem([u8; 0x10000]);
|
||||||
|
|
||||||
// impl Default for Mem {
|
impl Default for Mem {
|
||||||
// fn default() -> Self {
|
fn default() -> Self {
|
||||||
// let bytes = include_bytes!("/Users/august/projects/george-emu/src/roms/george.rom");
|
let bytes = include_bytes!("./roms/george.rom");
|
||||||
// let padding = [0; 0x8000];
|
let padding = [0; 0x8000];
|
||||||
// let rom: [u8; 0x10000] = {
|
let mem: [u8; 0x10000] = {
|
||||||
// let mut rom: [u8; 0x10000] = [0; 0x10000];
|
let mut rom: [u8; 0x10000] = [0; 0x10000];
|
||||||
// let (one, two) = rom.split_at_mut(padding.len());
|
let (one, two) = rom.split_at_mut(padding.len());
|
||||||
// one.copy_from_slice(&padding);
|
one.copy_from_slice(&padding);
|
||||||
// two.copy_from_slice(bytes);
|
two.copy_from_slice(bytes);
|
||||||
// rom
|
rom
|
||||||
// };
|
};
|
||||||
|
|
||||||
// Self(rom)
|
Self(mem)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
impl Mem {
|
impl Mem {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
@ -105,7 +104,7 @@ impl Mem {
|
||||||
println!("{:02x}", self.read(address));
|
println!("{:02x}", self.read(address));
|
||||||
}
|
}
|
||||||
pub fn load_bytes(&mut self, bytes: &[u8]) {
|
pub fn load_bytes(&mut self, bytes: &[u8]) {
|
||||||
for (address, byte) in bytes.into_iter().enumerate() {
|
for (address, byte) in bytes.iter().enumerate() {
|
||||||
self.write(address as u16 + 0x8000, *byte)
|
self.write(address as u16 + 0x8000, *byte)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub mod native;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub mod wasm;
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,116 +1,230 @@
|
||||||
; .setcpu "65C02"
|
; .setcpu "65C02"
|
||||||
.include "./macro.inc"
|
.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
|
.org $8000
|
||||||
|
|
||||||
n = $01 ; temporary storage for data stack operations
|
n = $01 ; temporary storage for data stack operations
|
||||||
|
|
||||||
key_row = $200 ; used for character lookup when key pressed
|
temp = $20 ; scratchpad page
|
||||||
key_col = $201
|
|
||||||
cursor = $202
|
|
||||||
|
|
||||||
char_buffer = $300 ; 256 byte character buffer
|
cursor = $300
|
||||||
|
cursor_x = cursor
|
||||||
|
cursor_y = cursor + 1
|
||||||
|
|
||||||
kb_row = $4400 ; keyboard hardware register
|
rand_index = $200
|
||||||
kb_row_cache = $203 ; cache
|
|
||||||
|
|
||||||
.org $8000
|
|
||||||
|
|
||||||
reset:
|
reset:
|
||||||
sei
|
sei
|
||||||
ldx #0; initialize data stack pointer
|
ldx #0; initialize data stack pointer
|
||||||
|
|
||||||
; initdisplay:
|
init:
|
||||||
; lda #0
|
cli
|
||||||
; 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:
|
main:
|
||||||
jsr draw
|
jsr print
|
||||||
|
jsr rand_draw
|
||||||
jmp main
|
jmp main
|
||||||
|
|
||||||
draw:
|
newline: ; sets cursor to start of next line
|
||||||
char = $200
|
stz cursor_x
|
||||||
x = $201
|
lda cursor_y
|
||||||
y = $202
|
cmp #28
|
||||||
.y_loop:
|
bne .end
|
||||||
.x_loop:
|
stz cursor_y
|
||||||
inc char
|
rts
|
||||||
lda x
|
|
||||||
push
|
|
||||||
sta 0, x
|
|
||||||
stz 1, x
|
|
||||||
lda y
|
|
||||||
push
|
|
||||||
sta 0, x
|
|
||||||
sta 1, x
|
|
||||||
lda char
|
|
||||||
push
|
|
||||||
sta 0, x
|
|
||||||
sta 1, x
|
|
||||||
jsr draw_char
|
|
||||||
inc x
|
|
||||||
lda #64
|
|
||||||
cmp x
|
|
||||||
bne .x_loop
|
|
||||||
inc y
|
|
||||||
lda #30
|
|
||||||
cmp y
|
|
||||||
bne .y_loop
|
|
||||||
.end:
|
.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
|
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
|
; prints string from cursor position, stopping at end of string or at 256 chars, whichever comes first
|
||||||
pop ; and pop it off the stack
|
; $6000 + (64*Y) + X
|
||||||
jsr get_char_address ; calculate where to put the character in memory
|
; THIS WILL WRITE OUT OF BOUNDS IF THE CURSOR IS OUT OF BOUNDS/STRING IS TOO LONG
|
||||||
sta (0, x) ; store a at the address pointed to on the stack
|
|
||||||
|
; 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
|
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
|
rand_draw:
|
||||||
;.byte $00
|
ldx rand_index
|
||||||
;.byte $60
|
lda random_x, x
|
||||||
lda #$60
|
sta cursor_x
|
||||||
push
|
lda random_y, x
|
||||||
sta 1, x
|
sta cursor_y
|
||||||
stz 0, x
|
jsr cursor_addr
|
||||||
jsr plus ; add vram start address to result
|
inc rand_index
|
||||||
|
|
||||||
pla
|
|
||||||
rts
|
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
|
isr: ; interrupt service routine
|
||||||
pha
|
pha
|
||||||
phx
|
phx
|
||||||
phy
|
phy
|
||||||
; jsr keyboard
|
; jsr irq
|
||||||
ply
|
ply
|
||||||
plx
|
plx
|
||||||
pla
|
pla
|
||||||
|
|
Binary file not shown.
175
src/video.rs
175
src/video.rs
|
@ -1,6 +1,7 @@
|
||||||
use minifb::{Scale, ScaleMode, Window, WindowOptions};
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use serde::{Deserialize, Serialize};
|
use std::io::{self, Write};
|
||||||
#[cfg(feature = "term")]
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use termion::{
|
use termion::{
|
||||||
color::{self, Bg, Fg},
|
color::{self, Bg, Fg},
|
||||||
cursor::Goto,
|
cursor::Goto,
|
||||||
|
@ -10,16 +11,19 @@ use crate::{
|
||||||
cpu::CpuController,
|
cpu::CpuController,
|
||||||
memory::{MemHandle, MemoryReader},
|
memory::{MemHandle, MemoryReader},
|
||||||
};
|
};
|
||||||
use std::{
|
use minifb::Window;
|
||||||
fs::File,
|
|
||||||
io::{self, Read, Write},
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
// const FG_COLOR: u32 = 0xFFCC00;
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
// const BG_COLOR: u32 = 0x110500;
|
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;
|
const FG_COLOR: u32 = 0xFF00CCFF;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
const BG_COLOR: u32 = 0xFF000511;
|
const BG_COLOR: u32 = 0xFF000511;
|
||||||
|
|
||||||
const WIDTH: usize = 512;
|
const WIDTH: usize = 512;
|
||||||
const HEIGHT: usize = 380;
|
const HEIGHT: usize = 380;
|
||||||
|
|
||||||
|
@ -39,72 +43,10 @@ const HEIGHT: usize = 380;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[cfg(not(feature = "term"))]
|
const CHAR_ROM: &[u8; 0x8000] = include_bytes!("./roms/cozette.rom");
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Renderer {
|
|
||||||
char_rom: [u8; 0x8000],
|
|
||||||
memory: MemHandle,
|
|
||||||
controller: CpuController,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "term"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
impl Renderer {
|
const ASCII_LOOKUP: [&str; 256] = [
|
||||||
// pub fn new<P>(controller: CpuController, memory: MemHandle, char_rom: Option<P>) -> Self
|
|
||||||
// where
|
|
||||||
// P: AsRef<Path>,
|
|
||||||
pub fn new(controller: CpuController, memory: MemHandle) -> Self {
|
|
||||||
// let char_rom = get_char_bin(char_rom);
|
|
||||||
let char_rom = *include_bytes!("./roms/cozette.rom");
|
|
||||||
Self {
|
|
||||||
memory,
|
|
||||||
char_rom,
|
|
||||||
controller,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(&self, window: &mut 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; 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.controller.irq();
|
|
||||||
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MemoryReader for Renderer {
|
|
||||||
fn read(&self, address: u16) -> u8 {
|
|
||||||
self.memory.read(address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "term")]
|
|
||||||
pub struct Renderer {
|
|
||||||
memory: MemHandle,
|
|
||||||
controller: CpuController,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "term")]
|
|
||||||
const ASCII_LOOPUP: [&str; 256] = [
|
|
||||||
" ", "░", "▒", "▓", "♡", "♥", "⭐", "✭", "", "✦", "✨", "♀", "♂", "⚢", "⚣", "⚥", "♩", "♪",
|
" ", "░", "▒", "▓", "♡", "♥", "⭐", "✭", "", "✦", "✨", "♀", "♂", "⚢", "⚣", "⚥", "♩", "♪",
|
||||||
"♫", "♬", "ﱝ", "", "", "", "奄", "奔", "婢", "ﱜ", "ﱛ", "", "", "", " ", "!", "\"", "#",
|
"♫", "♬", "ﱝ", "", "", "", "奄", "奔", "婢", "ﱜ", "ﱛ", "", "", "", " ", "!", "\"", "#",
|
||||||
"$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6",
|
"$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6",
|
||||||
|
@ -121,30 +63,75 @@ const ASCII_LOOPUP: [&str; 256] = [
|
||||||
"", "", "", "", "🎁", "", "", "", "", "⚐", "⚑", "", "", "",
|
"", "", "", "", "🎁", "", "", "", "", "⚐", "⚑", "", "", "",
|
||||||
];
|
];
|
||||||
|
|
||||||
#[cfg(feature = "term")]
|
#[derive(Debug)]
|
||||||
|
pub struct Renderer {
|
||||||
|
memory: MemHandle,
|
||||||
|
controller: CpuController,
|
||||||
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
pub fn new(controller: CpuController, memory: MemHandle) -> Self {
|
pub fn new(controller: CpuController, memory: MemHandle) -> Self {
|
||||||
Self { controller, memory }
|
Self { memory, controller }
|
||||||
}
|
}
|
||||||
pub fn render(&self) {
|
|
||||||
let mut stdout = io::stdout();
|
pub fn render(&self, window: Option<&mut Window>) {
|
||||||
let mut i = 0;
|
match window {
|
||||||
for char_row in 0..29 {
|
Some(window) => {
|
||||||
for char_col in 0..64 {
|
// the rest of this function is arcane wizardry
|
||||||
let ascii = self.read(0x6000 + i);
|
// based on the specifics of george's weird
|
||||||
i += 1;
|
// display and characters... don't fuck around w it
|
||||||
let char = ASCII_LOOPUP[ascii as usize];
|
let mut i = 0;
|
||||||
let _ = write!(
|
let mut buffer = [0; WIDTH * HEIGHT];
|
||||||
// FG_COLOR = 0xFFCC00
|
for char_row in 0..29 {
|
||||||
// BG_COLOR = 0x110500
|
for char_col in 0..64 {
|
||||||
stdout,
|
let ascii = self.read(0x6000 + i);
|
||||||
"{}{}{}{char}",
|
i += 1;
|
||||||
Goto(char_col + 1, char_row + 1),
|
for row in 0..13 {
|
||||||
Fg(color::Rgb(0xFF, 0xCC, 0x00)),
|
let byte = CHAR_ROM[ascii as usize + (row * 0x100)];
|
||||||
Bg(color::Rgb(0x11, 0x05, 0x00))
|
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();
|
self.controller.irq();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MemoryReader for Renderer {
|
||||||
|
fn read(&self, address: u16) -> u8 {
|
||||||
|
self.memory.read(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue