- SM83 implementation kinda works - Live disassembly, memory write tracing - Pretty snazzy debugger with custom memory editor - hexadecimal calculator with novel operator precedence rules
223 lines
6.6 KiB
Rust
223 lines
6.6 KiB
Rust
//! Trait for transferring data to and from a CPU
|
|
|
|
pub mod iter {
|
|
//! Iterates over the bytes in a [BusIO]
|
|
use super::BusIO;
|
|
|
|
pub struct Iter<'a> {
|
|
start: usize,
|
|
bus: &'a dyn BusIO,
|
|
}
|
|
impl<'a> Iter<'a> {
|
|
pub fn new(bus: &'a dyn BusIO) -> Self {
|
|
Self { start: 0, bus }
|
|
}
|
|
pub fn start_at(bus: &'a dyn BusIO, start: usize) -> Self {
|
|
Self { start, bus }
|
|
}
|
|
}
|
|
impl<'a> Iterator for Iter<'a> {
|
|
type Item = u8;
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
let Self { start, bus } = self;
|
|
let out = bus.read(*start);
|
|
if out.is_some() {
|
|
*start += 1;
|
|
}
|
|
out
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Trait for transferring data to and from a CPU
|
|
pub trait BusIO {
|
|
// required functions
|
|
/// Attempts to read a byte from self, returning None if the operation failed.
|
|
fn read(&self, addr: usize) -> Option<u8>;
|
|
/// Attempts to write a byte to self, returning None if the operation failed.
|
|
fn write(&mut self, addr: usize, data: u8) -> Option<()>;
|
|
|
|
/// Does some implementation-specific debug function
|
|
fn diag(&mut self, _param: usize) {}
|
|
|
|
/// Gets an iterator which reads the bytes from self
|
|
fn read_iter(&self) -> iter::Iter
|
|
where
|
|
Self: Sized,
|
|
{
|
|
iter::Iter::new(self)
|
|
}
|
|
/// Gets an iterator which reads bytes in an exclusive range
|
|
fn read_iter_from(&self, start: usize) -> iter::Iter
|
|
where
|
|
Self: Sized,
|
|
{
|
|
iter::Iter::start_at(self, start)
|
|
}
|
|
}
|
|
|
|
impl<const N: usize> BusIO for [u8; N] {
|
|
fn read(&self, addr: usize) -> Option<u8> {
|
|
self.get(addr).copied()
|
|
}
|
|
fn write(&mut self, addr: usize, data: u8) -> Option<()> {
|
|
self.get_mut(addr).map(|v| *v = data)
|
|
}
|
|
}
|
|
|
|
impl BusIO for [u8] {
|
|
fn read(&self, addr: usize) -> Option<u8> {
|
|
self.get(addr).copied()
|
|
}
|
|
fn write(&mut self, addr: usize, data: u8) -> Option<()> {
|
|
self.get_mut(addr).map(|v| *v = data)
|
|
}
|
|
}
|
|
|
|
impl BusIO for Vec<u8> {
|
|
fn read(&self, addr: usize) -> Option<u8> {
|
|
self.get(addr).copied()
|
|
}
|
|
fn write(&mut self, addr: usize, data: u8) -> Option<()> {
|
|
self.get_mut(addr).map(|v| *v = data)
|
|
}
|
|
}
|
|
|
|
impl<T: BusIO> BusAux for T {}
|
|
pub trait BusAux: BusIO {
|
|
// derived functions
|
|
/// Copies a contiguous region from the bus. Returns an incomplete copy on error.
|
|
fn read_arr<const N: usize>(&self, addr: usize) -> Result<[u8; N], [u8; N]> {
|
|
let mut out = [0; N];
|
|
for (idx, byte) in out.iter_mut().enumerate() {
|
|
match self.read(addr + idx) {
|
|
Some(value) => *byte = value,
|
|
None => return Err(out),
|
|
}
|
|
}
|
|
Ok(out)
|
|
}
|
|
/// Gets the cart entrypoint bytes (machine code starting at 0x100)
|
|
fn entry(&self) -> Result<[u8; 4], [u8; 4]> {
|
|
self.read_arr(0x100)
|
|
}
|
|
/// Gets the logo bytes (0x104..0x134)
|
|
fn logo(&self) -> Result<[u8; 0x30], [u8; 0x30]> {
|
|
self.read_arr(0x104)
|
|
}
|
|
/// Gets the title bytes as a String
|
|
/// # Note
|
|
/// The title area was repartitioned for other purposes during the Game Boy's life.
|
|
/// See [mfcode](BusAux::mfcode) and [cgbflag](BusAux::cgbflag).
|
|
fn title(&self) -> Result<String, [u8; 0x10]> {
|
|
// Safety: Chars are checked to be valid ascii before cast to char
|
|
Ok(self
|
|
.read_arr(0x134)? // to 0x144
|
|
.iter()
|
|
.copied()
|
|
.take_while(|c| *c != 0 && c.is_ascii())
|
|
.map(|c| unsafe { char::from_u32_unchecked(c as u32) })
|
|
.collect())
|
|
}
|
|
/// Gets the manufacturer code bytes
|
|
fn mfcode(&self) -> Result<[u8; 4], [u8; 4]> {
|
|
self.read_arr(0x13f)
|
|
}
|
|
/// Gets the CGB Flag byte
|
|
fn cgbflag(&self) -> Option<u8> {
|
|
self.read(0x143)
|
|
}
|
|
/// Gets the New Licensee bytes
|
|
fn newlicensee(&self) -> Result<[u8; 2], [u8; 2]> {
|
|
self.read_arr(0x144)
|
|
}
|
|
/// Gets the SGB Flag byte
|
|
fn sgbflag(&self) -> Option<u8> {
|
|
self.read(0x146)
|
|
}
|
|
/// Gets the Cartridge Type byte
|
|
fn carttype(&self) -> Option<u8> {
|
|
self.read(0x147)
|
|
}
|
|
/// Gets the size of the cartridge ROM (in 0x8000 byte banks)
|
|
fn romsize(&self) -> Option<usize> {
|
|
self.read(0x148).map(|s| 1usize.wrapping_shl(s as u32 + 1))
|
|
}
|
|
/// Gets the size of the cartridge RAM (in 0x8000 byte banks)
|
|
fn ramsize(&self) -> Option<usize> {
|
|
self.read(0x149).map(|s| match s {
|
|
0x02 => 1,
|
|
0x03 => 4,
|
|
0x04 => 16,
|
|
0x05 => 8,
|
|
_ => 0,
|
|
})
|
|
}
|
|
/// Gets the destination region code
|
|
fn destcode(&self) -> Option<u8> {
|
|
self.read(0x14a)
|
|
}
|
|
/// Gets the old licensee byte
|
|
fn oldlicencee(&self) -> Option<u8> {
|
|
self.read(0x14b)
|
|
}
|
|
/// Gets the Maskrom Version byte. Usually 0
|
|
fn maskromver(&self) -> Option<u8> {
|
|
self.read(0x14c)
|
|
}
|
|
/// Gets the header checksum
|
|
fn headercksum(&self) -> Option<u8> {
|
|
self.read(0x14d)
|
|
}
|
|
/// Gets the checksum of all bytes in ROM
|
|
fn globalcksum(&self) -> Option<u16> {
|
|
self.read_arr(0x14e).ok().map(u16::from_be_bytes)
|
|
}
|
|
}
|
|
|
|
pub mod interrupt {
|
|
use super::BusIO;
|
|
use bitfield_struct::bitfield;
|
|
|
|
/// Interrupt Enable register
|
|
const IE: usize = 0xffff;
|
|
/// Interrupt Flags register
|
|
const IF: usize = 0xff0f;
|
|
|
|
#[bitfield(u8)]
|
|
#[derive(PartialEq, Eq)]
|
|
pub struct Interrupt {
|
|
vblank: bool,
|
|
lcd: bool,
|
|
timer: bool,
|
|
serial: bool,
|
|
joypad: bool,
|
|
#[bits(3)]
|
|
_reserved: (),
|
|
}
|
|
|
|
impl<B: BusIO> Interrupts for B {}
|
|
/// Functionality for working with [Interrupt]s
|
|
pub trait Interrupts: BusIO {
|
|
/// Gets the set of enabled [Interrupt]s
|
|
fn enabled(&self) -> Option<Interrupt> {
|
|
self.read(IE).map(Interrupt::from_bits)
|
|
}
|
|
/// Gets the set of raised [Interrupt]s
|
|
#[inline]
|
|
fn raised(&self) -> Option<Interrupt> {
|
|
self.read(IF).map(Interrupt::from_bits)
|
|
}
|
|
/// Raises [Interrupt]s with the provided mask.
|
|
fn raise(&mut self, int: Interrupt) {
|
|
let flags = self.raised().unwrap_or_default();
|
|
let _ = self.write(IF, flags.into_bits() | int.into_bits());
|
|
}
|
|
/// Clears [Interrupt]s with the provided mask.
|
|
fn clear(&mut self, int: Interrupt) {
|
|
let flags = self.raised().unwrap_or_default();
|
|
let _ = self.write(IF, flags.into_bits() & !int.into_bits());
|
|
}
|
|
}
|
|
}
|