Playing around with reading gameboy roms, and maybe emulation

header should be there i think

Changed files
+348 -28
src
+1
.gitignore
··· 1 1 /target 2 2 /LegallyObtainedRom.gb 3 + /OtherLegallyObtainedRom.gb 3 4 .idea/
+189
src/enums.rs
··· 1 + pub enum CartridgeHeaderAddress { 2 + // Whole Header 3 + CartridgeHeaderStart = 0x0100, 4 + CartridgeHeaderEnd = 0x014F, 5 + // 0100-0103 — Entry point 6 + EntryPointEnd = 0x0103, 7 + // 0104-0133 — Nintendo logo 8 + NintendoLogoStart = 0x0104, 9 + NintendoLogoEnd = 0x00133, 10 + // 0134-0143 — Title 11 + TitleStart = 0x0134, 12 + TitleEnd = 0x0143, 13 + // 013F-0142 — Manufacturer code 14 + ManufacturerCodeStart = 0x013F, 15 + ManufacturerCodeEnd = 0x0142, 16 + // 0144–0145 — New licensee code 17 + NewLicenseStart = 0x0144, 18 + NewLicenseEnd = 0x0145, 19 + // 0146 — SGB flag 20 + SgbFlag = 0x0146, 21 + // 0147 — Cartridge type 22 + CartType = 0x0147, 23 + // 0148 — ROM size 24 + RomSize = 0x0148, 25 + // 0149 - RAM Size, 26 + RamSize = 0x0149, 27 + // 014A — Destination code 28 + DestinationCode = 0x014A, 29 + // 014B — Old licensee code 30 + OldLicenseeCode = 0x014b, 31 + // 014C — Mask ROM version number, 32 + MaskRomVersion = 0x014C, 33 + // 014D — Header checksum 34 + HeaderChecksum = 0x014D, 35 + // 014E-014F — Global checksum 36 + GlobalChecksumStart = 0x014E, 37 + //End is the end of cartheader 38 + } 39 + 40 + pub enum Error { 41 + CartridgeReadError, 42 + //Nintendo logos do not match 43 + NotAValidRom, 44 + } 45 + 46 + pub enum DestinationCode { 47 + Japan = 0x00, 48 + NotJapan = 0x01, 49 + } 50 + 51 + impl DestinationCode { 52 + pub fn from_byte(byte: u8) -> Self { 53 + match byte { 54 + 0x00 => Self::Japan, 55 + 0x01 | _ => Self::NotJapan, 56 + } 57 + } 58 + } 59 + 60 + #[derive(Debug)] 61 + pub enum RamSize { 62 + NoRAM = 0x000, 63 + Unused = 0x001, 64 + Ram8KiB = 0x002, 65 + Ram32KiB = 0x003, 66 + Ram128KiB = 0x004, 67 + Ram64KiB = 0x005, 68 + } 69 + 70 + impl RamSize { 71 + pub fn from_byte(byte: u8) -> Self { 72 + match byte { 73 + 0x001 => Self::Unused, 74 + 0x002 => Self::Ram8KiB, 75 + 0x003 => Self::Ram32KiB, 76 + 0x004 => Self::Ram128KiB, 77 + 0x005 => Self::Ram64KiB, 78 + _ => Self::NoRAM, 79 + } 80 + } 81 + } 82 + #[derive(Debug)] 83 + pub enum CartridgeType { 84 + RomOnly = 0x000, 85 + MBC1 = 0x001, 86 + Mbc1Ram = 0x002, 87 + Mbc1RamBattery = 0x003, 88 + MBC2 = 0x005, 89 + Mbc2Battery = 0x006, 90 + RomRam9 = 0x008, 91 + RomRamBattery9 = 0x009, 92 + MMM01 = 0x00B, 93 + Mmm01Ram = 0x00C, 94 + Mmm01RamBattery = 0x00D, 95 + Mbc3TimerBattery = 0x00F, 96 + Mbc3TimerRamBattery10 = 0x010, 97 + MBC3 = 0x011, 98 + Mbc3Ram10 = 0x012, 99 + Mbc3RaBattery10 = 0x013, 100 + MBC5 = 0x019, 101 + Mbc5Ram = 0x01A, 102 + Mbc5RamBattery = 0x01B, 103 + Mbc5Rumble = 0x01C, 104 + Mbc5RumbleRam = 0x01D, 105 + Mbc5RumbleRamBattery = 0x01E, 106 + MBC6 = 0x020, 107 + Mbc7SensorRumbleRamBattery = 0x022, 108 + PocketCamera = 0x0FC, 109 + BandaiTama5 = 0x0FD, 110 + HuC3 = 0x0FE, 111 + HuC1RamBattery = 0x0FF, 112 + } 113 + 114 + impl CartridgeType { 115 + pub fn from_byte(byte: u8) -> Self { 116 + match byte { 117 + 0x001 => Self::MBC1, 118 + 0x002 => Self::Mbc1Ram, 119 + 0x003 => Self::Mbc1RamBattery, 120 + 0x005 => Self::MBC2, 121 + 0x006 => Self::Mbc2Battery, 122 + 0x008 => Self::RomRam9, 123 + 0x009 => Self::RomRamBattery9, 124 + 0x00B => Self::MMM01, 125 + 0x00C => Self::Mmm01Ram, 126 + 0x00D => Self::Mmm01RamBattery, 127 + 0x00F => Self::Mbc3TimerBattery, 128 + 0x010 => Self::Mbc3TimerRamBattery10, 129 + 0x011 => Self::MBC3, 130 + 0x012 => Self::Mbc3Ram10, 131 + 0x013 => Self::Mbc3RaBattery10, 132 + 0x019 => Self::MBC5, 133 + 0x01A => Self::Mbc5Ram, 134 + 0x01B => Self::Mbc5RamBattery, 135 + 0x01C => Self::Mbc5Rumble, 136 + 0x01D => Self::Mbc5RumbleRam, 137 + 0x01E => Self::Mbc5RumbleRamBattery, 138 + 0x020 => Self::MBC6, 139 + 0x022 => Self::Mbc7SensorRumbleRamBattery, 140 + 0x0FC => Self::PocketCamera, 141 + 0x0FD => Self::BandaiTama5, 142 + 0x0FE => Self::HuC3, 143 + 0x0FF => Self::HuC1RamBattery, 144 + 0x000 | _ => Self::RomOnly, 145 + } 146 + } 147 + } 148 + 149 + #[derive(Debug)] 150 + pub enum RomSize { 151 + Rom32KiB = 0x000, 152 + Rom64KiB = 0x001, 153 + Rom128KiB = 0x002, 154 + Rom256KiB = 0x003, 155 + Rom512KiB = 0x004, 156 + Rom1MiB = 0x005, 157 + Rom2MiB = 0x006, 158 + Rom4MiB = 0x007, 159 + Rom8MiB = 0x008, 160 + Rom1_1MiB = 0x052, 161 + Rom1_2MiB = 0x053, 162 + Rom1_5MiB = 0x054, 163 + } 164 + 165 + impl RomSize { 166 + pub fn from_byte(byte: u8) -> Self { 167 + match byte { 168 + 0x001 => Self::Rom64KiB, 169 + 0x002 => Self::Rom128KiB, 170 + 0x003 => Self::Rom256KiB, 171 + 0x004 => Self::Rom512KiB, 172 + 0x005 => Self::Rom1MiB, 173 + 0x006 => Self::Rom2MiB, 174 + 0x007 => Self::Rom4MiB, 175 + 0x008 => Self::Rom8MiB, 176 + 0x052 => Self::Rom1_1MiB, 177 + 0x053 => Self::Rom1_2MiB, 178 + 0x054 => Self::Rom1_5MiB, 179 + 0x000 | _ => Self::Rom32KiB, 180 + } 181 + } 182 + } 183 + 184 + #[derive(Debug)] 185 + pub enum CGBFlag { 186 + SupportsColorBackwardCompatiable, 187 + ColorOnly, 188 + NotSet, 189 + }
+158 -28
src/main.rs
··· 1 + mod enums; 2 + 3 + use crate::enums::CartridgeHeaderAddress::OldLicenseeCode; 4 + use crate::enums::{ 5 + CGBFlag, CartridgeHeaderAddress, CartridgeType, DestinationCode, Error, RamSize, RomSize, 6 + }; 1 7 use std::fs::File; 2 - use std::io::{Read, Seek, SeekFrom, Write}; 8 + use std::io::Read; 3 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. 21 + const 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 + ]; 4 27 28 + // const ROM_NAME: &str = "OtherLegallyObtainedRom.gb"; 5 29 const ROM_NAME: &str = "LegallyObtainedRom.gb"; 6 - 7 - pub enum Error { 8 - CartridgeReadError, 9 - } 10 30 11 31 struct CartridgeHeader { 12 32 //Should be 80 bytes (0x014F(335) - 0x0100(256)) + 1 to include the last address 13 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, 14 48 } 15 49 50 + impl 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 + } 16 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)?; 17 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 + } 18 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); 19 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); 20 86 21 - impl CartridgeHeader { 22 - fn new(rom_bytes: &[u8] ) -> Result<Self, Error> { 23 - let start = CartridgeHeaderAddress::CartridgeHeaderStart as usize; 24 - //We know the header is only 80 chars 25 - let end = start + 80; 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 + ]); 26 121 27 122 Ok(Self { 28 - buffer: rom_bytes[start..end].try_into().map_err(|_| Error::CartridgeReadError)? 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, 29 139 }) 30 140 } 31 141 ··· 36 146 } 37 147 } 38 148 39 - 40 - pub enum CartridgeHeaderAddress { 41 - CartridgeHeaderStart = 0x0100, 42 - CartridgeHeaderEnd = 0x014F 43 - } 44 - 45 - 46 149 fn main() -> std::io::Result<()> { 47 150 let mut rom_file = File::open(ROM_NAME)?; 48 151 let mut rom_buffer: Vec<u8> = Vec::new(); 49 152 rom_file.read_to_end(&mut rom_buffer)?; 50 - let cart_header = CartridgeHeader::new(&*rom_buffer).map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, "Rom failed to parse"))?; 51 - cart_header.print_test(); 52 - Ok(()) 53 - } 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(); 54 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); 55 168 56 - fn buffer_from_file(path: &str) -> Vec<u8> { 57 - let mut file = std::fs::File::open(path).expect("File not there"); 58 - let mut buffer = Vec::new(); 59 - file.read_to_end(&mut buffer).expect("Could not read file"); 60 - buffer 61 - } 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 + }