+189
src/enums.rs
+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
+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
+
}