Skip to main content

gameboy_rom/
header.rs

1//! Data types related to the ROM header that the parser can produce.
2
3use serde::{Deserialize, Serialize};
4
5/// The ROM's declared use of Gameboy Color features
6#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
7pub enum GameboyColorCompatibility {
8    /// The ROM indicates that it does not make use of any Gameboy Color enhancements
9    Monochrome,
10    /// The ROM supports but does not require Gameboy Color
11    ColorOptional,
12    /// The ROM requires Gameboy Color enhancements
13    ColorRequired,
14}
15
16impl GameboyColorCompatibility {
17    /// Whether or not the ROM declares it uses GameBoy Color features
18    pub const fn supports_color(self) -> bool {
19        match self {
20            GameboyColorCompatibility::Monochrome => false,
21            _ => true,
22        }
23    }
24}
25
26/// The ROM type as a convenient enum
27#[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/// Metadata about the ROM
93#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
94pub struct RomHeader<'a> {
95    pub begin_code_execution_point: &'a [u8],
96    /// Logo at the start, should match Nintendo Logo
97    pub scrolling_graphic: &'a [u8],
98    /// up to 10 ASCII characters
99    pub game_title: &'a str,
100    /// gbc bit
101    pub gameboy_color: GameboyColorCompatibility,
102    /// 2 ASCII hex digits or zeros
103    pub licensee_code_new: [u8; 2],
104    /// sgb bit
105    pub super_gameboy: bool,
106    /// how the data after the header will be parsed
107    pub rom_type: RomType,
108    /// How many 16KB ROM banks to use
109    pub rom_size: u16,
110    /// How many RAM banks are available on the cart
111    pub ram_banks: u8,
112    /// The size of the RAM bank in bytes (normal values are 2kB and 8kB)
113    pub ram_bank_size: u16,
114    /// jp bit
115    pub japanese: bool,
116    pub licensee_code: u8,
117    pub mask_rom_version: u8,
118    pub complement: u8,
119    /// the sum of all bytes in the ROM except these two bytes, truncated to 2 bytes
120    pub checksum: u16,
121}
122
123impl<'a> RomHeader<'a> {
124    /// checks that the ROM header is internally consistent.
125    /// warning: this doesn't guarantee that the entire ROM header is well formed
126    /// TODO: consider parsing into unvalidated form (just segmented bytes and then doing a translation step...)
127    pub fn validate(&self) -> Result<(), HeaderValidationError> {
128        // TODO: look into if international copyright law actually protects these 48 bytes
129        // for now just validate proxy metrics of the logo
130        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/// Errors that may occur while attempting to validate a ROM header.
154#[derive(Debug, PartialEq, Eq)]
155pub enum HeaderValidationError {
156    /// SGB requires the old licensee code to be 0x33
157    SuperGameBoyOldLicenseeCodeMismatch,
158    /// Apparent mismatch on scrolling logo
159    ScrollingLogoMismatch,
160}