1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
7pub enum GameboyColorCompatibility {
8 Monochrome,
10 ColorOptional,
12 ColorRequired,
14}
15
16impl GameboyColorCompatibility {
17 pub const fn supports_color(self) -> bool {
19 match self {
20 GameboyColorCompatibility::Monochrome => false,
21 _ => true,
22 }
23 }
24}
25
26#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
28pub enum RomType {
29 RomOnly,
30 Mbc1,
31 Mbc1Ram,
32 Mbc1RamBattery,
33 Mbc2,
34 Mbc2Battery,
35 RomRam,
36 RomRamBattery,
37 Mmm01,
38 Mmm01Sram,
39 Mmm01SramBattery,
40 Mbc3TimerBattery,
41 Mbc3TimerRamBattery,
42 Mbc3,
43 Mbc3Ram,
44 Mbc3RamBattery,
45 Mbc5,
46 Mbc5Ram,
47 Mbc5RamBattery,
48 Mbc5Rumble,
49 Mbc5RumbleSram,
50 Mbc5RumbleSramBattery,
51 PocketCamera,
52 Tama5,
53 Huc3,
54 Huc1,
55 Other(u8),
56}
57
58impl From<u8> for RomType {
59 fn from(byte: u8) -> RomType {
60 match byte {
61 0x00 => RomType::RomOnly,
62 0x01 => RomType::Mbc1,
63 0x02 => RomType::Mbc1Ram,
64 0x03 => RomType::Mbc1RamBattery,
65 0x05 => RomType::Mbc2,
66 0x06 => RomType::Mbc2Battery,
67 0x08 => RomType::RomRam,
68 0x09 => RomType::RomRamBattery,
69 0x0B => RomType::Mmm01,
70 0x0C => RomType::Mmm01Sram,
71 0x0D => RomType::Mmm01SramBattery,
72 0x0F => RomType::Mbc3TimerBattery,
73 0x10 => RomType::Mbc3TimerRamBattery,
74 0x11 => RomType::Mbc3,
75 0x12 => RomType::Mbc3Ram,
76 0x13 => RomType::Mbc3RamBattery,
77 0x19 => RomType::Mbc5,
78 0x1A => RomType::Mbc5Ram,
79 0x1B => RomType::Mbc5RamBattery,
80 0x1C => RomType::Mbc5Rumble,
81 0x1D => RomType::Mbc5RumbleSram,
82 0x1E => RomType::Mbc5RumbleSramBattery,
83 0x1F => RomType::PocketCamera,
84 0xFD => RomType::Tama5,
85 0xFE => RomType::Huc3,
86 0xFF => RomType::Huc1,
87 otherwise => RomType::Other(otherwise),
88 }
89 }
90}
91
92#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
94pub struct RomHeader<'a> {
95 pub begin_code_execution_point: &'a [u8],
96 pub scrolling_graphic: &'a [u8],
98 pub game_title: &'a str,
100 pub gameboy_color: GameboyColorCompatibility,
102 pub licensee_code_new: [u8; 2],
104 pub super_gameboy: bool,
106 pub rom_type: RomType,
108 pub rom_size: u16,
110 pub ram_banks: u8,
112 pub ram_bank_size: u16,
114 pub japanese: bool,
116 pub licensee_code: u8,
117 pub mask_rom_version: u8,
118 pub complement: u8,
119 pub checksum: u16,
121}
122
123impl<'a> RomHeader<'a> {
124 pub fn validate(&self) -> Result<(), HeaderValidationError> {
128 const XOR_RESULT: u8 = 134;
131 const SUM_RESULT: u16 = 5446;
132 const OR_RESULT: u8 = 255;
133 const AND_RESULT: u8 = 0;
134 if self
135 .scrolling_graphic
136 .iter()
137 .map(|x| *x as u16)
138 .sum::<u16>()
139 != SUM_RESULT
140 || self.scrolling_graphic.iter().fold(0, |a, b| a | b) != OR_RESULT
141 || self.scrolling_graphic.iter().fold(0, |a, b| a & b) != AND_RESULT
142 || self.scrolling_graphic.iter().fold(0, |a, b| a ^ b) != XOR_RESULT
143 {
144 return Err(HeaderValidationError::ScrollingLogoMismatch);
145 }
146 if self.super_gameboy && self.licensee_code != 0x33 {
147 return Err(HeaderValidationError::SuperGameBoyOldLicenseeCodeMismatch);
148 }
149 Ok(())
150 }
151}
152
153#[derive(Debug, PartialEq, Eq)]
155pub enum HeaderValidationError {
156 SuperGameBoyOldLicenseeCodeMismatch,
158 ScrollingLogoMismatch,
160}