This repository has been archived on 2024-12-14. You can view files and clone it, but cannot push or open issues or pull requests.
rustboyadvance-ng/src/core/cartridge/builder.rs
Michel Heily 6344744414 feat/jni: More work on JNI bindings
Former-commit-id: 125f764bd17ee9add3221cd6b64cde6edb592255
2020-02-28 15:11:02 +02:00

193 lines
5.4 KiB
Rust

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_path: Option<PathBuf>,
save_type: BackupType,
create_backup_file: bool,
}
impl GamepakBuilder {
pub fn new() -> GamepakBuilder {
GamepakBuilder {
save_type: BackupType::AutoDetect,
path: None,
save_path: None,
bytes: None,
create_backup_file: true,
}
}
pub fn take_buffer(mut self, bytes: Box<[u8]>) -> Self {
self.bytes = Some(bytes);
self
}
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_path(mut self, path: &Path) -> Self {
self.save_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.save_path = None;
} else if self.save_path.is_none() {
if let Some(path) = &self.path {
self.save_path = Some(path.with_extension(BACKUP_FILE_EXT));
} else {
warn!("can't create save file as no save path was provided")
}
}
if self.save_type == BackupType::AutoDetect {
if let Some(detected) = detect_backup_type(&bytes) {
info!("Detected Backup: {:?}", detected);
self.save_type = detected;
} else {
warn!("could not detect backup save type");
}
}
let backup = create_backup(self.save_type, self.save_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(EepromController::new(backup_path)),
BackupType::AutoDetect => 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()),
_ => {}
}
}
None
}
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);
}
}
}