diff --git a/src/core/cartridge/backup/backup_file.rs b/src/core/cartridge/backup/backup_file.rs new file mode 100644 index 0000000..a6eb82f --- /dev/null +++ b/src/core/cartridge/backup/backup_file.rs @@ -0,0 +1,119 @@ +use std::fmt; +use std::fs::{File, OpenOptions}; +use std::io::prelude::*; +use std::io::SeekFrom; +use std::path::PathBuf; + +use serde::de::{self, Deserialize, Deserializer, SeqAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct, Serializer}; + +use super::BackupMemoryInterface; +use crate::util::write_bin_file; + +#[derive(Debug)] +pub struct BackupFile { + size: usize, + path: Option, + file: Option, + buffer: Vec, +} + +impl Clone for BackupFile { + fn clone(&self) -> Self { + BackupFile::new(self.size, self.path.clone()) + } +} + +impl Serialize for BackupFile { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("BackupFile", 2)?; + state.serialize_field("size", &self.size)?; + state.serialize_field("path", &self.path)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for BackupFile { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct BackupFileVisitor; + + impl<'de> Visitor<'de> for BackupFileVisitor { + type Value = BackupFile; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct BackupFile") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + let size = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let path: Option = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(1, &self))?; + Ok(BackupFile::new(size, path)) + } + } + + const FIELDS: &'static [&'static str] = &["size", "path"]; + deserializer.deserialize_struct("BackupFile", FIELDS, BackupFileVisitor) + } +} + +impl BackupFile { + pub fn new(size: usize, path: Option) -> BackupFile { + // TODO handle errors without unwrap + let mut file: Option = None; + let buffer = if let Some(path) = &path { + if !path.is_file() { + write_bin_file(&path, &vec![0xff; size]).unwrap(); + } + + let mut _file = OpenOptions::new() + .read(true) + .write(true) + .open(&path) + .unwrap(); + + let mut buffer = Vec::new(); + _file.read_to_end(&mut buffer).unwrap(); + buffer.resize(size, 0xff); + + file = Some(_file); + + buffer + } else { + vec![0xff; size] + }; + + BackupFile { + size, + path, + file: file, + buffer: buffer, + } + } +} + +impl BackupMemoryInterface for BackupFile { + fn write(&mut self, offset: usize, value: u8) { + self.buffer[offset] = value; + if let Some(file) = &mut self.file { + file.seek(SeekFrom::Start(offset as u64)).unwrap(); + file.write_all(&[value]).unwrap(); + } + } + + fn read(&self, offset: usize) -> u8 { + self.buffer[offset] + } +} diff --git a/src/core/cartridge/backup/flash.rs b/src/core/cartridge/backup/flash.rs index 88944ad..6ea655e 100644 --- a/src/core/cartridge/backup/flash.rs +++ b/src/core/cartridge/backup/flash.rs @@ -1,4 +1,4 @@ -use super::{BackupMemory, BackupMemoryInterface}; +use super::{BackupFile, BackupMemoryInterface}; use num::FromPrimitive; use serde::{Deserialize, Serialize}; @@ -56,7 +56,7 @@ pub struct Flash { mode: FlashMode, bank: usize, - memory: BackupMemory, + memory: BackupFile, } const MACRONIX_64K_CHIP_ID: u16 = 0x1CC2; @@ -66,14 +66,14 @@ const SECTOR_SIZE: usize = 0x1000; const BANK_SIZE: usize = 0x10000; impl Flash { - pub fn new(flash_path: PathBuf, flash_size: FlashSize) -> Flash { + pub fn new(flash_path: Option, flash_size: FlashSize) -> Flash { let chip_id = match flash_size { FlashSize::Flash64k => MACRONIX_64K_CHIP_ID, FlashSize::Flash128k => MACRONIX_128K_CHIP_ID, }; let size: usize = flash_size.into(); - let memory = BackupMemory::new(size, flash_path); + let memory = BackupFile::new(size, flash_path); Flash { chip_id: chip_id, diff --git a/src/core/cartridge/backup/mod.rs b/src/core/cartridge/backup/mod.rs index 90ddbf1..95369cc 100644 --- a/src/core/cartridge/backup/mod.rs +++ b/src/core/cartridge/backup/mod.rs @@ -1,126 +1,21 @@ use std::fmt; -use std::fs::{File, OpenOptions}; -use std::io::prelude::*; -use std::io::SeekFrom; -use std::path::PathBuf; - -use serde::de::{self, Deserialize, Deserializer, SeqAccess, Visitor}; -use serde::ser::{Serialize, SerializeStruct, Serializer}; - -use crate::util::write_bin_file; +mod backup_file; +pub use backup_file::BackupFile; pub mod eeprom; pub mod flash; -pub const BACKUP_FILE_EXT: &'static str = "sav"; - -#[derive(Debug, Primitive, Serialize, Deserialize, Clone)] +#[derive(Debug, Primitive, Serialize, Deserialize, Copy, Clone, PartialEq)] pub enum BackupType { Eeprom = 0, Sram = 1, Flash = 2, Flash512 = 3, Flash1M = 4, + AutoDetect = 5, } pub trait BackupMemoryInterface: Sized + fmt::Debug { fn write(&mut self, offset: usize, value: u8); fn read(&self, offset: usize) -> u8; } - -#[derive(Debug)] -pub struct BackupMemory { - size: usize, - path: PathBuf, - file: File, - buffer: Vec, -} - -impl Clone for BackupMemory { - fn clone(&self) -> Self { - BackupMemory::new(self.size, self.path.clone()) - } -} - -impl Serialize for BackupMemory { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("BackupMemory", 2)?; - state.serialize_field("size", &self.size)?; - state.serialize_field("path", &self.path)?; - state.end() - } -} - -impl<'de> Deserialize<'de> for BackupMemory { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct BackupMemoryVisitor; - - impl<'de> Visitor<'de> for BackupMemoryVisitor { - type Value = BackupMemory; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("struct BackupMemory") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - let size = seq - .next_element()? - .ok_or_else(|| de::Error::invalid_length(0, &self))?; - let path: String = seq - .next_element()? - .ok_or_else(|| de::Error::invalid_length(1, &self))?; - Ok(BackupMemory::new(size, PathBuf::from(path))) - } - } - - const FIELDS: &'static [&'static str] = &["size", "path"]; - deserializer.deserialize_struct("BackupMemory", FIELDS, BackupMemoryVisitor) - } -} - -impl BackupMemory { - pub fn new(size: usize, path: PathBuf) -> BackupMemory { - // TODO handle errors without unwrap - if !path.is_file() { - write_bin_file(&path, &vec![0xff; size]).unwrap(); - }; - - let mut file = OpenOptions::new() - .read(true) - .write(true) - .open(&path) - .unwrap(); - - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).unwrap(); - buffer.resize(size, 0xff); - - BackupMemory { - size, - path, - file: file, - buffer: buffer, - } - } -} - -impl BackupMemoryInterface for BackupMemory { - fn write(&mut self, offset: usize, value: u8) { - self.buffer[offset] = value; - self.file.seek(SeekFrom::Start(offset as u64)).unwrap(); - self.file.write_all(&[value]).unwrap(); - } - - fn read(&self, offset: usize) -> u8 { - self.buffer[offset] - } -} diff --git a/src/core/cartridge/builder.rs b/src/core/cartridge/builder.rs new file mode 100644 index 0000000..d2cf72a --- /dev/null +++ b/src/core/cartridge/builder.rs @@ -0,0 +1,178 @@ +use std::fs::File; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; + +use memmem::{Searcher, TwoWaySearcher}; +use num::FromPrimitive; +use zip::ZipArchive; + +use super::super::{GBAError, GBAResult}; +use super::backup::eeprom::*; +use super::backup::flash::*; +use super::backup::{BackupFile, BackupType}; +use super::header; +use super::BackupMedia; +use super::Cartridge; + +use crate::util::read_bin_file; + +#[derive(Debug)] +pub struct GamepakBuilder { + path: Option, + bytes: Option>, + save_type: BackupType, + create_backup_file: bool, +} + +impl GamepakBuilder { + pub fn new() -> GamepakBuilder { + GamepakBuilder { + save_type: BackupType::AutoDetect, + path: None, + bytes: None, + create_backup_file: true, + } + } + + pub fn buffer(mut self, bytes: &[u8]) -> Self { + self.bytes = Some(bytes.into()); + self + } + + pub fn file(mut self, path: &Path) -> Self { + self.path = Some(path.to_path_buf()); + self + } + + pub fn save_type(mut self, save_type: BackupType) -> Self { + self.save_type = save_type; + self + } + + pub fn with_sram(mut self) -> Self { + self.save_type = BackupType::Sram; + self + } + + pub fn with_flash128k(mut self) -> Self { + self.save_type = BackupType::Flash1M; + self + } + + pub fn with_flash64k(mut self) -> Self { + self.save_type = BackupType::Flash512; + self + } + + pub fn with_eeprom(mut self) -> Self { + self.save_type = BackupType::Eeprom; + self + } + + pub fn without_backup_to_file(mut self) -> Self { + self.create_backup_file = false; + self + } + + pub fn build(mut self) -> GBAResult { + let bytes = if let Some(bytes) = self.bytes { + Ok(bytes) + } else if let Some(path) = &self.path { + let loaded_rom = load_rom(&path)?; + Ok(loaded_rom.into()) + } else { + Err(GBAError::CartridgeLoadError( + "either provide file() or buffer()".to_string(), + )) + }?; + + let header = header::parse(&bytes); + info!("Loaded ROM: {:?}", header); + + if !self.create_backup_file { + self.path = None; + } + + if self.save_type == BackupType::AutoDetect { + let detected = detect_backup_type(&bytes)?; + info!("Detected Backup: {:?}", detected); + + self.save_type = detected; + } + + let backup = create_backup(self.save_type, self.path); + + let size = bytes.len(); + Ok(Cartridge { + header: header, + bytes: bytes, + size: size, + backup: backup, + }) + } +} + +const BACKUP_FILE_EXT: &'static str = "sav"; +fn create_backup(backup_type: BackupType, rom_path: Option) -> BackupMedia { + let backup_path = if let Some(rom_path) = rom_path { + Some(rom_path.with_extension(BACKUP_FILE_EXT)) + } else { + None + }; + match backup_type { + BackupType::Flash | BackupType::Flash512 => { + BackupMedia::Flash(Flash::new(backup_path, FlashSize::Flash64k)) + } + BackupType::Flash1M => BackupMedia::Flash(Flash::new(backup_path, FlashSize::Flash128k)), + BackupType::Sram => BackupMedia::Sram(BackupFile::new(0x8000, backup_path)), + BackupType::Eeprom => { + BackupMedia::Eeprom(SpiController::new(BackupFile::new(0x200, backup_path))) + } + BackupType::AutoDetect => panic!("called create_backup with backup_type==AutoDetect"), + } +} + +fn detect_backup_type(bytes: &[u8]) -> GBAResult { + const ID_STRINGS: &'static [&'static str] = + &["EEPROM", "SRAM", "FLASH_", "FLASH512_", "FLASH1M_"]; + + for i in 0..5 { + let search = TwoWaySearcher::new(ID_STRINGS[i].as_bytes()); + match search.search_in(bytes) { + Some(_) => return Ok(BackupType::from_u8(i as u8).unwrap()), + _ => {} + } + } + warn!("could not detect backup save type"); + return Err(GBAError::CartridgeLoadError( + "could not detect backup save type".to_string(), + )); +} + +fn load_rom(path: &Path) -> GBAResult> { + match path.extension() { + Some(extension) => match extension.to_str() { + Some("zip") => { + let zipfile = File::open(path)?; + let mut archive = ZipArchive::new(zipfile)?; + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + if file.name().ends_with(".gba") { + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + return Ok(buf); + } + } + panic!("no .gba file contained in the zip file"); + } + _ => { + let buf = read_bin_file(path)?; + return Ok(buf); + } + }, + _ => { + let buf = read_bin_file(path)?; + return Ok(buf); + } + } +} diff --git a/src/core/cartridge/header.rs b/src/core/cartridge/header.rs new file mode 100644 index 0000000..883e375 --- /dev/null +++ b/src/core/cartridge/header.rs @@ -0,0 +1,59 @@ +use serde::{Deserialize, Serialize}; +use std::str::from_utf8; + +/// From GBATEK +/// +/// The first 192 bytes at 8000000h-80000BFh in ROM are used as cartridge header. The same header is also used for Multiboot images at 2000000h-20000BFh (plus some additional multiboot entries at 20000C0h and up). +/// +/// Header Overview +/// Address Bytes Expl. +/// 000h 4 ROM Entry Point (32bit ARM branch opcode, eg. "B rom_start") +/// 004h 156 Nintendo Logo (compressed bitmap, required!) +/// 0A0h 12 Game Title (uppercase ascii, max 12 characters) +/// 0ACh 4 Game Code (uppercase ascii, 4 characters) +/// 0B0h 2 Maker Code (uppercase ascii, 2 characters) +/// 0B2h 1 Fixed value (must be 96h, required!) +/// 0B3h 1 Main unit code (00h for current GBA models) +/// 0B4h 1 Device type (usually 00h) (bit7=DACS/debug related) +/// 0B5h 7 Reserved Area (should be zero filled) +/// 0BCh 1 Software version (usually 00h) +/// 0BDh 1 Complement check (header checksum, required!) +/// 0BEh 2 Reserved Area (should be zero filled) +/// --- Additional Multiboot Header Entries --- +/// 0C0h 4 RAM Entry Point (32bit ARM branch opcode, eg. "B ram_start") +/// 0C4h 1 Boot mode (init as 00h - BIOS overwrites this value!) +/// 0C5h 1 Slave ID Number (init as 00h - BIOS overwrites this value!) +/// 0C6h 26 Not used (seems to be unused) +/// 0E0h 4 JOYBUS Entry Pt. (32bit ARM branch opcode, eg. "B joy_start") +/// +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct CartridgeHeader { + // rom_entry_point: Addr, + game_title: String, + game_code: String, + maker_code: String, + software_version: u8, + checksum: u8, + // ram_entry_point: Addr, + // joybus_entry_point: Addr, +} + +pub fn parse(bytes: &[u8]) -> CartridgeHeader { + // let (_, rom_entry_point) = le_u32(bytes).unwrap(); + let game_title = from_utf8(&bytes[0xa0..0xac]).unwrap(); + let game_code = from_utf8(&bytes[0xac..0xb0]).unwrap(); + let maker_code = from_utf8(&bytes[0xb0..0xb2]).unwrap(); + // let (_, ram_entry_point) = le_u32(&bytes[0xc0..]).unwrap(); + // let (_, joybus_entry_point) = le_u32(&bytes[0xc0..]).unwrap(); + + CartridgeHeader { + // rom_entry_point: rom_entry_point, + game_title: String::from(game_title), + game_code: String::from(game_code), + maker_code: String::from(maker_code), + software_version: bytes[0xbc], + checksum: bytes[0xbd], + // ram_entry_point: ram_entry_point, + // joybus_entry_point: joybus_entry_point, + } +} diff --git a/src/core/cartridge/mod.rs b/src/core/cartridge/mod.rs index 106603e..8e96e02 100644 --- a/src/core/cartridge/mod.rs +++ b/src/core/cartridge/mod.rs @@ -1,100 +1,26 @@ -use std::fs::File; -use std::io::prelude::*; -use std::path::{Path, PathBuf}; -use std::str::from_utf8; - -use memmem::{Searcher, TwoWaySearcher}; -use num::FromPrimitive; use serde::{Deserialize, Serialize}; -use zip::ZipArchive; -use super::super::util::read_bin_file; -use super::{Addr, Bus, GBAResult}; +use super::{Addr, Bus}; + +mod header; +use header::CartridgeHeader; mod backup; -use backup::eeprom::*; -use backup::flash::*; -use backup::{BackupMemory, BackupMemoryInterface, BackupType, BACKUP_FILE_EXT}; +use backup::eeprom::SpiController; +use backup::flash::Flash; +use backup::{BackupFile, BackupMemoryInterface}; -/// From GBATEK -/// -/// The first 192 bytes at 8000000h-80000BFh in ROM are used as cartridge header. The same header is also used for Multiboot images at 2000000h-20000BFh (plus some additional multiboot entries at 20000C0h and up). -/// -/// Header Overview -/// Address Bytes Expl. -/// 000h 4 ROM Entry Point (32bit ARM branch opcode, eg. "B rom_start") -/// 004h 156 Nintendo Logo (compressed bitmap, required!) -/// 0A0h 12 Game Title (uppercase ascii, max 12 characters) -/// 0ACh 4 Game Code (uppercase ascii, 4 characters) -/// 0B0h 2 Maker Code (uppercase ascii, 2 characters) -/// 0B2h 1 Fixed value (must be 96h, required!) -/// 0B3h 1 Main unit code (00h for current GBA models) -/// 0B4h 1 Device type (usually 00h) (bit7=DACS/debug related) -/// 0B5h 7 Reserved Area (should be zero filled) -/// 0BCh 1 Software version (usually 00h) -/// 0BDh 1 Complement check (header checksum, required!) -/// 0BEh 2 Reserved Area (should be zero filled) -/// --- Additional Multiboot Header Entries --- -/// 0C0h 4 RAM Entry Point (32bit ARM branch opcode, eg. "B ram_start") -/// 0C4h 1 Boot mode (init as 00h - BIOS overwrites this value!) -/// 0C5h 1 Slave ID Number (init as 00h - BIOS overwrites this value!) -/// 0C6h 26 Not used (seems to be unused) -/// 0E0h 4 JOYBUS Entry Pt. (32bit ARM branch opcode, eg. "B joy_start") -/// -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct CartridgeHeader { - // rom_entry_point: Addr, - game_title: String, - game_code: String, - maker_code: String, - software_version: u8, - checksum: u8, - // ram_entry_point: Addr, - // joybus_entry_point: Addr, -} - -impl CartridgeHeader { - fn parse(bytes: &[u8]) -> CartridgeHeader { - // let (_, rom_entry_point) = le_u32(bytes).unwrap(); - let game_title = from_utf8(&bytes[0xa0..0xac]).unwrap(); - let game_code = from_utf8(&bytes[0xac..0xb0]).unwrap(); - let maker_code = from_utf8(&bytes[0xb0..0xb2]).unwrap(); - // let (_, ram_entry_point) = le_u32(&bytes[0xc0..]).unwrap(); - // let (_, joybus_entry_point) = le_u32(&bytes[0xc0..]).unwrap(); - - CartridgeHeader { - // rom_entry_point: rom_entry_point, - game_title: String::from(game_title), - game_code: String::from(game_code), - maker_code: String::from(maker_code), - software_version: bytes[0xbc], - checksum: bytes[0xbd], - // ram_entry_point: ram_entry_point, - // joybus_entry_point: joybus_entry_point, - } - } -} +mod builder; +pub use builder::GamepakBuilder; #[derive(Serialize, Deserialize, Clone, Debug)] pub enum BackupMedia { - Sram(BackupMemory), + Sram(BackupFile), Flash(Flash), - Eeprom(SpiController), + Eeprom(SpiController), Undetected, } -impl BackupMedia { - pub fn type_string(&self) -> &'static str { - use BackupMedia::*; - match self { - Sram(..) => "SRAM", - Flash(..) => "FLASH", - Eeprom(..) => "EEPROM", - Undetected => "Undetected", - } - } -} - #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Cartridge { pub header: CartridgeHeader, @@ -103,100 +29,6 @@ pub struct Cartridge { pub(in crate) backup: BackupMedia, } -fn load_rom(path: &Path) -> GBAResult> { - match path.extension() { - Some(extension) => match extension.to_str() { - Some("zip") => { - let zipfile = File::open(path)?; - let mut archive = ZipArchive::new(zipfile)?; - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - if file.name().ends_with(".gba") { - let mut buf = Vec::new(); - file.read_to_end(&mut buf)?; - return Ok(buf); - } - } - panic!("no .gba file contained in the zip file"); - } - _ => { - let buf = read_bin_file(path)?; - return Ok(buf); - } - }, - _ => { - let buf = read_bin_file(path)?; - return Ok(buf); - } - } -} - -impl Cartridge { - pub fn from_path(rom_path: &Path) -> GBAResult { - let rom_bin = load_rom(rom_path)?; - Ok(Cartridge::from_bytes( - &rom_bin, - Some(rom_path.to_path_buf()), - )) - } - - pub fn from_bytes(bytes: &[u8], rom_path: Option) -> Cartridge { - let size = bytes.len(); - let header = CartridgeHeader::parse(&bytes); - - let backup = if let Some(path) = rom_path { - create_backup(bytes, &path) - } else { - BackupMedia::Undetected - }; - - info!("Header: {:?}", header); - info!("Backup: {}", backup.type_string()); - - Cartridge { - header: header, - bytes: bytes.into(), - size: size, - backup: backup, - } - } -} - -fn create_backup(bytes: &[u8], rom_path: &Path) -> BackupMedia { - let backup_path = rom_path.with_extension(BACKUP_FILE_EXT); - if let Some(backup_type) = detect_backup_type(bytes) { - match backup_type { - BackupType::Flash | BackupType::Flash512 => { - BackupMedia::Flash(Flash::new(backup_path, FlashSize::Flash64k)) - } - BackupType::Flash1M => { - BackupMedia::Flash(Flash::new(backup_path, FlashSize::Flash128k)) - } - BackupType::Sram => BackupMedia::Sram(BackupMemory::new(0x8000, backup_path)), - BackupType::Eeprom => { - BackupMedia::Eeprom(SpiController::new(BackupMemory::new(0x200, backup_path))) - } - } - } else { - BackupMedia::Undetected - } -} - -fn detect_backup_type(bytes: &[u8]) -> Option { - const ID_STRINGS: &'static [&'static str] = - &["EEPROM", "SRAM", "FLASH_", "FLASH512_", "FLASH1M_"]; - - for i in 0..5 { - let search = TwoWaySearcher::new(ID_STRINGS[i].as_bytes()); - match search.search_in(bytes) { - Some(_) => return Some(BackupType::from_u8(i as u8).unwrap()), - _ => {} - } - } - warn!("could not detect backup type"); - return None; -} - use super::sysbus::consts::*; pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00; diff --git a/src/core/gba.rs b/src/core/gba.rs index 9f52c4c..99f7dde 100644 --- a/src/core/gba.rs +++ b/src/core/gba.rs @@ -191,7 +191,7 @@ mod tests { use super::super::arm7tdmi; use super::super::bus::Bus; - use super::super::cartridge::Cartridge; + use super::super::cartridge::GamepakBuilder; struct DummyInterface {} @@ -208,7 +208,12 @@ mod tests { fn make_mock_gba(rom: &[u8]) -> GameBoyAdvance { let bios = vec![0; 0x4000]; let cpu = arm7tdmi::Core::new(); - let cartridge = Cartridge::from_bytes(rom, None); + let cartridge = GamepakBuilder::new() + .buffer(rom) + .with_sram() + .without_backup_to_file() + .build() + .unwrap(); let dummy = Rc::new(RefCell::new(DummyInterface::new())); let mut gba = GameBoyAdvance::new( cpu, diff --git a/src/core/mod.rs b/src/core/mod.rs index 21d89f3..4117be9 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -23,14 +23,30 @@ use crate::debugger; use zip; +use std::error::Error; +use std::fmt; + #[derive(Debug)] pub enum GBAError { IO(::std::io::Error), CpuError(arm7tdmi::CpuError), + CartridgeLoadError(String), #[cfg(feature = "debugger")] DebuggerError(debugger::DebuggerError), } +impl fmt::Display for GBAError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "error: {:?}", self) + } +} + +impl Error for GBAError { + fn description(&self) -> &str { + "emulator error" + } +} + pub type GBAResult = Result; impl From<::std::io::Error> for GBAError { diff --git a/src/lib.rs b/src/lib.rs index 166a4ef..c4275d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,7 @@ pub trait InputInterface { pub mod prelude { pub use super::core::arm7tdmi; - pub use super::core::cartridge::Cartridge; + pub use super::core::cartridge::GamepakBuilder; pub use super::core::{GBAError, GBAResult, GameBoyAdvance}; #[cfg(feature = "debugger")] pub use super::debugger::Debugger; diff --git a/src/plat/minifb/main.rs b/src/plat/minifb/main.rs index e2a3a59..8e72076 100644 --- a/src/plat/minifb/main.rs +++ b/src/plat/minifb/main.rs @@ -90,7 +90,7 @@ fn main() { let rom_name = rom_path.file_name().unwrap().to_str().unwrap(); let bios_bin = read_bin_file(bios_path).unwrap(); - let cart = Cartridge::from_path(rom_path).unwrap(); + let cart = GamepakBuilder::new().file(rom_path).build().unwrap(); let mut cpu = arm7tdmi::Core::new(); if skip_bios { cpu.skip_bios(); diff --git a/src/plat/sdl2/main.rs b/src/plat/sdl2/main.rs index 41b8fab..231de5f 100644 --- a/src/plat/sdl2/main.rs +++ b/src/plat/sdl2/main.rs @@ -123,12 +123,12 @@ fn main() -> Result<(), Box> { let mut savestate_path = get_savestate_path(&Path::new(&rom_path)); let mut rom_name = Path::new(&rom_path).file_name().unwrap().to_str().unwrap(); - let cart = Cartridge::from_path(Path::new(&rom_path)).unwrap(); + let gamepak = GamepakBuilder::new().file(Path::new(&rom_path)).build()?; let mut gba = GameBoyAdvance::new( arm7tdmi::Core::new(), bios_bin, - cart, + gamepak, video.clone(), audio.clone(), input.clone(), @@ -229,14 +229,14 @@ fn main() -> Result<(), Box> { rom_path = filename; savestate_path = get_savestate_path(&Path::new(&rom_path)); rom_name = Path::new(&rom_path).file_name().unwrap().to_str().unwrap(); - let cart = Cartridge::from_path(Path::new(&rom_path)).unwrap(); + let gamepak = GamepakBuilder::new().file(Path::new(&rom_path)).build()?; let bios_bin = read_bin_file(bios_path).unwrap(); // create a new emulator - TODO, export to a function gba = GameBoyAdvance::new( arm7tdmi::Core::new(), bios_bin, - cart, + gamepak, video.clone(), audio.clone(), input.clone(),