ttgb/src/emu.rs

316 lines
9.9 KiB
Rust

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<T: io::Read>(&mut self, mut src: T) {
let mut buf = Vec::<u8>::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::<usize>() + 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;
}
}