Big refactor! Better handling of wasm/native targets, and cute demo :)

This commit is contained in:
august kline 2024-09-07 22:24:51 -04:00
parent 8ac0cbc57b
commit 952b79cf91
18 changed files with 848 additions and 623 deletions

31
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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
View File

@ -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;

View File

@ -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>,

View File

@ -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() {}

View File

@ -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) {
if self.debug {
self.stop(); self.stop();
} }
}
} }

View File

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

View File

@ -1,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,8 +62,9 @@ impl Instruction<'_> {
} }
} }
#[cfg(feature = "debug")]
fn debug(&self, cpu: &Cpu) { fn debug(&self, cpu: &Cpu) {
#[cfg(not(target_arch = "wasm32"))]
{
// cpu state // 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!("{}{}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!( print!(
@ -113,6 +118,49 @@ impl Instruction<'_> {
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());
}
}
} }
fn accumulator(cpu: &mut Cpu) -> u16 { fn accumulator(cpu: &mut Cpu) -> u16 {
@ -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 {

View File

@ -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;

View File

@ -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()
} }
} }

View File

@ -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)
} }
} }

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

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

101
src/platform/native.rs Normal file
View File

@ -0,0 +1,101 @@
use minifb::Window;
use std::{
io::stdout,
process::exit,
thread::sleep,
time::{Duration, Instant},
};
use termion::{
async_stdin, clear,
cursor::{self, Goto},
event::Key,
input::TermRead,
raw::IntoRawMode,
};
use crate::{cpu::Cpu, keyboard::Keyboard, video::Renderer};
#[derive(Debug)]
pub struct GeorgeEmu {
cpu: Cpu,
renderer: Renderer,
input: Option<Keyboard>,
window: Option<Window>,
}
impl GeorgeEmu {
pub fn new(
mut cpu: Cpu,
renderer: Renderer,
input: Option<Keyboard>,
window: Option<Window>,
) -> Self {
cpu.reset();
Self {
cpu,
input,
renderer,
window,
}
}
pub fn draw(&mut self) {
self.renderer.render(self.window.as_mut())
}
pub fn cycle(&mut self) {
self.cpu.cycle()
}
pub fn run(&mut self) {
if self.window.is_none() {
let _ = stdout().into_raw_mode();
print!("{}{}", cursor::Hide, clear::All,);
}
let clock_interval = Duration::from_nanos(500);
let frame_interval = Duration::from_millis(16);
let mut next_clock = Instant::now() + clock_interval;
let mut next_frame = Instant::now() + frame_interval;
loop {
let now = Instant::now();
if now >= next_clock {
self.cpu.cycle();
next_clock += clock_interval;
}
if now >= next_frame {
if self.window.is_none() {
self.handle_input();
}
self.draw();
next_frame += frame_interval;
}
let next_run = std::cmp::min(next_clock, next_frame);
let sleep_dur = next_run - now;
sleep(sleep_dur)
}
}
fn handle_input(&mut self) {
let mut stdin = async_stdin().keys();
if let Some(Ok(key)) = stdin.next() {
match key {
Key::Char('q') => {
print!("{}{}{}", clear::All, cursor::Show, Goto(1, 1));
exit(0);
}
Key::Char('`') => self.cpu.toggle_stopped(),
Key::Char('\n') => self.cpu.cycle(),
Key::Char('i') => self.cpu.interrupt(),
_ => {
self.input.as_mut().unwrap().read_keys(key);
}
}
} else {
self.input.as_mut().unwrap().clear_keys();
}
}
}

29
src/platform/wasm.rs Normal file
View File

@ -0,0 +1,29 @@
use minifb::Window;
use crate::{cpu::Cpu, video::Renderer};
#[derive(Debug)]
pub struct GeorgeEmu {
cpu: Cpu,
renderer: Renderer,
window: Window,
}
impl GeorgeEmu {
pub fn new(mut cpu: Cpu, renderer: Renderer, window: Window) -> Self {
cpu.reset();
Self {
cpu,
renderer,
window,
}
}
pub fn draw(&mut self) {
self.renderer.render(Some(&mut self.window));
}
pub fn cycle(&mut self) {
self.cpu.cycle()
}
}

