Skip to content

Commit 625334e

Browse files
committed
core: try to read cartridge even if invalid
Will now deduce the size of the rom and ram if they are invalid, and ignore things like the header checksum. In does cases `Cartridge::new` will return a `Err((erro, Some(cartridge)))`. The ui frontned is just printing the error message and using the cartridge, but ideally it would prompt the user for confirmation.
1 parent 68ab3db commit 625334e

File tree

5 files changed

+135
-57
lines changed

5 files changed

+135
-57
lines changed

core/src/gameboy/cartridge.rs

Lines changed: 113 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{convert::TryInto, io::Read};
1+
use std::{convert::TryInto, fmt::Write, io::Read};
22

33
use crate::save_state::{LoadStateError, SaveState, SaveStateContext};
44

@@ -8,6 +8,31 @@ const NINTENDOO_LOGO: [u8; 48] = [
88
0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E,
99
];
1010

11+
// The size of the ROM in bytes, for each type of ROM size.
12+
const ROM_SIZES: &[usize] = &[
13+
2 * 0x4000, // no ROM Banking
14+
4 * 0x4000,
15+
8 * 0x4000,
16+
16 * 0x4000,
17+
32 * 0x4000,
18+
64 * 0x4000,
19+
128 * 0x4000,
20+
256 * 0x4000,
21+
512 * 0x4000,
22+
// 72 * 0x2000,
23+
// 80 * 0x2000,
24+
// 96 * 0x2000,
25+
];
26+
27+
const RAM_SIZES: &[usize] = &[
28+
0,
29+
0x800,
30+
0x2000, // Single Bank
31+
4 * 0x2000,
32+
16 * 0x2000,
33+
8 * 0x2000,
34+
];
35+
1136
fn mbc_type_name(code: u8) -> &'static str {
1237
match code {
1338
0x00 => "ROM ONLY",
@@ -114,27 +139,9 @@ impl CartridgeHeader {
114139
self.logo[..0x18] == NINTENDOO_LOGO[..0x18]
115140
}
116141

117-
pub fn rom_size_in_bytes(&self) -> Result<usize, String> {
118-
let rom_sizes = [
119-
2 * 0x4000, // no ROM Banking
120-
4 * 0x4000,
121-
8 * 0x4000,
122-
16 * 0x4000,
123-
32 * 0x4000,
124-
64 * 0x4000,
125-
128 * 0x4000,
126-
256 * 0x4000,
127-
512 * 0x4000,
128-
// 72 * 0x2000,
129-
// 80 * 0x2000,
130-
// 96 * 0x2000,
131-
];
142+
pub fn rom_size_in_bytes(&self) -> Option<usize> {
132143
let rom_size_type = self.rom_size;
133-
let rom_size = rom_sizes
134-
.get(rom_size_type as usize)
135-
.copied()
136-
.ok_or_else(|| format!("Rom size '{:02x}' is no supported", rom_size_type))?;
137-
Ok(rom_size)
144+
ROM_SIZES.get(rom_size_type as usize).copied()
138145
}
139146

140147
pub fn title_as_string(&self) -> String {
@@ -170,6 +177,21 @@ pub struct Cartridge {
170177
pub ram: Vec<u8>,
171178
mbc: Mbc,
172179
}
180+
181+
impl std::fmt::Debug for Cartridge {
182+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183+
f.debug_struct("Cartridge")
184+
// .field("header", &self.header)
185+
.field("lower_bank", &self.lower_bank)
186+
.field("upper_bank", &self.upper_bank)
187+
.field("rom.len()", &self.rom.len())
188+
.field("ram.len()", &self.ram.len())
189+
// .field("mbc", &self.mbc)
190+
.field("kind_name", &self.kind_name())
191+
.finish()
192+
}
193+
}
194+
173195
impl SaveState for Cartridge {
174196
fn save_state(
175197
&self,
@@ -208,21 +230,49 @@ impl SaveState for Cartridge {
208230
}
209231
}
210232
impl Cartridge {
211-
pub fn new(rom: Vec<u8>) -> Result<Self, String> {
233+
/// Create a new cartridge from the given ROM. If the ROM is invalid, return the a error
234+
/// 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>)> {
237+
let mut error = String::new();
238+
212239
let header = match CartridgeHeader::from_bytes(&rom) {
213-
Ok(x) | Err((Some(x), _)) => x,
214-
Err((None, err)) => return Err(err),
240+
Ok(x) => x,
241+
Err((Some(x), err)) => {
242+
writeln!(&mut error, "{}", err).unwrap();
243+
x
244+
}
245+
Err((None, err)) => return Err((err, None)),
215246
};
216247

217-
let rom_size = header.rom_size_in_bytes()?;
218-
219-
if rom_size != rom.len() {
220-
return Err(format!(
221-
"In the rom header the expected size is '{}' bytes, but the given rom has '{}' bytes",
222-
rom_size,
223-
rom.len()
224-
));
225-
}
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+
}
275+
};
226276

227277
// Cartridge Type
228278
let mbc_kind = header.cartridge_type;
@@ -252,44 +302,57 @@ impl Cartridge {
252302
0x0F..=0x13 => Mbc::Mbc3(Mbc3::new()),
253303
0x19..=0x1E => Mbc::Mbc5(Mbc5::new()),
254304
_ => {
255-
return Err(format!(
305+
writeln!(
306+
&mut error,
256307
"MBC type '{}' ({:02x}) is not supported",
257308
mbc_type_name(mbc_kind),
258309
mbc_kind
259-
))
310+
)
311+
.unwrap();
312+
return Err((error, None));
260313
}
261314
};
262315

263-
let ram_sizes = [
264-
0,
265-
0x800,
266-
0x2000, // Single Bank
267-
4 * 0x2000,
268-
16 * 0x2000,
269-
8 * 0x2000,
270-
];
271316
let ram_size_type = header.ram_size;
272317

273318
let ram_size = if let Mbc::Mbc2(_) = mbc {
274319
if ram_size_type != 0 {
275-
return Err(format!("Cartridge use MBC2, with a integrated ram (type '00'), but report the ram type '{:02x}'", ram_size_type));
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();
276325
}
277326
0x200
278327
} else {
279-
ram_sizes
280-
.get(ram_size_type as usize)
281-
.copied()
282-
.ok_or_else(|| format!("Ram size '{:02x}' is no supported", ram_size_type))?
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+
}
283340
};
284341

285-
Ok(Self {
342+
let cartridge = Self {
286343
header,
287344
lower_bank: 0,
288345
upper_bank: 1,
289346
rom,
290347
ram: vec![0; ram_size],
291348
mbc,
292-
})
349+
};
350+
351+
if !error.is_empty() {
352+
return Err((error, Some(cartridge)));
353+
}
354+
355+
Ok(cartridge)
293356
}
294357

295358
/// A Cartridge filled with HALT instructions. Used as a test cartridge, when the CPU does not

core/tests/check_interrupt_prediction.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ fn test_interrupt_prediction(rom: &str, timeout: u64) -> bool {
100100
let rom = std::fs::read(rom_path).unwrap();
101101
let cartridge = match Cartridge::new(rom) {
102102
Ok(x) => x,
103-
Err(x) => {
103+
Err((x, _)) => {
104104
eprintln!("Error reading rom: {}", x);
105105
return true;
106106
}

jit/tests/check_jit_compilation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ fn test_interrupt_prediction(rom: &str, timeout: u64) -> bool {
162162
};
163163
let cartridge = match Cartridge::new(rom) {
164164
Ok(x) => x,
165-
Err(x) => {
165+
Err((x, _)) => {
166166
eprintln!("Error reading rom: {}", x);
167167
return true;
168168
}

libretro/src/lib.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,16 @@ extern "C" fn retro_load_game(info: Option<&retro_game_info>) -> bool {
174174
log::info!("retro load game");
175175

176176
// load rom data into core
177-
let Some(info) = info else { return false } ;
177+
let Some(info) = info else { return false };
178178

179179
let data = unsafe { std::slice::from_raw_parts(info.data as *const u8, info.size as usize) };
180180
let cartridge = match Cartridge::new(data.to_vec()) {
181-
Ok(x) => x,
182-
Err(err) => {
181+
Ok(rom) => rom,
182+
Err((err, Some(rom))) => {
183+
log::warn!("Warnings when loading rom: {}", err);
184+
rom
185+
}
186+
Err((err, None)) => {
183187
log::error!("Error loading rom: {}", err);
184188
return false;
185189
}
@@ -371,7 +375,10 @@ impl log::Log for RetroLogger {
371375
let Ok(mut file) = std::fs::OpenOptions::new()
372376
.create(true)
373377
.append(true)
374-
.open(path) else { return };
378+
.open(path)
379+
else {
380+
return;
381+
};
375382

376383
let _ = writeln!(
377384
&mut file,

src/rom_loading.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,15 @@ cfg_if::cfg_if! {
2222
pub fn load_gameboy(rom: Vec<u8>, ram: Option<Vec<u8>>) -> Result<Box<GameBoy>, String> {
2323
let boot_rom = load_boot_rom();
2424

25-
let mut cartridge = Cartridge::new(rom)?;
25+
let mut cartridge = match Cartridge::new(rom) {
26+
Ok(rom) => Ok(rom),
27+
Err((warn, Some(rom))) => {
28+
println!("Warning: {}", warn);
29+
log::error!("{}", warn);
30+
Ok(rom)
31+
}
32+
Err((err, None)) => Err(err),
33+
}?;
2634
log::info!("Cartridge type: {}", cartridge.kind_name());
2735

2836
if let Some(ram) = ram {

0 commit comments

Comments
 (0)