#![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, } #[allow(dead_code)] pub struct Memory { bootrom: ROM, bootrom_enabled: bool, interrupt_table: [u8; 256], 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, interrupt_table: [0xFF; 256], rom, vram: [0x0; 8192], ram: [0x0; 8192], switchable_ram: [0x0; 8192], cpu_ram: [0x0; 128], oam: [0x0; 160], interrupts: 0x0, ime: false, io: [0xFF; 76], } } fn get(&self, address: Address) -> u8 { match address { 0x0..0x100 => { if self.bootrom_enabled { return self.bootrom[address as usize]; } else { return self.interrupt_table[address as usize]; } } 0x100..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 => 0xFF, 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 0xFF; } 0xFF80..0xFFFF => { return self.cpu_ram[(address - 0xFF80) as usize]; } 0xFFFF => { return self.interrupts; } } } fn set(&mut self, address: Address, data: u8) { println!("write addr: {:#X}, data: {:#X}", address, data); match address { 0x0..0x100 => { if !self.bootrom_enabled { self.interrupt_table[address as usize] = data; // panic!("setting {:#X} to {:#X}", address, data) } } 0x100..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 => { // panic!("switchable write"); // 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: 0x00B0 }, // 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 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); } #[allow(dead_code)] fn swap_rom_endian(rom: &ROM) -> ROM { rom.chunks(2) .map(|l| { let mut m = l.to_owned(); m.reverse(); m }) .flatten() .collect() } static mut PAUSE_ENABLED: bool = false; static mut PAUSE_QUEUED: bool = false; 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; let mut instructions_seen = vec![]; let mut last_state = cpu.state.clone(); let mut next_state: State; 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 { let will_pause; unsafe { will_pause = PAUSE_QUEUED.clone(); } cycle_num += 1; // print_cycles(&cycle_num); cpu.exec_next(); unsafe { next_state = cpu.state; if !PAUSE_ENABLED { if next_state.pc.as_u16 >= 0x100 { PAUSE_ENABLED = true; } } last_state = next_state; if will_pause { pause(); } } match instructions_seen.contains(&cpu.last_instruction) { true => {} false => { // println!("new instruction enountered: {:#X}", cpu.last_instruction); instructions_seen.push(cpu.last_instruction); } } }, } } #[allow(dead_code)] fn pause() { unsafe { if PAUSE_ENABLED { let line = &mut String::new(); io::stdin().read_line(line).unwrap(); PAUSE_QUEUED = !line.contains("continue"); } } } #[allow(dead_code)] fn print_cycles(cycles: &i32) { if *cycles % 456789 != 0 { return; } let instructions_per_second = 400000; print!( "cycle {} - approx {} seconds on real hardware\r", cycles, cycles / instructions_per_second ); stdout().flush().unwrap(); }