use std::cmp; use std::fs; use std::io; use std::io::{Read, Write}; use crate::breakpoint::{Command as BpCommand, BreakPointManager}; use crate::breakpoint_shell::BreakPointShell; use crate::op::*; const RAM_SIZE: usize = 0xFFFF + 1; const BOOT_ROM_SIZE: usize = 256; const REG_SIZE: usize = 0xB; pub struct GameBoy { boot_rom: [u8; BOOT_ROM_SIZE], mem: [u8; RAM_SIZE], reg: [u8; REG_SIZE], pc: u16, sp: u16, bp_manager: BreakPointManager, } impl GameBoy { pub fn new() -> GameBoy { GameBoy { boot_rom: [0; BOOT_ROM_SIZE], mem: [0; RAM_SIZE], reg: [0; REG_SIZE], pc: 0, sp: 0, bp_manager: BreakPointManager::new(), } } fn set(adr: u16, val: u8) { // check for special registers } fn set_fast(adr: u16, val: u8) { // no checks for special registers } pub fn load(&mut self, mut src: T) { let mut buf = Vec::::new(); let _ = src.read_to_end(&mut buf); // FIXME read only allowed amount match (&mut self.mem[..]).write_all(&buf) { Ok(_) => (), Err(e) => panic!("{e}"), }; let mut bios = fs::File::open("DMG_ROM.bin").expect("could not open bios"); bios.read_exact(&mut self.boot_rom) .expect("fail to read rom"); } pub fn register_breakpoint(&mut self, adr: &str) { let adr = match adr.strip_prefix("0x") { Some(adr) => adr, None => adr, }; let Ok(adr) = usize::from_str_radix(adr, 16) else { panic!("FIXME error handling on given bad breakpoint address"); }; self.bp_manager.register_breakpoint(adr, BreakPointShell::new()); } fn validate_header(&self) { let mem = &self.mem; let _nintendo_title = &mem[0x104..=0x133]; let _game_title = &mem[0x134..=0x13e]; let _game_code = &mem[0x13f..=0x142]; let _cgb_support_code = mem[0x143]; let _marker_code = ((mem[0x144] as u16) << 8) + (mem[0x145] as u16); let _sgb_support_code = mem[0x146]; let _software_type = mem[0x147]; let _rom_size = mem[0x148]; let _ext_ram_size = mem[0x149]; let _dest_code = mem[0x14A]; let _mask_rom_ver = mem[0x14C]; let reg_data = &mem[0x134..=0x14C]; let sum = reg_data.iter().map(|x| *x as usize).sum::() + 0x19; let comp = (-(sum as isize) as usize) & 0xFF; let comp_exp = mem[0x14D] as usize; if comp != comp_exp { panic!("comp check failed: calc: {comp:#010b}\nexp: {comp_exp:#010b}"); } else { println!("complement header check ok"); } // let checksum = ((mem[0x14E] as u16) << 8) + mem[0x15F] as u16; // println!("checksum: {checksum}"); } pub fn run(&mut self) { self.validate_header(); self.pc = 0x100; self.sp = 0xFFFE; loop { self.step(); } } fn op_ld(&mut self, dst: Loc, src: Loc, modifier: i8) { let pc = self.pc as usize; let (val, src_adr) = match src { Loc::Reg(i) => (self.reg[i as usize], i as usize), Loc::Val => (self.mem[pc + 1], 0), Loc::Mem(ref src) => match src { AddrLoc::Reg(reg) => { let mem_loc = self.reg[*reg] as usize; (self.mem[mem_loc], mem_loc) } AddrLoc::Mem => todo!(), AddrLoc::Val => todo!(), } Loc::HMem(_) => todo!(), }; let (dst_ref, dst_adr) = match dst { Loc::Mem(ref src) => match src { AddrLoc::Val => { let adr = self.read16(pc) as usize; (&mut self.mem[adr], adr) } _ => todo!(), }, Loc::HMem(ref src) => match src { AddrLoc::Val => { let adr = self.mem[pc + 1] as usize + 0xFF00; (&mut self.mem[adr], adr) } _ => todo!(), }, Loc::Reg(r) => (&mut self.reg[r as usize], r as usize), Loc::Val => panic!("val as destination should not be possible"), }; let dst_str = dst.repr(dst_adr, *dst_ref); let src_str = src.repr(src_adr, val); println!("ld {dst_str} <- {src_str} + {modifier}"); *dst_ref = (val as i16 + modifier as i16) as u8; } fn op_ld16(&mut self, dst: Loc, src: Loc) { let pc = self.pc as usize; let (l, h) = match src { Loc::Val => (self.mem[pc + 1], self.mem[pc + 2]), _ => todo!(), }; let dst_ref = match dst { Loc::Reg(reg) => { // FIXME assuming DD is a typo for DE... let reg_loc = reg.into(); &mut self.reg[reg_loc..=reg_loc + 1] } Loc::Val => panic!("value as dst is not allowed!"), _ => todo!(), }; match dst { Loc::Reg(_) => println!("ld16 {dst:6?} <- 0x{h:02x}{l:02x}"), _ => panic!("print address"), } // FIXME does this break when (if) copying from another register? dst_ref[0] = l; dst_ref[1] = h; } fn read16(&self, adr: usize) -> u16 { let ladr = self.mem[adr] as u16; let hadr = self.mem[adr+1] as u16; (hadr << 8) + ladr } fn store16(&mut self, loc: Loc, _loc_val: u8, h: u8, l: u8) { match loc { Loc::Reg(dst_reg) => { let dst_reg: usize = dst_reg.into(); self.reg[dst_reg] = h; self.reg[dst_reg + 1] = l; } _ => todo!() } } fn step(&mut self) { use Instr::*; let mut pc = self.pc as usize; let instr = self.mem[pc]; let win = &self.mem[pc..cmp::min(RAM_SIZE, pc + 3)]; let op = match Operation::try_from(instr) { Ok(op) => op, Err(err) => { panic!("pc:{pc:#08x}:{win:02x?} error decoding operation {instr:#010b}: {err}"); } }; match self.bp_manager.inform(pc, &op) { BpCommand::Ignore | BpCommand::Cont | BpCommand::Next => (), BpCommand::Step => todo!(), BpCommand::Kill => panic!("kill from breakpoint"), }; println!("pc:{pc:#08x}:{win:02x?} {op:?}"); match op.inst { Unimpl(instr) => { let h = (instr & HD_MSK) >> 6; let t1 = (instr & T1_MSK) >> 3; let t2 = instr & T2_MSK; panic!("unimplemented instruction {instr:#04x}: 0b{h:02b} {t1:03b} {t2:03b}") } Nop => (), Jp => { let new_pc = self.read16(pc+1); self.pc = new_pc; println!("jp {pc:#06x}->{new_pc:#06x}"); return; } Jr => { let e = self.mem[pc + 1] as i8 + 2; let old_pc = self.pc; let new_pc = (old_pc as i16 + e as i16) as u16; self.pc = new_pc; println!("jr {new_pc:#06x} <- {old_pc:#06x} + {e}"); return; } Call => { let sp = self.sp as usize; self.mem[sp - 1] = ((pc+op.offs) >> 8) as u8; self.mem[sp - 2] = ((pc+op.offs) & 0xFF) as u8; let new_pc = self.read16(pc+1); self.pc = new_pc; self.sp -= 2; println!("call {new_pc:#06x}"); return; } Ret => { let new_pc = self.read16(self.sp as usize); self.pc = new_pc; self.sp += 2; println!("ret {new_pc:#06x}"); return; } Psh(reg) => { let reg: usize = reg.into(); let sp = self.sp as usize; self.mem[sp - 1] = self.reg[reg]; self.mem[sp - 2] = self.reg[reg + 1]; self.sp -= 2; println!("push Reg({reg:#04b})"); } Pop(reg) => { let reg: usize = reg.into(); let sp = self.sp as usize; self.reg[reg + 1] = self.mem[sp]; self.reg[reg] = self.mem[sp + 1]; self.sp += 2; println!("pop Reg({reg:#04b})"); } Inc(reg) => { self.reg[reg] += 1; println!("Inc Reg({reg:#04b})"); } Dec(reg) => { self.reg[reg as usize] -= 1; println!("Dec Reg({reg:#04b})"); } Ld(dst, src) => self.op_ld(dst, src, 0), Ld16(dst, src) => self.op_ld16(dst, src), LdInc(src) => { self.op_ld(Loc::Reg(Register::A), src, 1) } LdDec(src) => { self.op_ld(Loc::Reg(Register::A), src, -1) } Or(src) => { let v = match src { Loc::Reg(r) => self.reg[r], Loc::Mem(ref src) => { let err_text = "Or Mem(Reg) only defined for register HR)"; let AddrLoc::Reg(src) = src else { panic!("Invalid op: {err_text}"); }; assert!(src == &Register::HL, "{err_text}"); self.mem[self.reg[*src] as usize] }, Loc::Val => self.mem[pc+1], _ => panic!("invalid command") }; self.reg[Register::A] |= v; println!("Or {src:?}"); } Di => println!("FIXME Di instruction not implemented"), } pc += op.offs; self.pc = pc as u16; } }