#![feature(exclusive_range_pattern)] mod processor; use clap::{ArgGroup, Parser}; use minifb::{Window, WindowOptions}; use processor::CPU; use std::{ fs, io::{self, stdout, Write}, sync::RwLock, }; use crate::processor::{gpu::GPU, Registers}; #[macro_export] macro_rules! verbose_println { ($($tts:tt)*) => { if crate::is_verbose() { println!($($tts)*); } }; } #[macro_export] macro_rules! verbose_print { ($($tts:tt)*) => { if crate::is_verbose() { print!($($tts)*); } }; } /// Simple program to greet a person #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] #[command(group(ArgGroup::new("prints").args(["verbose","cycle_count"])))] struct Args { /// ROM path #[arg(short, long)] rom: String, /// BootROM path #[arg(short, long)] bootrom: String, /// Just run BootROM #[arg(long)] run_bootrom: bool, /// Verbose print #[arg(short, long)] verbose: bool, /// Show cycle count #[arg(short, long)] cycle_count: bool, /// Step emulation by... #[arg(long)] step_by: Option, } type Address = u16; type ROM = Vec; #[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, ime_scheduled: u8, 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, ime_scheduled: 0x0, 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) { // verbose_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 => { // verbose_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 => { verbose_println!("interrupts set to {:#b}", data); verbose_println!(" / {:#X}", data); self.interrupts = data; } } } } 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; // static mut VERBOSE: bool = false; static VERBOSE: RwLock = RwLock::new(false); const WIDTH: usize = 160; const HEIGHT: usize = 144; fn main() { let args = Args::parse(); { let mut v = VERBOSE.write().unwrap(); *v = args.verbose; } // let buffer: Vec = ; let window = Window::new("Gameboy", WIDTH, HEIGHT, WindowOptions::default()).unwrap_or_else(|e| { panic!("{}", e); }); window.topmost(true); 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 reg = Registers::default(); if args.run_bootrom { reg.pc = 0x0; } let mut cpu = CPU { memory: Memory::init(bootrom, args.run_bootrom, rom), reg, last_instruction: 0x0, last_instruction_addr: 0x0, window, gpu: GPU::default(), }; cpu_ram_init(&mut cpu); let mut cycle_num = 0; let mut instructions_seen = vec![]; let mut last_state = cpu.reg.clone(); let mut next_state = last_state; verbose_println!("\n\n Begin execution...\n"); match args.step_by { Some(step_size) => loop { for _ in 0..step_size { cycle_num += 1; if args.cycle_count { print_cycles(&cycle_num); } run_cycle( &mut cpu, &mut next_state, &mut last_state, &mut instructions_seen, ); } print!( " ...{} cycles - press enter to continue\r", cycle_num ); stdout().flush().unwrap(); pause_once(); }, None => loop { cycle_num += 1; if args.cycle_count { print_cycles(&cycle_num); } run_cycle( &mut cpu, &mut next_state, &mut last_state, &mut instructions_seen, ); }, } } fn run_cycle( cpu: &mut CPU, next_state: &mut Registers, last_state: &mut Registers, instructions_seen: &mut Vec, ) { let will_pause; unsafe { will_pause = PAUSE_QUEUED.clone(); } cpu.exec_next(); unsafe { *next_state = cpu.reg; if !PAUSE_ENABLED { if next_state.pc >= 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); } } } fn pause() { unsafe { if PAUSE_ENABLED { let line = &mut String::new(); io::stdin().read_line(line).unwrap(); PAUSE_QUEUED = !line.contains("continue"); } } } fn pause_once() { io::stdin().read_line(&mut String::new()).unwrap(); } fn print_cycles(cycles: &i32) { if *cycles % 45678 != 0 { return; } let instructions_per_second = 400000; print!( "cycle {} - approx {} seconds on real hardware\r", cycles, cycles / instructions_per_second ); stdout().flush().unwrap(); } fn is_verbose() -> bool { match VERBOSE.read() { Ok(v) => *v, Err(_) => false, } }