use std::convert::TryFrom;
#[derive(Clone, Copy, Debug)]
pub enum CartReadingError {
UnrecognisedFormat,
UnknownMapper(u8),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Mirroring {
Horizontal,
Vertical,
SingleLower,
SingleUpper,
}
impl From<u8> for Mirroring {
fn from(mirroring: u8) -> Self {
match mirroring {
0 => Mirroring::SingleLower,
1 => Mirroring::SingleUpper,
2 => Mirroring::Vertical,
_ => Mirroring::Horizontal,
}
}
}
impl Mirroring {
pub fn is_vertical(self) -> bool {
self == Mirroring::Vertical
}
pub(crate) fn mirror_address(self, address: u16) -> u16 {
let address = (address - 0x2000) % 0x1000;
let table = match (self, address / 0x400) {
(Mirroring::Horizontal, 0) => 0,
(Mirroring::Horizontal, 1) => 0,
(Mirroring::Horizontal, 2) => 1,
(Mirroring::Horizontal, 3) => 1,
(Mirroring::Vertical, 0) => 0,
(Mirroring::Vertical, 1) => 1,
(Mirroring::Vertical, 2) => 0,
(Mirroring::Vertical, 3) => 1,
(Mirroring::SingleLower, _) => 0,
(Mirroring::SingleUpper, _) => 1,
_ => 0,
};
0x2000 + table * 0x400 + (address % 0x400)
}
}
#[derive(Clone, Copy, Debug)]
pub enum MapperID {
M2,
M1,
}
impl TryFrom<u8> for MapperID {
type Error = CartReadingError;
fn try_from(byte: u8) -> Result<Self, Self::Error> {
match byte {
0 => Ok(MapperID::M2),
1 => Ok(MapperID::M1),
2 => Ok(MapperID::M2),
_ => Err(CartReadingError::UnknownMapper(byte)),
}
}
}
pub struct Cart {
pub prg: Vec<u8>,
pub chr: Vec<u8>,
pub sram: [u8; 0x2000],
pub mapper: MapperID,
pub mirroring: Mirroring,
pub has_battery: bool,
}
impl Cart {
pub fn from_bytes(buffer: &[u8]) -> Result<Cart, CartReadingError> {
if buffer[0..4] == [0x4E, 0x45, 0x53, 0x1A] {
Cart::from_ines(buffer)
} else {
Err(CartReadingError::UnrecognisedFormat)
}
}
fn from_ines(buffer: &[u8]) -> Result<Cart, CartReadingError> {
let prg_chunks = buffer[4] as usize;
let chr_chunks = buffer[5] as usize;
let flag6 = buffer[6];
let flag7 = buffer[7];
let trainer_offset = if flag6 & 0b100 > 0 { 512 } else { 0 };
let prg_start = 16 + trainer_offset;
let prg_end = prg_start + 0x4000 * prg_chunks;
let chr_end = prg_end + 0x2000 * chr_chunks;
let mapper = MapperID::try_from((flag6 >> 4) | (flag7 & 0xF0))?;
let mirroring = if flag6 & 1 != 0 {
Mirroring::Vertical
} else {
Mirroring::Horizontal
};
let chr = if chr_chunks == 0 {
vec![0; 0x2000]
} else {
buffer[prg_end..chr_end].to_vec()
};
Ok(Cart {
prg: buffer[prg_start..prg_end].to_vec(),
chr,
mapper,
sram: [0; 0x2000],
mirroring,
has_battery: flag6 & 0b10 > 0,
})
}
}