1use crate::error::Error;
5
6use std::fmt;
7use std::io::Read;
8use std::io::Seek;
9use std::io::SeekFrom;
10use std::str;
11
12use byteorder::LittleEndian;
13use byteorder::ReadBytesExt;
14
15#[derive(Debug)]
17pub enum LanguageRegion {
18 Japan,
19 English,
20 EuropeElsewhere,
21 German,
22 French,
23 Italian,
24 Spanish,
25 Unknown(char),
26}
27
28impl fmt::Display for LanguageRegion {
29 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
30 match self {
31 Self::Japan => write!(fmt, "Japan")?,
32 Self::English => write!(fmt, "English")?,
33 Self::EuropeElsewhere => write!(fmt, "Other Europe")?,
34 Self::German => write!(fmt, "German")?,
35 Self::French => write!(fmt, "French")?,
36 Self::Italian => write!(fmt, "Italian")?,
37 Self::Spanish => write!(fmt, "Spanish")?,
38 Self::Unknown(ch) => write!(fmt, "Unknown region, code {ch}")?,
39 }
40 Ok(())
41 }
42}
43
44impl From<char> for LanguageRegion {
45 fn from(data: char) -> Self {
46 match data {
47 'J' => Self::Japan,
48 'E' => Self::English,
49 'P' => Self::EuropeElsewhere,
50 'D' => Self::German,
51 'F' => Self::French,
52 'I' => Self::Italian,
53 'S' => Self::Spanish,
54 _ => Self::Unknown(data),
55 }
56 }
57}
58
59#[derive(Debug)]
61pub enum MultiplayBootMode {
62 None,
63 Joybus,
64 Normal,
65 Multiplay,
66 Unknown(u8),
69}
70
71impl From<u8> for MultiplayBootMode {
72 fn from(data: u8) -> Self {
73 match data {
74 0 => Self::None,
75 1 => Self::Joybus,
76 2 => Self::Normal,
77 3 => Self::Multiplay,
78 _ => Self::Unknown(data),
79 }
80 }
81}
82
83impl fmt::Display for MultiplayBootMode {
84 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
85 match self {
86 Self::None => write!(fmt, "none")?,
87 Self::Joybus => write!(fmt, "joybus")?,
88 Self::Normal => write!(fmt, "normal")?,
89 Self::Multiplay => write!(fmt, "multiplay")?,
90 Self::Unknown(data) => write!(fmt, "unknown {data}")?,
91 }
92 Ok(())
93 }
94}
95
96#[derive(Debug)]
98pub struct Metadata {
99 pub entry_point: u32,
101 pub nintendo_logo: [u8; 156],
103 pub title: String,
105 pub game_code: String,
108 pub manufacturer_code: String,
110 pub main_unit_code: u8,
112 pub device_type: u8,
115 pub software_version: u8,
117 pub header_checksum: u8,
120}
121
122#[derive(Debug)]
124pub struct MultiBootHeader {
125 pub ram_entry_point: u32,
127 pub boot_mode: MultiplayBootMode,
129 pub slave_id_number: u8,
131 pub joybus_entry_point: u32,
133}
134
135pub trait MetadataRead {
136 fn read_gba_metadata(&mut self) -> Result<Metadata, Error>;
145}
146
147impl Metadata {
148 pub fn try_from<T: Read + Seek>(io: &mut T) -> Result<Self, Error> {
154 io.read_gba_metadata()
155 }
156}
157
158fn trim(string: &str) -> &str {
160 string.trim_matches('\0').trim()
161}
162
163impl<T: Read + Seek> MetadataRead for T {
164 fn read_gba_metadata(&mut self) -> Result<Metadata, Error> {
165 self.seek(SeekFrom::Start(0))?;
166
167 let entry_point = self.read_u32::<LittleEndian>()?;
168 let mut nintendo_logo = [0u8; 156];
169 self.read_exact(&mut nintendo_logo)?;
170
171 self.seek(SeekFrom::Start(0xA0))?;
172 let mut title = [0u8; 12];
173 self.read_exact(&mut title)?;
174 let title = trim(str::from_utf8(&title)?).to_string();
175 let mut game_code = [0u8; 4];
176 self.read_exact(&mut game_code)?;
177 let game_code = trim(str::from_utf8(&game_code)?).to_string();
178 let mut manufacturer_code = [0u8; 2];
179 self.read_exact(&mut manufacturer_code)?;
180 let manufacturer_code = trim(str::from_utf8(&manufacturer_code)?).to_string();
181
182 self.seek(SeekFrom::Current(1))?;
183 let main_unit_code = self.read_u8()?;
184 let device_type = self.read_u8()?;
185 self.seek(SeekFrom::Current(7))?;
186 let software_version = self.read_u8()?;
187 let header_checksum = self.read_u8()?;
188
189 Ok(Metadata {
190 entry_point,
191 nintendo_logo,
192 title,
193 game_code,
194 manufacturer_code,
195 main_unit_code,
196 device_type,
197 software_version,
198 header_checksum,
199 })
200 }
201}
202
203impl Metadata {
204 pub fn header_checksum<T: Read + Seek>(&mut self, io: &mut T) -> Result<u8, Error> {
210 io.seek(SeekFrom::Start(0xA0))?;
211 let mut checksum = 0u8;
212 let mut data = [0u8; 0xBC - 0xA0];
213 io.read_exact(&mut data)?;
215 for byte in &data {
216 checksum = checksum.wrapping_sub(*byte);
217 }
218 checksum = checksum.wrapping_sub(0x19);
219 Ok(checksum)
220 }
221}