316 lines
9.9 KiB
Rust
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;
|
|
}
|
|
}
|