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

View File

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

View File

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

2
run.sh
View File

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

View File

@ -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<String>,
pub screen: ScreenType,
pub char_rom: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
struct ConfigBuilder {
rom: Option<String>,

View File

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

View File

@ -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<CpuControl>);
#[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<CpuReceiver>,
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) {
if self.debug {
self.stop();
}
}
}

View File

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

View File

@ -1,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")]
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,8 +62,9 @@ impl Instruction<'_> {
}
}
#[cfg(feature = "debug")]
fn debug(&self, cpu: &Cpu) {
#[cfg(not(target_arch = "wasm32"))]
{
// cpu state
print!("{}{}a: {a:#04x}, x: {x:#04x}, y: {y:#04x}, pc: {pc:#06x}, sp: {s:#04x}, sr: {p:#010b}, irq: {irq:?}, nmi: {nmi:?}", Goto(0, 31),clear::UntilNewline, a = cpu.a, x = cpu.x, y = cpu.y, pc = cpu.pc, s = cpu.s, p = cpu.p, irq = cpu.irq, nmi = cpu.nmi);
print!(
@ -113,6 +118,49 @@ impl Instruction<'_> {
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 {
@ -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<u16>) {
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 {

View File

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

View File

@ -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<String>,
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> {
impl GeorgeEmu {
pub fn builder() -> GeorgeEmuBuilder<NoRom, NoWindow> {
GeorgeEmuBuilder::new()
}
fn new(
mut cpu: Cpu,
renderer: Renderer,
input: Option<Keyboard>,
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, Window> {
rom: Rom,
cpu: Option<Cpu>,
keyboard: Option<Keyboard>,
renderer: Option<Renderer>,
cpu_controller: Option<CpuController>,
memory_handle: Option<MemHandle>,
window: Window,
}
impl GeorgeEmuBuilder<NoRom, NoWindow> {
fn new() -> Self {
GeorgeEmuBuilder {
rom: NoRom(),
cpu: None,
keyboard: None,
renderer: None,
cpu_controller: None,
memory_handle: None,
window: NoWindow(),
}
}
pub fn rom(self, rom: [u8; 0x8000]) -> GeorgeEmuBuilder<SomeRom, NoWindow> {
let mut memory = Mem::new();
memory.load_bytes(&rom);
let memory_handle = MemHandle::new(memory);
let (cpu, cpu_controller) = Cpu::new_with_control(memory_handle.clone());
let Self {
renderer,
keyboard,
window,
..
} = self;
GeorgeEmuBuilder {
cpu: Some(cpu),
rom: SomeRom(),
keyboard,
renderer,
cpu_controller: Some(cpu_controller),
memory_handle: Some(memory_handle),
window,
}
}
// pub fn window(self, mut window: Window) -> GeorgeEmuBuilder<NoRom, SomeWindow> {
// let Self {
// rom,
// cpu,
// ref cpu_controller,
// ref memory_handle,
// keyboard,
// ..
// } = self;
// let renderer = Renderer::new(
// self.cpu_controller.clone().unwrap(),
// self.memory_handle.clone().unwrap(),
// );
// window.set_input_callback(Box::new(Keyboard::new(
// self.memory_handle.as_ref().unwrap().clone(),
// )));
// GeorgeEmuBuilder {
// cpu,
// rom,
// keyboard,
// renderer: Some(renderer),
// cpu_controller: cpu_controller.clone(),
// memory_handle: memory_handle.clone(),
// window: SomeWindow(window),
// }
// }
}
impl GeorgeEmuBuilder<SomeRom, NoWindow> {
/// Adds a minifb window to the emulator
pub fn window(self, mut window: Window) -> GeorgeEmuBuilder<SomeRom, SomeWindow> {
let Self {
rom,
cpu,
keyboard,
ref cpu_controller,
ref memory_handle,
..
} = self;
let renderer = Renderer::new(
self.cpu_controller.clone().unwrap(),
self.memory_handle.clone().unwrap(),
);
window.set_input_callback(Box::new(Keyboard::new(
self.memory_handle.as_ref().unwrap().clone(),
)));
GeorgeEmuBuilder {
cpu,
rom,
keyboard,
renderer: Some(renderer),
cpu_controller: cpu_controller.clone(),
memory_handle: memory_handle.clone(),
window: SomeWindow(window),
}
}
/// Enables debug printing and breakpoint triggering
pub fn debug(mut self) -> Self {
self.cpu.as_mut().unwrap().debug = true;
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn build(self) -> GeorgeEmu {
let keyboard = Keyboard::new(self.memory_handle.clone().unwrap());
GeorgeEmu(imp::GeorgeEmu::new(
self.cpu.unwrap(),
self.renderer.unwrap(),
Some(keyboard),
None,
))
}
}
impl GeorgeEmuBuilder<NoRom, SomeWindow> {
pub fn rom(self, rom: [u8; 0x8000]) -> GeorgeEmuBuilder<SomeRom, SomeWindow> {
let mut memory = Mem::new();
memory.load_bytes(&rom);
let memory_handle = MemHandle::new(memory);
let (cpu, cpu_controller) = Cpu::new_with_control(memory_handle.clone());
let Self {
renderer,
keyboard,
window,
..
} = self;
GeorgeEmuBuilder {
cpu: Some(cpu),
rom: SomeRom(),
keyboard,
renderer,
cpu_controller: Some(cpu_controller),
memory_handle: Some(memory_handle),
window,
}
}
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<SomeRom, SomeWindow> {
/// Enables debug printing and breakpoint triggering
pub fn debug(mut self) -> Self {
self.cpu.as_mut().unwrap().debug = true;
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn build(self) -> GeorgeEmu {
GeorgeEmu(imp::GeorgeEmu::new(
self.cpu.unwrap(),
self.renderer.unwrap(),
None,
Some(self.window.0),
))
}
#[cfg(target_arch = "wasm32")]
pub fn build(self) -> GeorgeEmu {
GeorgeEmu(imp::GeorgeEmu::new(
self.cpu.unwrap(),
self.renderer.unwrap(),
self.window.0,
))
}
}

View File

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

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"
.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
; 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
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
lda 0, x ; load a with character to draw
pop ; and pop it off the stack
jsr get_char_address ; calculate where to put the character in memory
sta (0, x) ; store a at the address pointed to on the stack
rts
get_char_address: ; gets vram address for a character at (x, y),
; (n1: x n2: y -- n: $6000 + x + (64 * y))
;jsr push_lit ; push 64 onto stack, low byte first
;.byte 64
;.byte 0
pha
lda #64
push ; doing this instead until `push_lit` is fixed
sta 0, x
stz 1, x
jsr mult ; multiply 64 with y (n2)
jsr plus ; add result with x (n1)
; 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
;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
adc temp + 1
sta temp + 1
rts
rand_draw:
ldx rand_index
lda random_x, x
sta cursor_x
lda random_y, x
sta cursor_y
jsr cursor_addr
inc rand_index
rts
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

Binary file not shown.

View File

@ -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<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] = [
#[cfg(not(target_arch = "wasm32"))]
const ASCII_LOOKUP: [&str; 256] = [
" ", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "奔", "", "", "", "", "", "", " ", "!", "\"", "#",
"$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "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 {
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 i = 0;
for char_row in 0..29 {
for char_col in 0..64 {
let ascii = self.read(0x6000 + i);
i += 1;
let char = ASCII_LOOPUP[ascii as usize];
let char = ASCII_LOOKUP[ascii as usize];
let _ = write!(
// FG_COLOR = 0xFFCC00
// BG_COLOR = 0x110500
@ -145,6 +123,15 @@ impl Renderer {
);
}
}
}
}
}
self.controller.irq();
}
}
impl MemoryReader for Renderer {
fn read(&self, address: u16) -> u8 {
self.memory.read(address)
}
}