#![feature(exclusive_range_pattern)] mod processor; use clap::Parser; use processor::CPU; use std::{ fs, io::{self, stdout, Write}, }; /// Simple program to greet a person #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// ROM path #[arg(short, long)] rom: String, /// BootROM path #[arg(short, long)] bootrom: String, /// Just run BootROM #[arg(long)] run_bootrom: bool, /// Step emulation by... #[arg(long)] step_by: Option, } type Address = u16; type ROM = Vec; #[derive(Clone, Copy)] struct Inner { left: u8, right: u8, } #[derive(Clone, Copy)] union Register { as_u8s: Inner, as_u16: u16, } pub struct Memory { bootrom: ROM, bootrom_enabled: bool, rom: ROM, vram: [u8; 8192], ram: [u8; 8192], switchable_ram: [u8; 8192], cpu_ram: [u8; 128], oam: [u8; 160], interrupts: u8, ime: bool, io: [u8; 76], } impl Memory { fn init(bootrom: ROM, bootrom_enabled: bool, rom: ROM) -> Self { Self { bootrom, bootrom_enabled, rom, vram: [0x0; 8192], ram: [0x0; 8192], switchable_ram: [0x0; 8192], cpu_ram: [0x0; 128], oam: [0x0; 160], interrupts: 0x0, ime: false, io: [0x0; 76], } } fn get(&self, address: Address) -> u8 { match address { 0x0..0x8000 => { // rom access // todo - switchable rom banks if self.bootrom_enabled && (address as usize) < self.bootrom.len() { return self.bootrom[address as usize]; } else { return self.rom[address as usize]; } } 0x8000..0xA000 => { return self.vram[(address - 0x8000) as usize]; } 0xA000..0xC000 => { return self.switchable_ram[(address - 0xA000) as usize]; } 0xC000..0xE000 => { return self.ram[(address - 0xC000) as usize]; } 0xE000..0xFE00 => { return self.ram[(address - 0xE000) as usize]; } 0xFE00..0xFEA0 => { return self.oam[(address - 0xFE00) as usize]; } 0xFEA0..0xFF00 => { return 0x0; } 0xFF00..0xFF4C => { return self.io[(address - 0xFF00) as usize]; } 0xFF4C..0xFF80 => { // println!("empty space 2 read"); return 0x0; } 0xFF80..0xFFFF => { return self.cpu_ram[(address - 0xFF80) as usize]; } 0xFFFF => { return self.interrupts; } } } fn set(&mut self, address: Address, data: u8) { match address { 0x0..0x8000 => { // change this with MBC code... // println!("tried to write {:#5X} at {:#X}", data, address); } 0x8000..0xA000 => { self.vram[(address - 0x8000) as usize] = data; } 0xA000..0xC000 => { self.switchable_ram[(address - 0xA000) as usize] = data; } 0xC000..0xE000 => { self.ram[(address - 0xC000) as usize] = data; } 0xE000..0xFE00 => { self.ram[(address - 0xE000) as usize] = data; } 0xFE00..0xFEA0 => { self.oam[(address - 0xFE00) as usize] = data; } 0xFEA0..0xFF00 => { // println!("empty space write: {:#X} to addr {:#X}", data, address); } 0xFF00..0xFF4C => { print!("writing to addr {:#X}\r", address); stdout().flush().unwrap(); if address == 0xFF02 && data == 0x81 { print!("{}", self.get(0xFF01) as char); stdout().flush().unwrap(); } self.io[(address - 0xFF00) as usize] = data; } 0xFF4C..0xFF80 => { // println!("empty space 2 write: {:#X} to addr {:#X}", data, address); } 0xFF80..0xFFFF => { self.cpu_ram[(address - 0xFF80) as usize] = data; } 0xFFFF => { println!("interrupts set to {:#b}", data); println!(" / {:#X}", data); self.interrupts = data; } } } } #[derive(Clone, Copy)] pub struct State { af: Register, bc: Register, de: Register, hl: Register, sp: Register, pc: Register, } impl Default for State { fn default() -> Self { // default post-bootrom values Self { af: Register { as_u16: 0x01B0 }, bc: Register { as_u16: 0x0013 }, de: Register { as_u16: 0x00D8 }, hl: Register { as_u16: 0x014D }, sp: Register { as_u16: 0xFFFE }, pc: Register { as_u16: 0x0100 }, } } } fn main() { let args = Args::parse(); let rom: ROM = fs::read(args.rom).expect("Could not load ROM"); let bootrom: ROM = fs::read(args.bootrom).expect("Could not load BootROM"); let mut state = State::default(); if args.run_bootrom { state.pc = Register { as_u16: 0x0 }; } let mut cpu = CPU { memory: Memory::init(bootrom, args.run_bootrom, rom), state, last_instruction: 0x0, last_instruction_addr: 0x0, }; cpu_ram_init(&mut cpu); #[allow(unused_variables)] let mut cycle_num = 0; match args.step_by { Some(step_size) => loop { for _ in 0..step_size { cycle_num += 1; cpu.exec_next(); println!( "exec {:#4X} from {:#4X}", cpu.last_instruction, cpu.last_instruction_addr ); } print!( " ...{} cycles - press enter to continue\r", cycle_num ); stdout().flush().unwrap(); pause(); }, None => loop { cycle_num += 1; // print_cycles(&cycle_num); cpu.exec_next(); }, } } #[allow(dead_code)] fn pause() { io::stdin().read_line(&mut String::new()).unwrap(); } #[allow(dead_code)] fn print_cycles(cycles: &i32) { if *cycles % 456 != 0 { return; } let instructions_per_second = 400000; print!( "cycle {} - approx {} seconds on real hardware\r", cycles, cycles / instructions_per_second ); stdout().flush().unwrap(); } fn cpu_ram_init(cpu: &mut CPU) { cpu.memory.set(0xFF10, 0x80); cpu.memory.set(0xFF11, 0xBF); cpu.memory.set(0xFF12, 0xF3); cpu.memory.set(0xFF14, 0xBF); cpu.memory.set(0xFF16, 0x3F); cpu.memory.set(0xFF19, 0xBF); cpu.memory.set(0xFF1A, 0x7F); cpu.memory.set(0xFF1B, 0xFF); cpu.memory.set(0xFF1C, 0x9F); cpu.memory.set(0xFF1E, 0xBF); cpu.memory.set(0xFF20, 0xFF); cpu.memory.set(0xFF23, 0xBF); cpu.memory.set(0xFF24, 0x77); cpu.memory.set(0xFF25, 0xF3); cpu.memory.set(0xFF26, 0xF1); cpu.memory.set(0xFF40, 0x91); cpu.memory.set(0xFF47, 0xFC); cpu.memory.set(0xFF48, 0xFF); cpu.memory.set(0xFF49, 0xFF); }