use crate::cart_type::CartType;
use crate::error::Error;
use crate::licensee::Licensee;
use crate::licensee::NewLicensee;
use std::fmt;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::str;
use byteorder::BigEndian;
use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
#[derive(Debug)]
pub enum Region {
Japan,
Elsewhere,
}
impl fmt::Display for Region {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Japan => write!(fmt, "Japan")?,
Self::Elsewhere => write!(fmt, "Not Japan")?,
}
Ok(())
}
}
impl From<u8> for Region {
fn from(data: u8) -> Self {
match data {
0 => Self::Japan,
_ => Self::Elsewhere,
}
}
}
#[derive(Debug)]
pub struct Header {
pub entry_point: u32,
pub nintendo_logo: [u8; 48],
pub title: String,
pub manufacturer_code: Option<String>,
pub cgb_flag: Option<u8>,
pub new_licensee: Option<NewLicensee>,
pub sgb_flag: bool,
pub cart_type: CartType,
pub rom_size: u16,
pub ram_size: u8,
pub region: Region,
pub licensee: Licensee,
pub rom_version: u8,
pub header_checksum: u8,
pub checksum: u16,
}
impl Header {
pub fn print_logo(&self) {
let output = self.extract_boot_logo();
for y in 0..16 {
for x in 0..48 {
if output[x + y * 48] == 0 {
print!(" ");
} else {
print!("\u{2588}\u{2588}");
}
}
println!();
}
}
fn extract_boot_logo(&self) -> [u8; 48 * 16] {
let logo = self.nintendo_logo;
let mut output = [0u8; 48 * 16];
for i in (0..logo.len()).step_by(4) {
let tile_num = i / 4;
let output_index = ((tile_num % 6) * 8) + ((tile_num / 6) * 48 * 8);
for y in (0..7).step_by(4) {
for x in 0..8 {
let byte_index = (x / 4) * 2 + (y / 4);
let byte = logo[i + byte_index];
let output_index = output_index + x + y * 48;
let bit = x % 4;
output[output_index] = ((byte >> (7 - bit)) & 0x1) * 0xFF;
output[output_index + 48] = ((byte >> (7 - bit)) & 0x1) * 0xFF;
output[output_index + 48 * 2] = ((byte >> (3 - bit)) & 0x1) * 0xFF;
output[output_index + 48 * 3] = ((byte >> (3 - bit)) & 0x1) * 0xFF;
}
}
}
output
}
}
pub struct Cart<T: Read + Seek> {
pub header: Header,
io: T,
}
pub trait HeaderRead {
fn read_gb_header(&mut self) -> Result<Header, Error>;
}
impl<T: Read + Seek> Cart<T> {
pub fn new(mut io: T) -> Result<Self, Error> {
let header = io.read_gb_header()?;
Ok(Self { header, io })
}
}
fn trim(string: &str) -> &str {
string.trim_matches('\0').trim()
}
impl<T: Read + Seek> HeaderRead for T {
fn read_gb_header(&mut self) -> Result<Header, Error> {
self.seek(SeekFrom::Start(0x100))?;
let entry_point = self.read_u32::<LittleEndian>()?;
let mut nintendo_logo = [0u8; 48];
self.read_exact(&mut nintendo_logo)?;
let mut title = [0u8; 16];
self.read_exact(&mut title)?;
let (title, manufacturer_code, cgb_flag) = if title[15].is_ascii() {
(&title[..], None, None)
} else {
let manufacturer_code = &title[11..15];
if !manufacturer_code.is_ascii() {
return Err(Error::Parse("manufacturer code is not ASCII".into()));
}
let cgb_flag = title[15];
(
&title[0..11],
Some(trim(str::from_utf8(manufacturer_code)?).to_string()),
Some(cgb_flag),
)
};
if !title.is_ascii() {
return Err(Error::Parse("title not valid ASCII".into()));
}
let title = trim(str::from_utf8(title)?).to_string();
let new_licensee = self.read_u16::<LittleEndian>()?;
let lower_nibble: u8 = (new_licensee & 0x0F) as u8;
let upper_nibble: u8 = ((new_licensee >> 4) & 0xF0) as u8;
let new_licensee = (upper_nibble | lower_nibble).into();
let sgb_flag = self.read_u8()? == 0x03;
let cart_type = self.read_u8()?.into();
let rom_size = self.read_u8()?;
if rom_size > 8 {
return Err(Error::Parse("ROM size too large".into()));
}
let rom_size: u16 = 32 * (1 << rom_size);
let ram_size = self.read_u8()?;
let ram_size = match ram_size {
0 => 0,
2 => 8,
3 => 32,
4 => 128,
5 => 64,
_ => return Err(Error::Parse("Unknown RAM size".into())),
};
let region = self.read_u8()?.into();
let licensee = self.read_u8()?.into();
let rom_version = self.read_u8()?;
let header_checksum = self.read_u8()?;
let checksum = self.read_u16::<BigEndian>()?;
let new_licensee = match licensee {
Licensee::NewLicenseeField => Some(new_licensee),
_ => None,
};
Ok(Header {
entry_point,
nintendo_logo,
title,
manufacturer_code,
cgb_flag,
new_licensee,
sgb_flag,
cart_type,
rom_size,
ram_size,
region,
licensee,
rom_version,
header_checksum,
checksum,
})
}
}
impl<T: Read + Seek> Cart<T> {
pub fn header_checksum(&mut self) -> Result<u8, Error> {
self.io.seek(SeekFrom::Start(0x134))?;
let mut checksum = 0u8;
for _ in 0..(0x14D - 0x134) {
checksum = checksum.wrapping_sub(self.io.read_u8()?).wrapping_sub(1);
}
Ok(checksum)
}
pub fn checksum(&mut self) -> Result<u16, Error> {
self.io.seek(SeekFrom::Start(0))?;
let mut checksum = 0u16;
let mut data = Vec::new();
self.io.read_to_end(&mut data)?;
data[0x14E] = 0;
data[0x14F] = 0;
for byte in &data {
checksum = checksum.wrapping_add(u16::from(*byte));
}
Ok(checksum)
}
}