refactor(core/cartridge): Refactor cartridge module to make it easier to configure savetypes

Former-commit-id: 2e8dd8c3f60c7de8c55bd4c38eaa0630af73c1cc
This commit is contained in:
Michel Heily 2020-01-31 14:24:41 +02:00
parent 3fb75079a2
commit 5fc38546ce
11 changed files with 404 additions and 300 deletions

View file

@ -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<PathBuf>,
file: Option<File>,
buffer: Vec<u8>,
}
impl Clone for BackupFile {
fn clone(&self) -> Self {
BackupFile::new(self.size, self.path.clone())
}
}
impl Serialize for BackupFile {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<V>(self, mut seq: V) -> Result<BackupFile, V::Error>
where
V: SeqAccess<'de>,
{
let size = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let path: Option<PathBuf> = 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<PathBuf>) -> BackupFile {
// TODO handle errors without unwrap
let mut file: Option<File> = 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]
}
}

View file

@ -1,4 +1,4 @@
use super::{BackupMemory, BackupMemoryInterface}; use super::{BackupFile, BackupMemoryInterface};
use num::FromPrimitive; use num::FromPrimitive;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -56,7 +56,7 @@ pub struct Flash {
mode: FlashMode, mode: FlashMode,
bank: usize, bank: usize,
memory: BackupMemory, memory: BackupFile,
} }
const MACRONIX_64K_CHIP_ID: u16 = 0x1CC2; const MACRONIX_64K_CHIP_ID: u16 = 0x1CC2;
@ -66,14 +66,14 @@ const SECTOR_SIZE: usize = 0x1000;
const BANK_SIZE: usize = 0x10000; const BANK_SIZE: usize = 0x10000;
impl Flash { impl Flash {
pub fn new(flash_path: PathBuf, flash_size: FlashSize) -> Flash { pub fn new(flash_path: Option<PathBuf>, flash_size: FlashSize) -> Flash {
let chip_id = match flash_size { let chip_id = match flash_size {
FlashSize::Flash64k => MACRONIX_64K_CHIP_ID, FlashSize::Flash64k => MACRONIX_64K_CHIP_ID,
FlashSize::Flash128k => MACRONIX_128K_CHIP_ID, FlashSize::Flash128k => MACRONIX_128K_CHIP_ID,
}; };
let size: usize = flash_size.into(); let size: usize = flash_size.into();
let memory = BackupMemory::new(size, flash_path); let memory = BackupFile::new(size, flash_path);
Flash { Flash {
chip_id: chip_id, chip_id: chip_id,

View file

@ -1,126 +1,21 @@
use std::fmt; 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 eeprom;
pub mod flash; pub mod flash;
pub const BACKUP_FILE_EXT: &'static str = "sav"; #[derive(Debug, Primitive, Serialize, Deserialize, Copy, Clone, PartialEq)]
#[derive(Debug, Primitive, Serialize, Deserialize, Clone)]
pub enum BackupType { pub enum BackupType {
Eeprom = 0, Eeprom = 0,
Sram = 1, Sram = 1,
Flash = 2, Flash = 2,
Flash512 = 3, Flash512 = 3,
Flash1M = 4, Flash1M = 4,
AutoDetect = 5,
} }
pub trait BackupMemoryInterface: Sized + fmt::Debug { pub trait BackupMemoryInterface: Sized + fmt::Debug {
fn write(&mut self, offset: usize, value: u8); fn write(&mut self, offset: usize, value: u8);
fn read(&self, offset: usize) -> u8; fn read(&self, offset: usize) -> u8;
} }
#[derive(Debug)]
pub struct BackupMemory {
size: usize,
path: PathBuf,
file: File,
buffer: Vec<u8>,
}
impl Clone for BackupMemory {
fn clone(&self) -> Self {
BackupMemory::new(self.size, self.path.clone())
}
}
impl Serialize for BackupMemory {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<V>(self, mut seq: V) -> Result<BackupMemory, V::Error>
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]
}
}

View file

@ -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<PathBuf>,
bytes: Option<Box<[u8]>>,
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<Cartridge> {
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<PathBuf>) -> 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<BackupType> {
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<Vec<u8>> {
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);
}
}
}

View file

@ -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,
}
}

View file

