Skip to content

Commit 5bda6a1

Browse files
committed
core: add cli option for overriding mbc type
You can know pass, for example, `--mbc='MBC1,0x8000,0x2000'` to the cli to override the mbc type, rom size and ram size.
1 parent 625334e commit 5bda6a1

File tree

5 files changed

+352
-98
lines changed

5 files changed

+352
-98
lines changed

core/src/gameboy/cartridge.rs

Lines changed: 208 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,174 @@ enum Mbc {
166166
Mbc5(Mbc5),
167167
}
168168

169+
enum MbcKind {
170+
Mbc0,
171+
Mbc1,
172+
Mbc1M,
173+
Mbc2,
174+
Mbc3,
175+
Mbc5,
176+
}
177+
178+
pub struct MbcSpecification {
179+
// The mbc type, as indicated in the cartridge header.
180+
kind: MbcKind,
181+
// The rom size, in bytes. Should be a value in ROM_SIZES.
182+
rom_size: usize,
183+
// The ram size, in bytes. Should be a value in RAM_SIZES.
184+
ram_size: usize,
185+
}
186+
187+
impl MbcSpecification {
188+
fn from_str(string: &str) -> Result<Self, String> {
189+
let mut parts = string.split(',');
190+
191+
let error_message = "expected 3 comma separated values";
192+
193+
let kind = parts.next().ok_or(error_message)?;
194+
let rom_size = parts.next().ok_or(error_message)?;
195+
let ram_size = parts.next().ok_or(error_message)?;
196+
197+
if parts.next().is_some() {
198+
return Err(error_message.to_string());
199+
}
200+
201+
let kind = match kind {
202+
"MBC1" => MbcKind::Mbc1,
203+
"MBC1M" => MbcKind::Mbc1M,
204+
"MBC2" => MbcKind::Mbc2,
205+
"MBC3" => MbcKind::Mbc3,
206+
"MBC5" => MbcKind::Mbc5,
207+
_ => return Err(format!("invalid mbc type '{}'", kind)),
208+
};
209+
210+
let rom_size = crate::parser::parse_size(rom_size)
211+
.ok()
212+
.or_else(|| crate::parser::parse_number(rom_size).ok())
213+
.ok_or_else(|| format!("invalid rom size '{}'", rom_size))?
214+
as usize;
215+
216+
ROM_SIZES
217+
.iter()
218+
.find(|&&x| x == rom_size)
219+
.ok_or_else(|| format!("invalid rom size '{}'", rom_size))?;
220+
221+
let ram_size = crate::parser::parse_size(ram_size)
222+
.ok()
223+
.or_else(|| crate::parser::parse_number(ram_size).ok())
224+
.ok_or_else(|| format!("invalid ram size '{}'", ram_size))?
225+
as usize;
226+
227+
RAM_SIZES
228+
.iter()
229+
.find(|&&x| x == ram_size)
230+
.ok_or_else(|| format!("invalid ram size '{}'", ram_size))?;
231+
232+
Ok(Self {
233+
kind,
234+
rom_size,
235+
ram_size,
236+
})
237+
}
238+
239+
fn from_header(header: &CartridgeHeader, error: &mut String, rom: &[u8]) -> Option<Self> {
240+
let rom_size = header
241+
.rom_size_in_bytes()
242+
.ok_or_else(|| format!("Rom size type '{:02x}' is not supported", header.rom_size));
243+
244+
let rom_size = match rom_size {
245+
Ok(rom_size) if rom_size != rom.len() => {
246+
let size = *ROM_SIZES.iter().find(|&&x| x >= rom.len()).unwrap();
247+
writeln!(
248+
error,
249+
"The ROM header expected rom size as '{}' bytes, but the given rom has '{}' bytes. Deducing size from ROM size as {}.",
250+
rom_size,
251+
rom.len(),
252+
size,
253+
).unwrap();
254+
size
255+
}
256+
Ok(size) => size,
257+
Err(err) => {
258+
let size = *ROM_SIZES.iter().find(|&&x| x >= rom.len()).unwrap();
259+
writeln!(error, "{}, deducing size from ROM size as {}", err, size,).unwrap();
260+
size
261+
}
262+
};
263+
264+
// Cartridge Type
265+
let mbc_kind = header.cartridge_type;
266+
let kind = match mbc_kind {
267+
0 | 8 | 9 => MbcKind::Mbc0,
268+
1..=3 => 'mbc1: {
269+
// Detect if it is a MBC1M card
270+
if header.rom_size == 5 {
271+
let mut number_of_games = 0;
272+
for i in 0..4 {
273+
let header = match CartridgeHeader::from_bytes(&rom[i * 0x40000..]) {
274+
Ok(x) | Err((Some(x), _)) => x,
275+
Err((None, _)) => continue,
276+
};
277+
if header.check_logo() {
278+
number_of_games += 1;
279+
}
280+
}
281+
// multicarts will have, at least, a game selecion screen, and two other games.
282+
if number_of_games >= 3 {
283+
break 'mbc1 MbcKind::Mbc1M;
284+
}
285+
}
286+
MbcKind::Mbc1
287+
}
288+
5 | 6 => MbcKind::Mbc2,
289+
0x0F..=0x13 => MbcKind::Mbc3,
290+
0x19..=0x1E => MbcKind::Mbc5,
291+
_ => {
292+
writeln!(
293+
error,
294+
"MBC type '{}' ({:02x}) is not supported",
295+
mbc_type_name(mbc_kind),
296+
mbc_kind
297+
)
298+
.unwrap();
299+
return None;
300+
}
301+
};
302+
303+
let ram_size_type = header.ram_size;
304+
305+
let ram_size = if let MbcKind::Mbc2 = kind {
306+
if ram_size_type != 0 {
307+
writeln!(
308+
error,
309+
"Cartridge use MBC2, with a integrated ram (type '00'), but report the ram type '{:02x}'",
310+
ram_size_type,
311+
).unwrap();
312+
}
313+
0x200
314+
} else {
315+
match RAM_SIZES.get(ram_size_type as usize).copied() {
316+
Some(x) => x,
317+
None => {
318+
writeln!(
319+
error,
320+
"Ram size type '{:02x}' is not supported, using RAM size as 0x2000",
321+
ram_size_type,
322+
)
323+
.unwrap();
324+
0x2000
325+
}
326+
}
327+
};
328+
329+
Some(Self {
330+
kind,
331+
rom_size,
332+
ram_size,
333+
})
334+
}
335+
}
336+
169337
#[derive(PartialEq, Eq, Clone)]
170338
pub struct Cartridge {
171339
pub header: CartridgeHeader,
@@ -229,11 +397,32 @@ impl SaveState for Cartridge {
229397
Ok(())
230398
}
231399
}
400+
401+
#[allow(clippy::result_large_err)]
232402
impl Cartridge {
403+
pub fn new(rom: Vec<u8>) -> Result<Self, (String, Option<Self>)> {
404+
Self::new_maybe_with_spec(rom, None)
405+
}
406+
407+
pub fn new_with_spec_str(
408+
rom: Vec<u8>,
409+
spec: Option<&str>,
410+
) -> Result<Self, (String, Option<Self>)> {
411+
Self::new_maybe_with_spec(
412+
rom,
413+
match spec {
414+
None => None,
415+
Some(spec) => Some(MbcSpecification::from_str(spec).map_err(|x| (x, None))?),
416+
},
417+
)
418+
}
419+
233420
/// Create a new cartridge from the given ROM. If the ROM is invalid, return the a error
234421
/// message and a deduced cartridge, if possible.
235-
#[allow(clippy::result_large_err)]
236-
pub fn new(mut rom: Vec<u8>) -> Result<Self, (String, Option<Self>)> {
422+
fn new_maybe_with_spec(
423+
mut rom: Vec<u8>,
424+
spec: Option<MbcSpecification>,
425+
) -> Result<Self, (String, Option<Self>)> {
237426
let mut error = String::new();
238427

239428
let header = match CartridgeHeader::from_bytes(&rom) {
@@ -245,106 +434,34 @@ impl Cartridge {
245434
Err((None, err)) => return Err((err, None)),
246435
};
247436

248-
let rom_size = header
249-
.rom_size_in_bytes()
250-
.ok_or_else(|| format!("Rom size type '{:02x}' is not supported", header.rom_size));
251-
252-
match rom_size {
253-
Ok(rom_size) if rom_size != rom.len() => {
254-
let size = *ROM_SIZES.iter().find(|&&x| x >= rom.len()).unwrap();
255-
writeln!(
256-
&mut error,
257-
"The ROM header expected rom size as '{}' bytes, but the given rom has '{}' bytes. Deducing size from ROM size as {}.",
258-
rom_size,
259-
rom.len(),
260-
size,
261-
).unwrap();
262-
rom.resize(size, 0);
263-
}
264-
Ok(_) => {}
265-
Err(err) => {
266-
let size = *ROM_SIZES.iter().find(|&&x| x >= rom.len()).unwrap();
267-
writeln!(
268-
&mut error,
269-
"{}, deducing size from ROM size as {}",
270-
err, size,
271-
)
272-
.unwrap();
273-
rom.resize(size, 0);
274-
}
437+
let spec = match spec {
438+
Some(spec) => spec,
439+
None => match MbcSpecification::from_header(&header, &mut error, &rom) {
440+
Some(v) => v,
441+
None => return Err((error, None)),
442+
},
275443
};
276444

277-
// Cartridge Type
278-
let mbc_kind = header.cartridge_type;
279-
let mbc = match mbc_kind {
280-
0 | 8 | 9 => Mbc::None(Mbc0 {}),
281-
1..=3 => 'mbc1: {
282-
// Detect if it is a MBC1M card
283-
if header.rom_size == 5 {
284-
let mut number_of_games = 0;
285-
for i in 0..4 {
286-
let header = match CartridgeHeader::from_bytes(&rom[i * 0x40000..]) {
287-
Ok(x) | Err((Some(x), _)) => x,
288-
Err((None, _)) => continue,
289-
};
290-
if header.check_logo() {
291-
number_of_games += 1;
292-
}
293-
}
294-
// multicarts will have, at least, a game selecion screen, and two other games.
295-
if number_of_games >= 3 {
296-
break 'mbc1 Mbc::Mbc1M(Mbc1M::new());
297-
}
298-
}
299-
Mbc::Mbc1(Mbc1::new())
300-
}
301-
5 | 6 => Mbc::Mbc2(Mbc2::new()),
302-
0x0F..=0x13 => Mbc::Mbc3(Mbc3::new()),
303-
0x19..=0x1E => Mbc::Mbc5(Mbc5::new()),
304-
_ => {
305-
writeln!(
306-
&mut error,
307-
"MBC type '{}' ({:02x}) is not supported",
308-
mbc_type_name(mbc_kind),
309-
mbc_kind
310-
)
311-
.unwrap();
312-
return Err((error, None));
313-
}
314-
};
445+
let rom_size = spec.rom_size;
315446

316-
let ram_size_type = header.ram_size;
447+
// resize the rom in case the header expected a different size
448+
rom.resize(rom_size, 0);
317449

318-
let ram_size = if let Mbc::Mbc2(_) = mbc {
319-
if ram_size_type != 0 {
320-
writeln!(
321-
&mut error,
322-
"Cartridge use MBC2, with a integrated ram (type '00'), but report the ram type '{:02x}'",
323-
ram_size_type,
324-
).unwrap();
325-
}
326-
0x200
327-
} else {
328-
match RAM_SIZES.get(ram_size_type as usize).copied() {
329-
Some(x) => x,
330-
None => {
331-
writeln!(
332-
&mut error,
333-
"Ram size type '{:02x}' is not supported, using RAM size as 0x2000",
334-
ram_size_type,
335-
)
336-
.unwrap();
337-
0x2000
338-
}
339-
}
450+
let mbc = match spec.kind {
451+
MbcKind::Mbc0 => Mbc::None(Mbc0 {}),
452+
MbcKind::Mbc1 => Mbc::Mbc1(Mbc1::new()),
453+
MbcKind::Mbc1M => Mbc::Mbc1M(Mbc1M::new()),
454+
MbcKind::Mbc2 => Mbc::Mbc2(Mbc2::new()),
455+
MbcKind::Mbc3 => Mbc::Mbc3(Mbc3::new()),
456+
MbcKind::Mbc5 => Mbc::Mbc5(Mbc5::new()),
340457
};
341458

342459
let cartridge = Self {
343460
header,
344461
lower_bank: 0,
345462
upper_bank: 1,
346463
rom,
347-
ram: vec![0; ram_size],
464+
ram: vec![0; spec.ram_size],
348465
mbc,
349466
};
350467

core/src/parser/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use std::io::{Read, Seek, SeekFrom};
22

3+
mod size;
4+
5+
pub use size::{parse_number, parse_size};
6+
37
fn read_u32(file: &mut impl Read) -> Result<u32, std::io::Error> {
48
let mut value = [0; 4];
59
file.read_exact(&mut value)?;

0 commit comments

Comments
 (0)