View File

@ -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 .end:
push inc cursor_y
sta 0, x rts
stz 1, x
lda y text:
push .asciiz "george loves u <3"
sta 0, x
sta 1, x random_y:
lda char .byte 25,12,0,20,4,25,5,13
push .byte 20, 1, 1, 22, 12, 19, 19, 19
sta 0, x .byte 9, 0, 4, 18, 13, 14, 4, 16
sta 1, x .byte 8, 17, 21, 14, 23, 21, 9, 0
jsr draw_char .byte 14, 10, 14, 2, 26, 18, 15, 23
inc x .byte 12, 2, 5, 4, 25, 20, 27, 4
lda #64 .byte 28, 21, 3, 22, 11, 25, 2, 25
cmp x .byte 13, 17, 17, 24, 8, 8, 20, 21
bne .x_loop .byte 11, 24, 27, 25, 8, 12, 7, 0
inc y .byte 27, 12, 19, 27, 10, 3, 19, 2
lda #30 .byte 2, 23, 22, 5, 26, 28, 4, 16
cmp y .byte 18, 7, 10, 9, 6, 19, 9, 2
bne .y_loop .byte 14, 8, 14, 18, 18, 2, 13, 0
.byte 15, 26, 3, 23, 17, 12, 18, 11
.byte 4, 16, 17, 22, 9, 25, 3, 15
.byte 28, 3, 6, 14, 25, 5, 21, 8
.byte 15, 18, 15, 5, 28, 6, 15, 4
.byte 10, 1, 16, 24, 6, 9, 22, 3
.byte 17, 18, 10, 19, 27, 11, 22, 16
.byte 22, 17, 15, 6, 23, 11, 11, 11
.byte 4, 15, 5, 25, 19, 1, 8, 26
.byte 21, 20, 17, 27, 11, 3, 11, 20
.byte 15, 28, 0, 6, 14, 23, 20, 21
.byte 17, 20, 16, 15, 19, 6, 21, 19
.byte 15, 27, 1, 22, 7, 0, 5, 2
.byte 14, 24, 15, 4, 20, 16, 1, 14
.byte 4, 16, 4, 8, 13, 26, 3, 9
.byte 12, 25, 5, 0, 7, 17, 14, 20
.byte 2, 26, 2, 27, 18, 23, 5, 8
.byte 4, 21, 10, 11, 28, 22, 6, 6
.byte 10, 13, 23, 12, 20, 28, 20, 1
.byte 27, 19, 25, 6, 1, 10, 1
random_x:
.byte 42, 59, 11, 5, 18, 0, 26, 1
.byte 61, 16, 1, 51, 36, 47, 23, 1
.byte 16, 50, 46, 4, 55, 31, 15, 2
.byte 45, 21, 59, 53, 15, 43, 0, 2
.byte 64, 31, 38, 41, 25, 12, 12, 3
.byte 30, 13, 64, 44, 21, 8, 48, 3
.byte 46, 1, 2, 33, 4, 32, 59, 28
.byte 4, 24, 58, 53, 21, 41, 30, 2
.byte 56, 53, 31, 10, 42, 12, 9, 54
.byte 14, 14, 24, 29, 43, 60, 54, 26
.byte 5, 53, 17, 55, 27, 46, 31, 3
.byte 26, 44, 63, 30, 10, 34, 62, 48
.byte 42, 47, 51, 7, 55, 32, 14, 21
.byte 15, 26, 52, 37, 48, 0, 13, 2
.byte 50, 20, 35, 32, 8, 41, 2, 24
.byte 18, 9, 52, 22, 52, 12, 19, 32
.byte 29, 46, 34, 58, 54, 51, 43, 57
.byte 62, 10, 12, 57, 36, 39, 4, 30
.byte 38, 9, 30, 32, 33, 57, 3, 25
.byte 21, 36, 59, 30, 19, 39, 9, 60
.byte 34, 50, 52, 37, 34, 42, 3, 33
.byte 40, 19, 2, 26, 10, 38, 46, 30
.byte 3, 1, 19, 16, 26, 58, 42, 49
.byte 63, 1, 63, 41, 0, 21, 41, 19
.byte 21, 45, 44, 52, 20, 5, 11, 64
.byte 1, 62, 16, 5, 5, 8, 58, 56
.byte 16, 26, 6, 37, 19, 16, 25, 29
.byte 64, 59, 16, 6, 41, 28, 8, 51
.byte 54, 5, 19, 28, 13, 38, 52, 35
.byte 42, 13, 34, 33, 61, 61, 7, 27
.byte 38, 33, 9, 57, 10, 30, 8, 4
.byte 46, 3, 39, 46, 62, 20, 48, 7
; increments the cursor line by line, looping to (0, 0) after (63, 28)
inc_cursor:
lda cursor_x
cmp #63
beq .newline
inc cursor_x
rts
.newline:
lda cursor_y
cmp #28
beq .newscreen
stz cursor_x
inc cursor_y
rts
.newscreen:
stz cursor_y
stz cursor_x
rts
; zeroes out the display, resets cursor to 0,0
clear:
lda #0
ldy #0
.loop:
sta $6000,y
sta $6100,y
sta $6200,y
sta $6300,y
sta $6400,y
sta $6500,y
sta $6600,y
sta $6700,y ; this goes slightly over but it's fine
iny
bne .loop
stz cursor
stz cursor + 1
rts
; prints string from cursor position, stopping at end of string or at 256 chars, whichever comes first
; $6000 + (64*Y) + X
; THIS WILL WRITE OUT OF BOUNDS IF THE CURSOR IS OUT OF BOUNDS/STRING IS TOO LONG
; TODO: figure out a simple way of writing arbitrary length strings
print:
jsr cursor_addr
ldy #0
; y_overflow = temp + 5
.loop:
lda text, y
beq .end
sta (temp), y
iny
bra .loop
.end: .end:
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
pop ; and pop it off the stack
jsr get_char_address ; calculate where to put the character in memory
sta (0, x) ; store a at the address pointed to on the stack
rts
get_char_address: ; gets vram address for a character at (x, y), ; calculates real vram address from cursor (x, y)
; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first cursor_addr:
;.byte 64 stz temp
;.byte 0 stz temp + 1
pha lda cursor_y
lda #64 beq .add_x ; if y's zero just add x
push ; doing this instead until `push_lit` is fixed .y_mult:
sta 0, x ; multiply by 64
stz 1, x clc
jsr mult ; multiply 64 with y (n2) asl
jsr plus ; add result with x (n1) 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
;jsr push_lit ; push vram address onto the stack
;.byte $00
;.byte $60
lda #$60 lda #$60
push adc temp + 1
sta 1, x sta temp + 1
stz 0, x rts
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 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.

View File

@ -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,19 +63,55 @@ 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) {
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 stdout = io::stdout();
let mut i = 0; let mut i = 0;
for char_row in 0..29 { for char_row in 0..29 {
for char_col in 0..64 { for char_col in 0..64 {
let ascii = self.read(0x6000 + i); let ascii = self.read(0x6000 + i);
i += 1; i += 1;
let char = ASCII_LOOPUP[ascii as usize]; let char = ASCII_LOOKUP[ascii as usize];
let _ = write!( let _ = write!(
// FG_COLOR = 0xFFCC00 // FG_COLOR = 0xFFCC00
// BG_COLOR = 0x110500 // BG_COLOR = 0x110500
@ -145,6 +123,15 @@ impl Renderer {
); );
} }
} }
}
}
}
self.controller.irq(); self.controller.irq();
} }
} }
impl MemoryReader for Renderer {
fn read(&self, address: u16) -> u8 {
self.memory.read(address)
}
}