@ -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 serde::{Deserialize, Serialize};
use zip::ZipArchive;
use super::super::util::read_bin_file; use super::{Addr, Bus};
use super::{Addr, Bus, GBAResult};
mod header;
use header::CartridgeHeader;
mod backup; mod backup;
use backup::eeprom::*; use backup::eeprom::SpiController;
use backup::flash::*; use backup::flash::Flash;
use backup::{BackupMemory, BackupMemoryInterface, BackupType, BACKUP_FILE_EXT}; use backup::{BackupFile, BackupMemoryInterface};
/// From GBATEK mod builder;
/// pub use builder::GamepakBuilder;
/// 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,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub enum BackupMedia { pub enum BackupMedia {
Sram(BackupMemory), Sram(BackupFile),
Flash(Flash), Flash(Flash),
Eeprom(SpiController<BackupMemory>), Eeprom(SpiController<BackupFile>),
Undetected, 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)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Cartridge { pub struct Cartridge {
pub header: CartridgeHeader, pub header: CartridgeHeader,
@ -103,100 +29,6 @@ pub struct Cartridge {
pub(in crate) backup: BackupMedia, pub(in crate) backup: BackupMedia,
} }
fn load_rom(path: &Path) -> GBAResult<Vec<u8>> {
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<Cartridge> {
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<PathBuf>) -> 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<BackupType> {
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::*; use super::sysbus::consts::*;
pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00; pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;

View file

@ -191,7 +191,7 @@ mod tests {
use super::super::arm7tdmi; use super::super::arm7tdmi;
use super::super::bus::Bus; use super::super::bus::Bus;
use super::super::cartridge::Cartridge; use super::super::cartridge::GamepakBuilder;
struct DummyInterface {} struct DummyInterface {}
@ -208,7 +208,12 @@ mod tests {
fn make_mock_gba(rom: &[u8]) -> GameBoyAdvance { fn make_mock_gba(rom: &[u8]) -> GameBoyAdvance {
let bios = vec![0; 0x4000]; let bios = vec![0; 0x4000];
let cpu = arm7tdmi::Core::new(); 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 dummy = Rc::new(RefCell::new(DummyInterface::new()));
let mut gba = GameBoyAdvance::new( let mut gba = GameBoyAdvance::new(
cpu, cpu,

View file

@ -23,14 +23,30 @@ use crate::debugger;
use zip; use zip;
use std::error::Error;
use std::fmt;
#[derive(Debug)] #[derive(Debug)]
pub enum GBAError { pub enum GBAError {
IO(::std::io::Error), IO(::std::io::Error),
CpuError(arm7tdmi::CpuError), CpuError(arm7tdmi::CpuError),
CartridgeLoadError(String),
#[cfg(feature = "debugger")] #[cfg(feature = "debugger")]
DebuggerError(debugger::DebuggerError), 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<T> = Result<T, GBAError>; pub type GBAResult<T> = Result<T, GBAError>;
impl From<::std::io::Error> for GBAError { impl From<::std::io::Error> for GBAError {

View file

@ -74,7 +74,7 @@ pub trait InputInterface {
pub mod prelude { pub mod prelude {
pub use super::core::arm7tdmi; pub use super::core::arm7tdmi;
pub use super::core::cartridge::Cartridge; pub use super::core::cartridge::GamepakBuilder;
pub use super::core::{GBAError, GBAResult, GameBoyAdvance}; pub use super::core::{GBAError, GBAResult, GameBoyAdvance};
#[cfg(feature = "debugger")] #[cfg(feature = "debugger")]
pub use super::debugger::Debugger; pub use super::debugger::Debugger;

View file

@ -90,7 +90,7 @@ fn main() {
let rom_name = rom_path.file_name().unwrap().to_str().unwrap(); let rom_name = rom_path.file_name().unwrap().to_str().unwrap();
let bios_bin = read_bin_file(bios_path).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(); let mut cpu = arm7tdmi::Core::new();
if skip_bios { if skip_bios {
cpu.skip_bios(); cpu.skip_bios();

View file

@ -123,12 +123,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut savestate_path = get_savestate_path(&Path::new(&rom_path)); 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 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( let mut gba = GameBoyAdvance::new(
arm7tdmi::Core::new(), arm7tdmi::Core::new(),
bios_bin, bios_bin,
cart, gamepak,
video.clone(), video.clone(),
audio.clone(), audio.clone(),
input.clone(), input.clone(),
@ -229,14 +229,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
rom_path = filename; rom_path = filename;
savestate_path = get_savestate_path(&Path::new(&rom_path)); savestate_path = get_savestate_path(&Path::new(&rom_path));
rom_name = Path::new(&rom_path).file_name().unwrap().to_str().unwrap(); 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(); let bios_bin = read_bin_file(bios_path).unwrap();
// create a new emulator - TODO, export to a function // create a new emulator - TODO, export to a function
gba = GameBoyAdvance::new( gba = GameBoyAdvance::new(
arm7tdmi::Core::new(), arm7tdmi::Core::new(),
bios_bin, bios_bin,
cart, gamepak,
video.clone(), video.clone(),
audio.clone(), audio.clone(),
input.clone(), input.clone(),