diff --git a/Cargo.lock b/Cargo.lock index c2128e7..f332bee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,6 +190,7 @@ dependencies = [ "serde", "termion", "toml", + "web-sys", ] [[package]] @@ -228,9 +229,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -298,6 +299,7 @@ dependencies = [ [[package]] name = "minifb" version = "0.27.0" +source = "git+https://github.com/augustkline/rust_minifb#2e2fdcf1d692c8c3d827a221a66569d81c73f99a" dependencies = [ "cc", "console_error_panic_hook", @@ -602,11 +604,12 @@ checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" [[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", @@ -614,9 +617,9 @@ dependencies = [ [[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", @@ -641,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", @@ -651,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", @@ -664,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" @@ -743,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", diff --git a/Cargo.toml b/Cargo.toml index 06d2cee..c481ef5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,16 +3,10 @@ name = "georgeemu" version = "0.1.0" edition = "2021" -[features] -debug = [] -term = [] -web = ["minifb/web"] - [[bin]] path = "src/bin/main.rs" name = "georgeemu" - [target.'cfg(target_arch = "wasm32")'.lib] crate-type = ["cdylib", "rlib"] @@ -20,11 +14,15 @@ crate-type = ["cdylib", "rlib"] [dependencies] anyhow = "1.0.81" -console_error_panic_hook = "0.1.7" -# minifb = { git = "https://github.com/augustkline/rust_minifb" } -minifb = { path = "/Users/august/projects/rust_minifb_fork/" } +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" diff --git a/george.toml b/george.toml index f6f9271..d34e324 100644 --- a/george.toml +++ b/george.toml @@ -1,3 +1,3 @@ char_rom = "./src/roms/cozette.rom" -rom = "./src/roms/demo.rom" +rom = "./src/roms/keyboard_sys.rom" screen = "Window" diff --git a/run.sh b/run.sh index 74b1cb2..56d858c 100755 --- a/run.sh +++ b/run.sh @@ -8,5 +8,5 @@ fi set -e 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; diff --git a/src/bin/cli/mod.rs b/src/bin/cli/mod.rs index 977823a..8a50f65 100644 --- a/src/bin/cli/mod.rs +++ b/src/bin/cli/mod.rs @@ -1,13 +1,25 @@ use std::{ - default, env, + env, fs::File, io::{ErrorKind, Read}, process::exit, }; -use georgeemu::{Config, ScreenType}; use serde::{Deserialize, Serialize}; +#[derive(Serialize, Deserialize, Default, Debug)] +pub enum ScreenType { + Terminal, + #[default] + Window, +} + +pub struct Config { + pub rom: Option, + pub screen: ScreenType, + pub char_rom: Option, +} + #[derive(Serialize, Deserialize, Debug)] struct ConfigBuilder { rom: Option, diff --git a/src/bin/main.rs b/src/bin/main.rs index 74df6ef..b59c455 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,11 +1,18 @@ +#[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() { - let mut window = Window::new( + use std::{fs::File, io::Read}; + + let window = Window::new( "ʕ·ᴥ·ʔ-☆", 512, 380, @@ -21,13 +28,22 @@ fn main() { }, ) .unwrap(); + let config = get_input(); - #[cfg(not(feature = "term"))] - let mut emu = GeorgeEmu::builder() - .window(&mut window) - .rom("/Users/august/projects/george-emu/src/roms/demo.rom") - .build(); - #[cfg(feature = "term")] - let mut emu = GeorgeEmu::builder().build(); + 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() {} diff --git a/src/cpu.rs b/src/cpu.rs index 145a823..1276691 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -2,7 +2,9 @@ use crate::instructions::get_instruction; use crate::memory::{MemHandle, MemoryReader, MemoryWriter}; use std::fmt::Display; use std::sync::mpsc::{channel, Receiver, Sender}; +#[cfg(not(target_arch = "wasm32"))] use std::thread::sleep; +#[cfg(not(target_arch = "wasm32"))] use std::time::Duration; #[derive(Clone, Copy)] @@ -20,6 +22,7 @@ pub enum StatusFlag { #[derive(Clone, Debug)] pub struct CpuController(Sender); +#[derive(Clone, Copy)] pub enum CpuControl { Irq, Nmi, @@ -33,19 +36,19 @@ 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); } } @@ -69,6 +72,7 @@ pub struct Cpu { pub nmi: bool, pub memory: MemHandle, pub pending_cycles: usize, + pub debug: bool, receiver: Option, stopped: bool, cycle: bool, @@ -104,6 +108,7 @@ impl Cpu { nmi: false, receiver: None, memory, + debug: false, stopped: false, pending_cycles: 0, cycle: false, // cycle_count: 0, @@ -230,23 +235,24 @@ impl Cpu { } pub fn cycle(&mut self) { - // self.receive_control(); + self.receive_control(); - // if self.stopped & !self.cycle { - // self.set_flag(StatusFlag::IrqDisable, true); - // return; - // } - // self.cycle = false; + if self.stopped & !self.cycle { + self.set_flag(StatusFlag::IrqDisable, true); + return; + } + self.cycle = false; + // can't sleep in wasm #[cfg(not(target_arch = "wasm32"))] while self.pending_cycles != 0 { // roughly cycle-accurate timing sleep(Duration::from_nanos(500)); self.pending_cycles -= 1; } - // if !self.get_flag(StatusFlag::IrqDisable) && self.irq { - // self.interrupt(); - // } + if !self.get_flag(StatusFlag::IrqDisable) && self.irq { + self.interrupt(); + } let opcode = self.read(self.pc); let instruction = get_instruction(opcode); instruction.call(self); @@ -254,9 +260,9 @@ impl Cpu { pub fn stop(&mut self) { self.stopped = true; } - - #[cfg(feature = "debug")] pub fn breakpoint(&mut self) { - self.stop(); + if self.debug { + self.stop(); + } } } diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index ed6fdac..0000000 --- a/src/error.rs +++ /dev/null @@ -1,67 +0,0 @@ -#[derive(Debug)] -pub enum GeorgeErrorKind { - Memory(MemoryError), - Execution(ExecutionError), - AddrMode(AddressingModeError), - Mapping(MappingError), -} - -impl From for GeorgeErrorKind { - fn from(value: MemoryError) -> Self { - Self::Memory(value) - } -} -impl From for GeorgeErrorKind { - fn from(value: AddressingModeError) -> Self { - Self::AddrMode(value) - } -} -impl From 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 for AddressingModeError { - fn from(_value: ExecutionError) -> Self { - Self::IncompatibleAddrMode - } -} - -impl From 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, -} diff --git a/src/instructions.rs b/src/instructions.rs index edbde25..0250fea 100644 --- a/src/instructions.rs +++ b/src/instructions.rs @@ -1,7 +1,9 @@ #![allow(clippy::upper_case_acronyms)] -#[cfg(feature = "debug")] +#[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}; @@ -20,8 +22,9 @@ pub struct Instruction<'a> { impl Instruction<'_> { pub fn call(&self, cpu: &mut Cpu) { - #[cfg(feature = "debug")] - self.debug(cpu); + if cpu.debug { + self.debug(cpu); + } 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 => { - #[cfg(feature = "debug")] - { + None => + { + #[cfg(not(target_arch = "wasm32"))] + if cpu.debug { println!( "{}An invalid instruction was called at {:04x}, with opcode {:02x}", Goto(0, 35), @@ -58,60 +62,104 @@ impl Instruction<'_> { } } - #[cfg(feature = "debug")] fn debug(&self, cpu: &Cpu) { - // 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 - ); + #[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 - ); + // 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) - ) + // 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) + ) + } + #[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); } -#[cfg(feature = "debug")] fn breakpoint(cpu: &mut Cpu, _address: Option) { cpu.breakpoint() } @@ -978,16 +1025,10 @@ pub const OPCODES: [Instruction; 256] = [ addr_mode: "zero_page_indexed_indirect", }, Instruction { - #[cfg(feature = "debug")] instr_fn: Some(breakpoint), - #[cfg(not(feature = "debug"))] - instr_fn: None, address_fn: None, cycles: 0, - #[cfg(not(feature = "debug"))] name: "none", - #[cfg(feature = "debug")] - name: "breakpoint", addr_mode: "implied", }, Instruction { diff --git a/src/keyboard.rs b/src/keyboard.rs index f17509b..b488e99 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -1,7 +1,7 @@ use minifb::InputCallback; use minifb::Key as MKey; -#[cfg(feature = "term")] +#[cfg(not(target_arch = "wasm32"))] use termion::event::Key; use crate::memory::{MemHandle, MemoryWriter}; @@ -16,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); @@ -25,7 +26,7 @@ impl Keyboard { self.memory.write(0x4405, 0x00); } - #[cfg(feature = "term")] + #[cfg(not(target_arch = "wasm32"))] pub fn read_keys(&self, key: Key) { let mut row0 = 0; let mut row1 = 0; diff --git a/src/lib.rs b/src/lib.rs index c5ba450..e37b4c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ pub mod cpu; -pub mod error; -pub mod instructions; -pub mod keyboard; -pub mod memory; +mod instructions; +mod keyboard; +mod memory; +mod platform; pub mod video; use crate::cpu::Cpu; @@ -10,262 +10,243 @@ use crate::keyboard::Keyboard; use crate::memory::Mem; use crate::video::Renderer; -use core::panic; use cpu::CpuController; use memory::MemHandle; 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"))] -use std::{io::stdout, process::exit}; -#[cfg(any(feature = "term", feature = "debug"))] -use termion::{ - async_stdin, clear, - cursor::{self, Goto}, - event::Key, - input::TermRead, - raw::IntoRawMode, - AsyncReader, -}; +#[cfg(not(target_arch = "wasm32"))] +use platform::native as imp; +#[cfg(target_arch = "wasm32")] +use platform::wasm as imp; -#[derive(Serialize, Deserialize, Default, Debug)] -pub enum ScreenType { - Terminal, - #[default] - Window, -} +pub struct GeorgeEmu(imp::GeorgeEmu); -pub struct Config { - pub rom: Option, - pub screen: ScreenType, - pub char_rom: Option, -} - -pub struct GeorgeEmuBuilder<'a> { - cpu: Cpu, - renderer: Option, - input: Option, - config: Option, - 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

(mut self, path: P) -> Self - where - P: AsRef + 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, - pub window: Option<&'a mut Window>, -} - -impl<'a> GeorgeEmu<'a> { - pub fn builder() -> GeorgeEmuBuilder<'a> { +impl GeorgeEmu { + pub fn builder() -> GeorgeEmuBuilder { GeorgeEmuBuilder::new() } - fn new( - mut cpu: Cpu, - renderer: Renderer, - input: Option, - window: Option<&'a mut Window>, - ) -> Self { - cpu.reset(); - Self { - cpu, - input, + 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: Rom, + cpu: Option, + keyboard: Option, + renderer: Option, + cpu_controller: Option, + memory_handle: Option, + window: Window, +} + +impl GeorgeEmuBuilder { + 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 { + 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 { + // 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 { + /// Adds a minifb window to the emulator + pub fn window(self, mut window: Window) -> GeorgeEmuBuilder { + 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 { + pub fn rom(self, rom: [u8; 0x8000]) -> GeorgeEmuBuilder { + 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 run(&mut self) { - #[cfg(any(feature = "term", feature = "debug"))] - { - let _ = stdout().into_raw_mode(); - 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) - } + /// 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"))] - fn draw(&mut self) { - #[cfg(feature = "term")] - self.renderer.render(); - #[cfg(not(feature = "term"))] - match self.window { - Some(ref mut window) => self.renderer.render(window), - None => todo!(), - } + 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 draw(&self, window: &mut Window) { - #[cfg(feature = "term")] - self.renderer.render(); - #[cfg(not(feature = "term"))] - self.renderer.render(window); - } - - #[cfg(any(feature = "term", feature = "debug"))] - fn handle_input(&mut self, stdin: &mut AsyncReader) { - match &self.input { - Some(input) => { - let mut stdin = 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(), - _ => { - #[cfg(feature = "term")] - input.read_keys(key); - } - } - } else { - #[cfg(feature = "term")] - input.clear_keys(); - } - } - None => {} - } - } - - pub fn cycle(&mut self) { - self.cpu.cycle() + pub fn build(self) -> GeorgeEmu { + GeorgeEmu(imp::GeorgeEmu::new( + self.cpu.unwrap(), + self.renderer.unwrap(), + self.window.0, + )) + } +} + +impl GeorgeEmuBuilder { + /// 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, + )) } } diff --git a/src/memory.rs b/src/memory.rs index 68314c3..678abd5 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,6 +1,5 @@ use anyhow::{bail, Result}; use std::io::{self, Write}; -use std::panic; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -46,21 +45,21 @@ pub trait MemoryWriter { // 0x10000 elements pub struct Mem([u8; 0x10000]); -// impl Default for Mem { -// fn default() -> Self { -// let bytes = include_bytes!("/Users/august/projects/george-emu/src/roms/george.rom"); -// let padding = [0; 0x8000]; -// let rom: [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 -// }; +impl Default for Mem { + fn default() -> Self { + 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(rom) -// } -// } + Self(mem) + } +} impl Mem { pub fn new() -> Self { @@ -105,7 +104,7 @@ impl Mem { println!("{:02x}", self.read(address)); } 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) } } diff --git a/src/platform/mod.rs b/src/platform/mod.rs new file mode 100644 index 0000000..3a59b69 --- /dev/null +++ b/src/platform/mod.rs @@ -0,0 +1,4 @@ +#[cfg(not(target_arch = "wasm32"))] +pub mod native; +#[cfg(target_arch = "wasm32")] +pub mod wasm; diff --git a/src/platform/native.rs b/src/platform/native.rs new file mode 100644 index 0000000..ab3a7ca --- /dev/null +++ b/src/platform/native.rs @@ -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, + window: Option, +} + +impl GeorgeEmu { + pub fn new( + mut cpu: Cpu, + renderer: Renderer, + input: Option, + window: Option, + ) -> 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(); + } + } +} diff --git a/src/platform/wasm.rs b/src/platform/wasm.rs new file mode 100644 index 0000000..cbedfb0 --- /dev/null +++ b/src/platform/wasm.rs @@ -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() + } +} diff --git a/src/roms/demo.asm b/src/roms/demo.asm index dcd9933..824dbfd 100644 --- a/src/roms/demo.asm +++ b/src/roms/demo.asm @@ -1,116 +1,230 @@ ; .setcpu "65C02" .include "./macro.inc" -; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates + .org $8000 + n = $01 ; temporary storage for data stack operations -key_row = $200 ; used for character lookup when key pressed -key_col = $201 -cursor = $202 +temp = $20 ; scratchpad page -char_buffer = $300 ; 256 byte character buffer +cursor = $300 +cursor_x = cursor +cursor_y = cursor + 1 -kb_row = $4400 ; keyboard hardware register -kb_row_cache = $203 ; cache - - .org $8000 +rand_index = $200 reset: sei ldx #0; initialize data stack pointer -; initdisplay: -; lda #0 -; ldy #0 - -; cleardisplay: -; sta $6000,y -; sta $6100,y -; sta $6200,y -; sta $6300,y -; sta $6400,y -; sta $6500,y -; sta $6600,y -; sta $6700,y ; this goes slightly over but it's fine -; iny -; bne cleardisplay -; cli +init: + cli main: - jsr draw + jsr print + jsr rand_draw jmp main -draw: -char = $200 -x = $201 -y = $202 - .y_loop: - .x_loop: - inc char - 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 +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 -draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- ) - lda 0, x ; load a with character to draw - pop ; and pop it off the stack - jsr get_char_address ; calculate where to put the character in memory - sta (0, x) ; store a at the address pointed to on the stack + +; 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 -get_char_address: ; gets vram address for a character at (x, y), - ; (n1: x n2: y -- n: $6000 + x + (64 * y)) - ;jsr push_lit ; push 64 onto stack, low byte first - ;.byte 64 - ;.byte 0 - pha - lda #64 - push ; doing this instead until `push_lit` is fixed - sta 0, x - stz 1, x - jsr mult ; multiply 64 with y (n2) - jsr plus ; add result with x (n1) - - ;jsr push_lit ; push vram address onto the stack - ;.byte $00 - ;.byte $60 - lda #$60 - push - sta 1, x - stz 0, x - jsr plus ; add vram start address to result - - pla + +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 - -fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- ) - jsr get_char_address + isr: ; interrupt service routine pha phx phy - ; jsr keyboard + ; jsr irq ply plx pla diff --git a/src/roms/demo.rom b/src/roms/demo.rom index 8a65037..747b7a3 100644 Binary files a/src/roms/demo.rom and b/src/roms/demo.rom differ diff --git a/src/video.rs b/src/video.rs index 13dd908..26867da 100644 --- a/src/video.rs +++ b/src/video.rs @@ -1,6 +1,7 @@ -use minifb::{Scale, ScaleMode, Window, WindowOptions}; -use serde::{Deserialize, Serialize}; -#[cfg(feature = "term")] +#[cfg(not(target_arch = "wasm32"))] +use std::io::{self, Write}; + +#[cfg(not(target_arch = "wasm32"))] use termion::{ color::{self, Bg, Fg}, cursor::Goto, @@ -10,16 +11,19 @@ use crate::{ cpu::CpuController, memory::{MemHandle, MemoryReader}, }; -use std::{ - fs::File, - io::{self, Read, Write}, - path::Path, -}; +use minifb::Window; -// const FG_COLOR: u32 = 0xFFCC00; -// const BG_COLOR: u32 = 0x110500; +#[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; @@ -39,72 +43,10 @@ const HEIGHT: usize = 380; // } // } -#[cfg(not(feature = "term"))] -#[derive(Debug)] -pub struct Renderer { - char_rom: [u8; 0x8000], - memory: MemHandle, - controller: CpuController, -} +const CHAR_ROM: &[u8; 0x8000] = include_bytes!("./roms/cozette.rom"); -#[cfg(not(feature = "term"))] -impl Renderer { - // pub fn new

(controller: CpuController, memory: MemHandle, char_rom: Option

) -> Self - // where - // P: AsRef, - 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] = [ +#[cfg(not(target_arch = "wasm32"))] +const ASCII_LOOKUP: [&str; 256] = [ " ", "░", "▒", "▓", "♡", "♥", "⭐", "✭", "", "✦", "✨", "♀", "♂", "⚢", "⚣", "⚥", "♩", "♪", "♫", "♬", "ﱝ", "", "", "", "奄", "奔", "婢", "ﱜ", "ﱛ", "", "", "", " ", "!", "\"", "#", "$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "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 { pub fn new(controller: CpuController, memory: MemHandle) -> Self { - Self { controller, memory } + Self { memory, controller } } - pub fn render(&self) { - 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 _ = 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)) - ); + + pub fn render(&self, window: Option<&mut Window>) { + match window { + Some(window) => { + // the rest of this function is arcane wizardry + // based on the specifics of george's weird + // display and characters... don't fuck around w it + let mut i = 0; + let mut buffer = [0; WIDTH * HEIGHT]; + for char_row in 0..29 { + for char_col in 0..64 { + let ascii = self.read(0x6000 + i); + i += 1; + for row in 0..13 { + let byte = CHAR_ROM[ascii as usize + (row * 0x100)]; + for bit_index in (0..8).rev() { + let buffer_index = + ((char_row) * 13 + (row)) * 512 + (char_col * 8 + bit_index); + if (byte << bit_index) & 0x80 == 0x80 { + buffer[buffer_index] = FG_COLOR; + } else { + buffer[buffer_index] = BG_COLOR; + } + } + } + } + } + window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap(); + } + None => { + #[cfg(not(target_arch = "wasm32"))] + { + let mut stdout = io::stdout(); + let mut i = 0; + for char_row in 0..29 { + for char_col in 0..64 { + let ascii = self.read(0x6000 + i); + i += 1; + let char = ASCII_LOOKUP[ascii as usize]; + let _ = write!( + // FG_COLOR = 0xFFCC00 + // BG_COLOR = 0x110500 + stdout, + "{}{}{}{char}", + Goto(char_col + 1, char_row + 1), + Fg(color::Rgb(0xFF, 0xCC, 0x00)), + Bg(color::Rgb(0x11, 0x05, 0x00)) + ); + } + } + } } } self.controller.irq(); } } + +impl MemoryReader for Renderer { + fn read(&self, address: u16) -> u8 { + self.memory.read(address) + } +}