Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
465 changes: 360 additions & 105 deletions Cargo.lock

Large diffs are not rendered by default.

358 changes: 269 additions & 89 deletions core/src/gameboy/cartridge.rs

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions core/src/parser/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use std::io::{Read, Seek, SeekFrom};

mod size;

pub use size::{parse_number, parse_size};

fn read_u32(file: &mut impl Read) -> Result<u32, std::io::Error> {
let mut value = [0; 4];
file.read_exact(&mut value)?;
Expand Down
114 changes: 114 additions & 0 deletions core/src/parser/size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::num::ParseIntError;

#[derive(Debug, PartialEq)]
pub enum ParseSizeError {
InvalidFormat,
InvalidSuffix,
NumberTooLarge,
ParseError(ParseIntError),
}

pub fn parse_number(input: &str) -> Result<u64, ParseIntError> {
if let Some(stripped) = input.strip_prefix("0x") {
u64::from_str_radix(stripped, 16)
} else {
input.parse()
}
}

pub fn parse_size(input: &str) -> Result<u64, ParseSizeError> {
if input.is_empty() {
return Err(ParseSizeError::InvalidFormat);
}

let (num_part, suffix) = input.trim().split_at(
input
.find(|c: char| !c.is_ascii_digit() && c != 'x')
.unwrap_or(input.len()),
);

let num = parse_number(num_part).map_err(ParseSizeError::ParseError)?;

let multiplier = match suffix.trim() {
"B" => 1,
"kB" => 1_024,
"MB" => 1_024 * 1_024,
"GB" => 1_024 * 1_024 * 1_024,
"" => return Err(ParseSizeError::InvalidFormat),
_ => return Err(ParseSizeError::InvalidSuffix),
};

let result = num
.checked_mul(multiplier as u64)
.ok_or(ParseSizeError::NumberTooLarge)?;

if result > u32::MAX as u64 {
return Err(ParseSizeError::NumberTooLarge);
}

Ok(result)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_number_decimal() {
assert_eq!(parse_number("123"), Ok(123));
assert_eq!(parse_number("0"), Ok(0));
}

#[test]
fn test_parse_number_hexadecimal() {
assert_eq!(parse_number("0x10"), Ok(16));
assert_eq!(parse_number("0x1F4"), Ok(500));
}

#[test]
fn test_parse_number_invalid() {
assert!(parse_number("not_a_number").is_err());
assert!(parse_number("0xZZZ").is_err());
}

#[test]
fn test_parse_size_with_hexadecimal() {
assert_eq!(parse_size("0x100B"), Ok(256));
assert_eq!(parse_size("0x1kB"), Ok(1_024));
assert_eq!(parse_size("0x2MB"), Ok(2 * 1_024 * 1_024));
}

#[test]
fn test_valid_sizes() {
assert_eq!(parse_size("0B"), Ok(0));
assert_eq!(parse_size("256B"), Ok(256));
assert_eq!(parse_size("1kB"), Ok(1_024));
assert_eq!(parse_size("2MB"), Ok(2 * 1_024 * 1_024));
assert_eq!(parse_size("1GB"), Ok(1_024 * 1_024 * 1_024));
}

#[test]
fn test_invalid_formats() {
assert_eq!(parse_size(""), Err(ParseSizeError::InvalidFormat));
assert_eq!(parse_size("256"), Err(ParseSizeError::InvalidFormat));
assert_eq!(parse_size("256MBExtra"), Err(ParseSizeError::InvalidSuffix));
}

#[test]
fn test_invalid_suffix() {
assert_eq!(parse_size("256mB"), Err(ParseSizeError::InvalidSuffix));
assert_eq!(parse_size("256TB"), Err(ParseSizeError::InvalidSuffix));
}

#[test]
fn test_parse_errors() {
assert!(matches!(
parse_size("not_a_numberMB"),
Err(ParseSizeError::ParseError(_))
));
assert!(matches!(
parse_size("0xnot_hexB"),
Err(ParseSizeError::ParseError(_))
));
}
}
2 changes: 1 addition & 1 deletion core/tests/check_interrupt_prediction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ fn test_interrupt_prediction(rom: &str, timeout: u64) -> bool {
let rom = std::fs::read(rom_path).unwrap();
let cartridge = match Cartridge::new(rom) {
Ok(x) => x,
Err(x) => {
Err((x, _)) => {
eprintln!("Error reading rom: {}", x);
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion jit/tests/check_jit_compilation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ fn test_interrupt_prediction(rom: &str, timeout: u64) -> bool {
};
let cartridge = match Cartridge::new(rom) {
Ok(x) => x,
Err(x) => {
Err((x, _)) => {
eprintln!("Error reading rom: {}", x);
return true;
}
Expand Down
15 changes: 11 additions & 4 deletions libretro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,16 @@ extern "C" fn retro_load_game(info: Option<&retro_game_info>) -> bool {
log::info!("retro load game");

// load rom data into core
let Some(info) = info else { return false } ;
let Some(info) = info else { return false };

let data = unsafe { std::slice::from_raw_parts(info.data as *const u8, info.size as usize) };
let cartridge = match Cartridge::new(data.to_vec()) {
Ok(x) => x,
Err(err) => {
Ok(rom) => rom,
Err((err, Some(rom))) => {
log::warn!("Warnings when loading rom: {}", err);
rom
}
Err((err, None)) => {
log::error!("Error loading rom: {}", err);
return false;
}
Expand Down Expand Up @@ -371,7 +375,10 @@ impl log::Log for RetroLogger {
let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path) else { return };
.open(path)
else {
return;
};

let _ = writeln!(
&mut file,
Expand Down
9 changes: 8 additions & 1 deletion license/about-android.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
ignore-dev-dependencies = true
ignore-build-dependencies = true
accepted = ["Apache-2.0", "MIT", "Unicode-DFS-2016", "ISC", "BSD-3-Clause"]
accepted = [
"Apache-2.0",
"MIT",
"Unicode-DFS-2016",
"Unicode-3.0",
"ISC",
"BSD-3-Clause",
]

targets = ["aarch64-linux-android"]
1 change: 1 addition & 0 deletions license/about-linux.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ accepted = [
"Apache-2.0",
"MIT",
"Unicode-DFS-2016",
"Unicode-3.0",
"BSD-2-Clause",
"BSD-3-Clause",
"ISC",
Expand Down
1 change: 1 addition & 0 deletions license/about-windows.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ accepted = [
"Apache-2.0",
"MIT",
"Unicode-DFS-2016",
"Unicode-3.0",
"ISC",
"BSD-3-Clause",
"BSL-1.0",
Expand Down
19 changes: 15 additions & 4 deletions native/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::path::PathBuf;

use clap::{ArgAction, Args, Parser, Subcommand};
use gameroy_lib::config::parse_screen_size;
use gameroy_lib::{config, gameroy, rom_loading::load_gameboy, RomFile};
use gameroy_lib::{config, gameroy, rom_loading::load_gameboy_with_spec, RomFile};

mod bench;

Expand All @@ -35,7 +35,7 @@ pub struct Cli {
//
// The disassembly produced follows no particular synxtax, and don't show all instructions or
// data. It only shows instructions that are statically reachable from the entry point.
#[arg(long)]
#[arg(long, requires("rom_path"))]
disassembly: bool,

/// Play the given .vbm file
Expand Down Expand Up @@ -74,6 +74,17 @@ pub struct Cli {
#[arg(long, value_name = "WIDTHxHEIGHT")]
screen_size: Option<String>,

/// The MBC type of the rom
///
/// Overrides the MBC type of the rom, useful in case its is not correctly detected. Must be a
/// string in the format "<type>,<rom_sise>,<ram_size>", where <type> is the MBC type (either
/// "MBC1", "MBC1M", "MBC2", "MBC3" or "MBC5"), <rom_size> is the size of the rom in bytes, and
/// <ram_size> is the size of the ram in bytes. The sizes must be a power of 2 multiple of
/// 0x4000. The size can be in decimal or hexadecimal format, and can have a suffix of "B",
///
#[arg(long)]
mbc: Option<String>,

#[command(subcommand)]
command: Option<Commands>,
}
Expand Down Expand Up @@ -202,7 +213,7 @@ pub fn main() {
Err(e) => return eprintln!("failed to load '{}': {}", rom_path, e),
};

let gb = load_gameboy(rom, None);
let gb = load_gameboy_with_spec(rom, None, args.mbc.as_deref());
let mut gb = match gb {
Ok(x) => x,
Err(e) => return eprintln!("failed to load rom: {}", e),
Expand Down Expand Up @@ -230,7 +241,7 @@ pub fn main() {

let file = RomFile::from_path(PathBuf::from(rom_path));

let gb = load_gameboy(rom, None);
let gb = load_gameboy_with_spec(rom, None, args.mbc.as_deref());
match gb {
Ok(x) => Some((file, x)),
Err(e) => return eprintln!("failed to load rom: {}", e),
Expand Down
18 changes: 17 additions & 1 deletion src/rom_loading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,25 @@ cfg_if::cfg_if! {
}

pub fn load_gameboy(rom: Vec<u8>, ram: Option<Vec<u8>>) -> Result<Box<GameBoy>, String> {
load_gameboy_with_spec(rom, ram, None)
}

pub fn load_gameboy_with_spec(
rom: Vec<u8>,
ram: Option<Vec<u8>>,
spec: Option<&str>,
) -> Result<Box<GameBoy>, String> {
let boot_rom = load_boot_rom();

let mut cartridge = Cartridge::new(rom)?;
let mut cartridge = match Cartridge::new_with_spec_str(rom, spec) {
Ok(rom) => Ok(rom),
Err((warn, Some(rom))) => {
println!("Warning: {}", warn.strip_suffix('\n').unwrap_or(&warn));
log::warn!("{}", warn);
Ok(rom)
}
Err((err, None)) => Err(err),
}?;
log::info!("Cartridge type: {}", cartridge.kind_name());

if let Some(ram) = ram {
Expand Down
Loading