1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//! Data types related to the ROM header that the parser can produce.

use serde::{Deserialize, Serialize};

/// The ROM's declared use of Gameboy Color features
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
pub enum GameboyColorCompatibility {
    /// The ROM indicates that it does not make use of any Gameboy Color enhancements
    Monochrome,
    /// The ROM supports but does not require Gameboy Color
    ColorOptional,
    /// The ROM requires Gameboy Color enhancements
    ColorRequired,
}

impl GameboyColorCompatibility {
    /// Whether or not the ROM declares it uses GameBoy Color features
    pub const fn supports_color(self) -> bool {
        match self {
            GameboyColorCompatibility::Monochrome => false,
            _ => true,
        }
    }
}

/// The ROM type as a convenient enum
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
pub enum RomType {
    RomOnly,
    Mbc1,
    Mbc1Ram,
    Mbc1RamBattery,
    Mbc2,
    Mbc2Battery,
    RomRam,
    RomRamBattery,
    Mmm01,
    Mmm01Sram,
    Mmm01SramBattery,
    Mbc3TimerBattery,
    Mbc3TimerRamBattery,
    Mbc3,
    Mbc3Ram,
    Mbc3RamBattery,
    Mbc5,
    Mbc5Ram,
    Mbc5RamBattery,
    Mbc5Rumble,
    Mbc5RumbleSram,
    Mbc5RumbleSramBattery,
    PocketCamera,
    Tama5,
    Huc3,
    Huc1,
    Other(u8),
}

impl From<u8> for RomType {
    fn from(byte: u8) -> RomType {
        match byte {
            0x00 => RomType::RomOnly,
            0x01 => RomType::Mbc1,
            0x02 => RomType::Mbc1Ram,
            0x03 => RomType::Mbc1RamBattery,
            0x05 => RomType::Mbc2,
            0x06 => RomType::Mbc2Battery,
            0x08 => RomType::RomRam,
            0x09 => RomType::RomRamBattery,
            0x0B => RomType::Mmm01,
            0x0C => RomType::Mmm01Sram,
            0x0D => RomType::Mmm01SramBattery,
            0x0F => RomType::Mbc3TimerBattery,
            0x10 => RomType::Mbc3TimerRamBattery,
            0x11 => RomType::Mbc3,
            0x12 => RomType::Mbc3Ram,
            0x13 => RomType::Mbc3RamBattery,
            0x19 => RomType::Mbc5,
            0x1A => RomType::Mbc5Ram,
            0x1B => RomType::Mbc5RamBattery,
            0x1C => RomType::Mbc5Rumble,
            0x1D => RomType::Mbc5RumbleSram,
            0x1E => RomType::Mbc5RumbleSramBattery,
            0x1F => RomType::PocketCamera,
            0xFD => RomType::Tama5,
            0xFE => RomType::Huc3,
            0xFF => RomType::Huc1,
            otherwise => RomType::Other(otherwise),
        }
    }
}

/// Metadata about the ROM
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RomHeader<'a> {
    pub begin_code_execution_point: &'a [u8],
    /// Logo at the start, should match Nintendo Logo
    pub scrolling_graphic: &'a [u8],
    /// up to 10 ASCII characters
    pub game_title: &'a str,
    /// gbc bit
    pub gameboy_color: GameboyColorCompatibility,
    /// 2 ASCII hex digits or zeros
    pub licensee_code_new: [u8; 2],
    /// sgb bit
    pub super_gameboy: bool,
    /// how the data after the header will be parsed
    pub rom_type: RomType,
    /// How many 16KB ROM banks to use
    pub rom_size: u16,
    /// How many RAM banks are available on the cart
    pub ram_banks: u8,
    /// The size of the RAM bank in bytes (normal values are 2kB and 8kB)
    pub ram_bank_size: u16,
    /// jp bit
    pub japanese: bool,
    pub licensee_code: u8,
    pub mask_rom_version: u8,
    pub complement: u8,
    /// the sum of all bytes in the ROM except these two bytes, truncated to 2 bytes
    pub checksum: u16,
}

impl<'a> RomHeader<'a> {
    /// checks that the ROM header is internally consistent.
    /// warning: this doesn't guarantee that the entire ROM header is well formed
    /// TODO: consider parsing into unvalidated form (just segmented bytes and then doing a translation step...)
    pub fn validate(&self) -> Result<(), HeaderValidationError> {
        // TODO: look into if international copyright law actually protects these 48 bytes
        // for now just validate proxy metrics of the logo
        const XOR_RESULT: u8 = 134;
        const SUM_RESULT: u16 = 5446;
        const OR_RESULT: u8 = 255;
        const AND_RESULT: u8 = 0;
        if self
            .scrolling_graphic
            .iter()
            .map(|x| *x as u16)
            .sum::<u16>()
            != SUM_RESULT
            || self.scrolling_graphic.iter().fold(0, |a, b| a | b) != OR_RESULT
            || self.scrolling_graphic.iter().fold(0, |a, b| a & b) != AND_RESULT
            || self.scrolling_graphic.iter().fold(0, |a, b| a ^ b) != XOR_RESULT
        {
            return Err(HeaderValidationError::ScrollingLogoMismatch);
        }
        if self.super_gameboy && self.licensee_code != 0x33 {
            return Err(HeaderValidationError::SuperGameBoyOldLicenseeCodeMismatch);
        }
        Ok(())
    }
}

/// Errors that may occur while attempting to validate a ROM header.
#[derive(Debug, PartialEq, Eq)]
pub enum HeaderValidationError {
    /// SGB requires the old licensee code to be 0x33
    SuperGameBoyOldLicenseeCodeMismatch,
    /// Apparent mismatch on scrolling logo
    ScrollingLogoMismatch,
}