Playing around with reading gameboy roms, and maybe emulation
1mod enums;
2
3use crate::enums::CartridgeHeaderAddress::OldLicenseeCode;
4use crate::enums::{
5 CGBFlag, CartridgeHeaderAddress, CartridgeType, DestinationCode, Error, RamSize, RomSize,
6};
7use std::fs::File;
8use std::io::Read;
9
10// https://github.com/ISSOtm/gb-bootroms/blob/2dce25910043ce2ad1d1d3691436f2c7aabbda00/src/dmg.asm#L259-L269
11// Each tile is encoded using 2 (!) bytes
12// The tiles are represented below
13// XX.. .XX. XX.. .... .... .... .... .... .... ...X X... ....
14// XXX. .XX. XX.. .... ..XX .... .... .... .... ...X X... ....
15// XXX. .XX. .... .... .XXX X... .... .... .... ...X X... ....
16// XX.X .XX. XX.X X.XX ..XX ..XX XX.. XX.X X... XXXX X..X XXX.
17// XX.X .XX. XX.X XX.X X.XX .XX. .XX. XXX. XX.X X..X X.XX ..XX
18// XX.. XXX. XX.X X..X X.XX .XXX XXX. XX.. XX.X X..X X.XX ..XX
19// XX.. XXX. XX.X X..X X.XX .XX. .... XX.. XX.X X..X X.XX ..XX
20// XX.. .XX. XX.X X..X X.XX ..XX XXX. XX.. XX.. XXXX X..X XXX.
21const NINTENDO_LOGO: [u8; 48] = [
22 0x0CE, 0x0ED, 0x066, 0x066, 0x0CC, 0x00D, 0x000, 0x00B, 0x003, 0x073, 0x000, 0x083, 0x000,
23 0x00C, 0x000, 0x00D, 0x000, 0x008, 0x011, 0x01F, 0x088, 0x089, 0x000, 0x00E, 0x0DC, 0x0CC,
24 0x06E, 0x0E6, 0x0DD, 0x0DD, 0x0D9, 0x099, 0x0BB, 0x0BB, 0x067, 0x063, 0x06E, 0x00E, 0x0EC,
25 0x0CC, 0x0DD, 0x0DC, 0x099, 0x09F, 0x0BB, 0x0B9, 0x033, 0x03E,
26];
27
28// const ROM_NAME: &str = "OtherLegallyObtainedRom.gb";
29const ROM_NAME: &str = "LegallyObtainedRom.gb";
30
31struct CartridgeHeader {
32 //Should be 80 bytes (0x014F(335) - 0x0100(256)) + 1 to include the last address
33 buffer: [u8; 80],
34 title: [char; 16],
35 manufacturer_code: [char; 4],
36 cgb_flag: CGBFlag,
37 // https://gbdev.io/pandocs/The_Cartridge_Header.html#01440145--new-licensee-code
38 license_code: [char; 2],
39 support_gb: bool,
40 cart_type: CartridgeType,
41 rom_size: RomSize,
42 ram_size: RamSize,
43 old_licensee_code: Option<u8>,
44 destination_code: DestinationCode,
45 version: u8,
46 header_checksum: u8,
47 global_checksum: u16,
48}
49
50impl CartridgeHeader {
51 fn bytes_to_chars<const N: usize>(bytes: &[u8], break_on_null: bool) -> [char; N] {
52 let mut chars = [0x000 as char; N];
53 for (i, byte) in bytes.iter().enumerate() {
54 if break_on_null && *byte == 0x00 {
55 break;
56 }
57 chars[i] = *byte as char;
58 }
59 chars
60 }
61
62 fn parse(rom_bytes: &[u8]) -> Result<Self, Error> {
63 let start = CartridgeHeaderAddress::CartridgeHeaderStart as usize;
64 let end = start + 80;
65 let header_buffer: &[u8] = rom_bytes[start..end]
66 .try_into()
67 .map_err(|_| Error::CartridgeReadError)?;
68
69 //Checks if the Nintendo logo matches the device's version. Early anti piracy feature. Neat to take note of
70 let nintendo_logo_from_rom = &rom_bytes[CartridgeHeaderAddress::NintendoLogoStart as usize
71 ..CartridgeHeaderAddress::NintendoLogoEnd as usize + 1];
72 for (i, true_logo_byte) in NINTENDO_LOGO.iter().enumerate() {
73 let rom_byte = nintendo_logo_from_rom[i];
74 if rom_byte != *true_logo_byte {
75 return Err(Error::CartridgeReadError);
76 }
77 }
78
79 let title = &rom_bytes[CartridgeHeaderAddress::TitleStart as usize
80 ..CartridgeHeaderAddress::TitleEnd as usize + 1];
81 let title_chars = Self::bytes_to_chars::<16>(title, true);
82
83 let manufacturer_code = &rom_bytes[CartridgeHeaderAddress::ManufacturerCodeStart as usize
84 ..CartridgeHeaderAddress::ManufacturerCodeEnd as usize + 1];
85 let man_code_chars = Self::bytes_to_chars::<4>(manufacturer_code, false);
86
87 let cgb_flag_byte = rom_bytes[0x0143];
88 let cgb_flag = match cgb_flag_byte {
89 0x80 => CGBFlag::SupportsColorBackwardCompatiable,
90 0xC0 => CGBFlag::ColorOnly,
91 _ => CGBFlag::NotSet,
92 };
93
94 let license_code = &rom_bytes[CartridgeHeaderAddress::NewLicenseStart as usize
95 ..CartridgeHeaderAddress::NewLicenseEnd as usize + 1];
96 let license_code_chars = Self::bytes_to_chars::<2>(license_code, false);
97
98 // https://gbdev.io/pandocs/The_Cartridge_Header.html#0146--sgb-flag
99 let sgb_flag = rom_bytes[CartridgeHeaderAddress::SgbFlag as usize] == 0x03;
100
101 let cart_type =
102 CartridgeType::from_byte(rom_bytes[CartridgeHeaderAddress::CartType as usize]);
103
104 let rom_size = RomSize::from_byte(rom_bytes[CartridgeHeaderAddress::RomSize as usize]);
105 let ram_size = RamSize::from_byte(rom_bytes[CartridgeHeaderAddress::RamSize as usize]);
106
107 let mut old_licensee_code = None;
108 if !sgb_flag {
109 old_licensee_code = Some(rom_bytes[CartridgeHeaderAddress::OldLicenseeCode as usize])
110 }
111
112 let destination_code =
113 DestinationCode::from_byte(rom_bytes[CartridgeHeaderAddress::DestinationCode as usize]);
114
115 let version = rom_bytes[CartridgeHeaderAddress::MaskRomVersion as usize];
116 let header_checksum = rom_bytes[CartridgeHeaderAddress::HeaderChecksum as usize];
117 let global_checksum = u16::from_le_bytes([
118 rom_bytes[CartridgeHeaderAddress::GlobalChecksumStart as usize],
119 rom_bytes[CartridgeHeaderAddress::CartridgeHeaderEnd as usize],
120 ]);
121
122 Ok(Self {
123 buffer: header_buffer
124 .try_into()
125 .map_err(|_| Error::CartridgeReadError)?,
126 title: title_chars,
127 manufacturer_code: man_code_chars,
128 cgb_flag,
129 license_code: license_code_chars,
130 support_gb: sgb_flag,
131 cart_type,
132 rom_size,
133 ram_size,
134 old_licensee_code,
135 destination_code,
136 version,
137 header_checksum,
138 global_checksum,
139 })
140 }
141
142 fn print_test(&self) {
143 for byte in self.buffer.iter() {
144 print!("{} ", *byte as char);
145 }
146 }
147}
148
149fn main() -> std::io::Result<()> {
150 let mut rom_file = File::open(ROM_NAME)?;
151 let mut rom_buffer: Vec<u8> = Vec::new();
152 rom_file.read_to_end(&mut rom_buffer)?;
153 let cart_header = match CartridgeHeader::parse(&*rom_buffer) {
154 Ok(header) => header,
155 Err(err) => {
156 return Err(std::io::Error::new(
157 std::io::ErrorKind::Other,
158 "Rom failed to parse",
159 ));
160 }
161 };
162 // cart_header.print_test();
163
164 let title: String = String::from_iter(cart_header.title);
165 println!("Title: {}", title);
166 let manufacturer_code = String::from_iter(cart_header.manufacturer_code);
167 println!("Manufacturer Code: {}", manufacturer_code);
168
169 match cart_header.old_licensee_code {
170 Some(code) => println!("Uses Old Licensee Code: {:#X}", code),
171 None => println!(
172 "Uses New Licensee Code: {}",
173 String::from_iter(cart_header.license_code)
174 ),
175 }
176
177 println!("CGB Flag: {:?}", cart_header.cgb_flag);
178 println!("Supports SGB: {:?}", cart_header.support_gb);
179 println!("Cartridge Type: {:?}", cart_header.cart_type);
180 println!("ROM Size: {:?}", cart_header.rom_size);
181 println!("RAM Size: {:?}", cart_header.ram_size);
182 match cart_header.destination_code {
183 DestinationCode::Japan => println!("Destination Code: Japan"),
184 DestinationCode::NotJapan => println!("Destination Code: Not Japan"),
185 }
186 println!("Version: {:?}", cart_header.version);
187 println!("Header Checksum: {:#X}", cart_header.header_checksum);
188 println!("Global Checksum: {:#X}", cart_header.global_checksum);
189
190 Ok(())
191}