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}