1use serde::Serialize;
10
11use crate::error::RomAnalyzerError;
12use crate::region::{Region, check_region_mismatch};
13
14#[derive(Debug, PartialEq, Clone, Serialize)]
16pub struct GbaAnalysis {
17 pub source_name: String,
19 pub region: Region,
21 pub region_string: String,
23 pub region_mismatch: bool,
25 pub game_title: String,
27 pub game_code: String,
29 pub maker_code: String,
31}
32
33impl GbaAnalysis {
34 pub fn print(&self) -> String {
36 format!(
37 "{}\n\
38 System: Game Boy Advance (GBA)\n\
39 Game Title: {}\n\
40 Game Code: {}\n\
41 Maker Code: {}\n\
42 Region: {}",
43 self.source_name, self.game_title, self.game_code, self.maker_code, self.region
44 )
45 }
46}
47
48pub fn map_region(region_byte: u8) -> (&'static str, Region) {
83 match region_byte {
84 0x00 => ("Japan", Region::JAPAN),
85 0x01 => ("USA", Region::USA),
86 0x02 => ("Europe", Region::EUROPE),
87 b'J' => ("Japan", Region::JAPAN),
89 b'U' => ("USA", Region::USA),
90 b'E' => ("Europe", Region::EUROPE),
91 b'P' => ("Europe", Region::EUROPE), _ => ("Unknown", Region::UNKNOWN),
93 }
94}
95
96pub fn analyze_gba_data(data: &[u8], source_name: &str) -> Result<GbaAnalysis, RomAnalyzerError> {
113 const HEADER_SIZE: usize = 0xC0;
116 if data.len() < HEADER_SIZE {
117 return Err(RomAnalyzerError::DataTooSmall {
118 file_size: data.len(),
119 required_size: HEADER_SIZE,
120 details: "GBA header".to_string(),
121 });
122 }
123
124 let game_title = String::from_utf8_lossy(&data[0xA0..0xAC])
126 .trim_matches(char::from(0)) .to_string();
128
129 let game_code = String::from_utf8_lossy(&data[0xAC..0xB0])
131 .trim_matches(char::from(0)) .to_string();
133
134 let maker_code = String::from_utf8_lossy(&data[0xB0..0xB2])
136 .trim_matches(char::from(0)) .to_string();
138
139 let region_code_byte = data[0xB4];
141
142 let (region_name, region) = map_region(region_code_byte);
144
145 let region_mismatch = check_region_mismatch(source_name, region);
146
147 Ok(GbaAnalysis {
148 source_name: source_name.to_string(),
149 region,
150 region_string: region_name.to_string(),
151 region_mismatch,
152 game_title,
153 game_code,
154 maker_code,
155 })
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 fn generate_gba_header(
164 game_code: &str,
165 maker_code: &str,
166 region_byte: u8,
167 title: &str,
168 ) -> Vec<u8> {
169 let mut data = vec![0; 0xC0]; let mut title_bytes = title.as_bytes().to_vec();
173 title_bytes.resize(12, 0);
174 data[0xA0..0xAC].copy_from_slice(&title_bytes);
175
176 let mut game_code_bytes = game_code.as_bytes().to_vec();
178 game_code_bytes.resize(4, 0);
179 data[0xAC..0xB0].copy_from_slice(&game_code_bytes);
180
181 let mut maker_code_bytes = maker_code.as_bytes().to_vec();
183 maker_code_bytes.resize(2, 0);
184 data[0xB0..0xB2].copy_from_slice(&maker_code_bytes);
185
186 data[0xB4] = region_byte;
188
189 data
190 }
191
192 #[test]
193 fn test_analyze_gba_data_japan_code() -> Result<(), RomAnalyzerError> {
194 let data = generate_gba_header("ABCD", "XX", 0x00, "GBA JP GAME"); let analysis = analyze_gba_data(&data, "test_rom_jp.gba")?;
196
197 assert_eq!(analysis.source_name, "test_rom_jp.gba");
198 assert_eq!(analysis.game_title, "GBA JP GAME");
199 assert_eq!(analysis.game_code, "ABCD");
200 assert_eq!(analysis.maker_code, "XX");
201 assert_eq!(analysis.region, Region::JAPAN);
202 assert_eq!(analysis.region_string, "Japan");
203 assert_eq!(
204 analysis.print(),
205 "test_rom_jp.gba\n\
206 System: Game Boy Advance (GBA)\n\
207 Game Title: GBA JP GAME\n\
208 Game Code: ABCD\n\
209 Maker Code: XX\n\
210 Region: Japan"
211 );
212 Ok(())
213 }
214
215 #[test]
216 fn test_analyze_gba_data_pal_char() -> Result<(), RomAnalyzerError> {
217 let data = generate_gba_header("YZAB", "DD", b'P', "GBA PAL GAME"); let analysis = analyze_gba_data(&data, "test_rom_pal.gba")?;
219
220 assert_eq!(analysis.source_name, "test_rom_pal.gba");
221 assert_eq!(analysis.game_title, "GBA PAL GAME");
222 assert_eq!(analysis.game_code, "YZAB");
223 assert_eq!(analysis.maker_code, "DD");
224 assert_eq!(analysis.region, Region::EUROPE);
225 assert_eq!(analysis.region_string, "Europe");
226 assert_eq!(
227 analysis.print(),
228 "test_rom_pal.gba\n\
229 System: Game Boy Advance (GBA)\n\
230 Game Title: GBA PAL GAME\n\
231 Game Code: YZAB\n\
232 Maker Code: DD\n\
233 Region: Europe"
234 );
235 Ok(())
236 }
237
238 #[test]
239 fn test_analyze_gba_data_europe_char() -> Result<(), RomAnalyzerError> {
240 let data = generate_gba_header("IJKL", "ZZ", b'E', "GBA EUR GAME"); let analysis = analyze_gba_data(&data, "test_rom_eur.gba")?;
242
243 assert_eq!(analysis.source_name, "test_rom_eur.gba");
244 assert_eq!(analysis.game_title, "GBA EUR GAME");
245 assert_eq!(analysis.game_code, "IJKL");
246 assert_eq!(analysis.maker_code, "ZZ");
247 assert_eq!(analysis.region, Region::EUROPE);
248 assert_eq!(analysis.region_string, "Europe");
249 Ok(())
250 }
251
252 #[test]
253 fn test_analyze_gba_data_japan_char() -> Result<(), RomAnalyzerError> {
254 let data = generate_gba_header("MNOP", "AA", b'J', "GBA JP CHAR"); let analysis = analyze_gba_data(&data, "test_rom_jp_char.gba")?;
256
257 assert_eq!(analysis.source_name, "test_rom_jp_char.gba");
258 assert_eq!(analysis.game_title, "GBA JP CHAR");
259 assert_eq!(analysis.game_code, "MNOP");
260 assert_eq!(analysis.maker_code, "AA");
261 assert_eq!(analysis.region, Region::JAPAN);
262 assert_eq!(analysis.region_string, "Japan");
263 Ok(())
264 }
265
266 #[test]
267 fn test_analyze_gba_data_usa_char() -> Result<(), RomAnalyzerError> {
268 let data = generate_gba_header("UVWX", "CC", b'U', "GBA US CHAR"); let analysis = analyze_gba_data(&data, "test_rom_us_char.gba")?;
270
271 assert_eq!(analysis.source_name, "test_rom_us_char.gba");
272 assert_eq!(analysis.game_title, "GBA US CHAR");
273 assert_eq!(analysis.game_code, "UVWX");
274 assert_eq!(analysis.maker_code, "CC");
275 assert_eq!(analysis.region, Region::USA);
276 assert_eq!(analysis.region_string, "USA");
277 assert_eq!(
278 analysis.print(),
279 "test_rom_us_char.gba\n\
280 System: Game Boy Advance (GBA)\n\
281 Game Title: GBA US CHAR\n\
282 Game Code: UVWX\n\
283 Maker Code: CC\n\
284 Region: USA"
285 );
286 Ok(())
287 }
288
289 #[test]
290 fn test_analyze_gba_data_too_small() {
291 let data = vec![0; 50]; let result = analyze_gba_data(&data, "too_small.gba");
294 assert!(result.is_err());
295 assert!(result.unwrap_err().to_string().contains("too small"));
296 }
297}