use crate::error::Error;
use std::fmt;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::str;
use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
#[derive(Debug)]
pub enum LanguageRegion {
Japan,
English,
EuropeElsewhere,
German,
French,
Italian,
Spanish,
Unknown(char),
}
impl fmt::Display for LanguageRegion {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Japan => write!(fmt, "Japan")?,
Self::English => write!(fmt, "English")?,
Self::EuropeElsewhere => write!(fmt, "Other Europe")?,
Self::German => write!(fmt, "German")?,
Self::French => write!(fmt, "French")?,
Self::Italian => write!(fmt, "Italian")?,
Self::Spanish => write!(fmt, "Spanish")?,
Self::Unknown(ch) => write!(fmt, "Unknown region, code {ch}")?,
}
Ok(())
}
}
impl From<char> for LanguageRegion {
fn from(data: char) -> Self {
match data {
'J' => Self::Japan,
'E' => Self::English,
'P' => Self::EuropeElsewhere,
'D' => Self::German,
'F' => Self::French,
'I' => Self::Italian,
'S' => Self::Spanish,
_ => Self::Unknown(data),
}
}
}
#[derive(Debug)]
pub enum MultiplayBootMode {
None,
Joybus,
Normal,
Multiplay,
Unknown(u8),
}
impl From<u8> for MultiplayBootMode {
fn from(data: u8) -> Self {
match data {
0 => Self::None,
1 => Self::Joybus,
2 => Self::Normal,
3 => Self::Multiplay,
_ => Self::Unknown(data),
}
}
}
impl fmt::Display for MultiplayBootMode {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::None => write!(fmt, "none")?,
Self::Joybus => write!(fmt, "joybus")?,
Self::Normal => write!(fmt, "normal")?,
Self::Multiplay => write!(fmt, "multiplay")?,
Self::Unknown(data) => write!(fmt, "unknown {data}")?,
}
Ok(())
}
}
#[derive(Debug)]
pub struct Header {
pub entry_point: u32,
pub nintendo_logo: [u8; 156],
pub title: String,
pub game_code: String,
pub manufacturer_code: String,
pub main_unit_code: u8,
pub device_type: u8,
pub software_version: u8,
pub header_checksum: u8,
}
#[derive(Debug)]
pub struct MultiBootHeader {
pub ram_entry_point: u32,
pub boot_mode: MultiplayBootMode,
pub slave_id_number: u8,
pub joybus_entry_point: u32,
}
pub struct Cart<T: Read + Seek> {
pub header: Header,
io: T,
}
pub trait HeaderRead {
fn read_gba_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_gba_header()?;
Ok(Self { header, io })
}
}
fn trim(string: &str) -> &str {
string.trim_matches('\0').trim()
}
impl<T: Read + Seek> HeaderRead for T {
fn read_gba_header(&mut self) -> Result<Header, Error> {
self.seek(SeekFrom::Start(0))?;
let entry_point = self.read_u32::<LittleEndian>()?;
let mut nintendo_logo = [0u8; 156];
self.read_exact(&mut nintendo_logo)?;
self.seek(SeekFrom::Start(0xA0))?;
let mut title = [0u8; 12];
self.read_exact(&mut title)?;
let title = trim(str::from_utf8(&title)?).to_string();
let mut game_code = [0u8; 4];
self.read_exact(&mut game_code)?;
let game_code = trim(str::from_utf8(&game_code)?).to_string();
let mut manufacturer_code = [0u8; 2];
self.read_exact(&mut manufacturer_code)?;
let manufacturer_code = trim(str::from_utf8(&manufacturer_code)?).to_string();
self.seek(SeekFrom::Current(1))?;
let main_unit_code = self.read_u8()?;
let device_type = self.read_u8()?;
self.seek(SeekFrom::Current(7))?;
let software_version = self.read_u8()?;
let header_checksum = self.read_u8()?;
Ok(Header {
entry_point,
nintendo_logo,
title,
game_code,
manufacturer_code,
main_unit_code,
device_type,
software_version,
header_checksum,
})
}
}
impl<T: Read + Seek> Cart<T> {
pub fn header_checksum(&mut self) -> Result<u8, Error> {
self.io.seek(SeekFrom::Start(0xA0))?;
let mut checksum = 0u8;
let mut data = [0u8; 0xBC - 0xA0];
self.io.read_exact(&mut data)?;
for byte in &data {
checksum = checksum.wrapping_sub(*byte);
}
checksum = checksum.wrapping_sub(0x19);
Ok(checksum)
}
}