Bweh/src/memory/io.rs
John acfbb8f1cf boy: Initial public commit
- SM83 implementation kinda works
- Live disassembly, memory write tracing
- Pretty snazzy debugger with custom memory editor
- hexadecimal calculator with novel operator precedence rules
2024-06-22 07:25:59 -05:00

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());
}
}
}