diff --git a/rustboyadvance-core/Cargo.toml b/rustboyadvance-core/Cargo.toml index 9f0b245..601784f 100644 --- a/rustboyadvance-core/Cargo.toml +++ b/rustboyadvance-core/Cargo.toml @@ -31,6 +31,8 @@ nom = {version = "5.0.0", optional = true} gdbstub = { version = "0.1.2", optional = true, features = ["std"] } ringbuf = "0.2.1" +goblin = { version = "0.2", optional = true } + [target.'cfg(target_arch="wasm32")'.dependencies] instant = { version = "0.1.2", features = [ "wasm-bindgen" ] } @@ -44,6 +46,7 @@ criterion = "0.3" default = ["arm7tdmi_dispatch_table"] debugger = ["nom", "rustyline"] gdb = ["gdbstub"] +elf_support = ["goblin"] # Uses lookup tables when executing instructions instead of `match` statements. # Faster, but consumes more memory. arm7tdmi_dispatch_table = [] diff --git a/rustboyadvance-core/src/core/cartridge/builder.rs b/rustboyadvance-core/src/core/cartridge/builder.rs index 91e8066..1114e84 100644 --- a/rustboyadvance-core/src/core/cartridge/builder.rs +++ b/rustboyadvance-core/src/core/cartridge/builder.rs @@ -1,10 +1,7 @@ -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::*; @@ -14,7 +11,7 @@ use super::header; use super::BackupMedia; use super::Cartridge; -use crate::util::read_bin_file; +use super::loader::{load_from_bytes, load_from_file, LoadRom}; #[derive(Debug)] pub struct GamepakBuilder { @@ -87,11 +84,16 @@ impl GamepakBuilder { } pub fn build(mut self) -> GBAResult { - let bytes = if let Some(bytes) = self.bytes { - Ok(bytes) + let (bytes, symbols) = if let Some(bytes) = self.bytes { + match load_from_bytes(bytes.to_vec())? { + LoadRom::Elf { data, symbols } => Ok((data, Some(symbols))), + LoadRom::Raw(data) => Ok((data, None)), + } } else if let Some(path) = &self.path { - let loaded_rom = load_rom(&path)?; - Ok(loaded_rom.into()) + match load_from_file(&path)? { + LoadRom::Elf { data, symbols } => Ok((data, Some(symbols))), + LoadRom::Raw(data) => Ok((data, None)), + } } else { Err(GBAError::CartridgeLoadError( "either provide file() or buffer()".to_string(), @@ -125,9 +127,10 @@ impl GamepakBuilder { let size = bytes.len(); Ok(Cartridge { header: header, - bytes: bytes, + bytes: bytes.into_boxed_slice(), size: size, backup: backup, + symbols: symbols, }) } } @@ -163,31 +166,3 @@ fn detect_backup_type(bytes: &[u8]) -> Option { } None } - -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/rustboyadvance-core/src/core/cartridge/loader.rs b/rustboyadvance-core/src/core/cartridge/loader.rs new file mode 100644 index 0000000..49d6119 --- /dev/null +++ b/rustboyadvance-core/src/core/cartridge/loader.rs @@ -0,0 +1,134 @@ +use super::super::{GBAError, GBAResult}; +use std::collections::HashMap; +use std::io::prelude::*; +use std::io::Cursor; +use std::path::Path; + +use crate::util::read_bin_file; +use zip::ZipArchive; + +#[cfg(feature = "elf_support")] +use goblin; + +pub enum LoadRom { + Elf { + data: Vec, + symbols: HashMap, + }, + Raw(Vec), +} +type LoadRomResult = GBAResult; + +#[cfg(feature = "elf_support")] +impl From for GBAError { + fn from(err: goblin::error::Error) -> GBAError { + GBAError::CartridgeLoadError(format!("elf parsing error: {}", err)) + } +} + +fn try_load_zip(data: &[u8]) -> LoadRomResult { + let reader = Cursor::new(data); + let mut archive = ZipArchive::new(reader)?; + 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(LoadRom::Raw(buf)); + } + } + Err(GBAError::CartridgeLoadError( + "no .gba files found within the zip archive".to_owned(), + )) +} + +#[cfg(feature = "elf_support")] +fn try_load_elf(elf_bytes: &[u8]) -> LoadRomResult { + const CART_BASE: usize = 0x0800_0000; + + let elf = goblin::elf::Elf::parse(&elf_bytes)?; + + let entry = elf.entry; + if entry != (CART_BASE as u64) { + return Err(GBAError::CartridgeLoadError( + "bad elf entry point, maybe multiboot rom ?".to_owned(), + )); + } + + let mut rom = vec![0; 0x200_0000]; + for phdr in &elf.program_headers { + if phdr.p_type == goblin::elf::program_header::PT_LOAD { + let file_range = phdr.file_range(); + let phys_range = + (phdr.p_paddr as usize)..(phdr.p_paddr as usize + phdr.p_memsz as usize); + let phys_range_adjusted = (phdr.p_paddr as usize - CART_BASE) + ..(phdr.p_paddr as usize + phdr.p_memsz as usize - CART_BASE); + + if phys_range_adjusted.start + (phdr.p_filesz as usize) >= rom.len() { + warn!("ELF: skipping program header {:?}", phdr); + continue; + } + + info!( + "ELF: loading segment phdr: {:?} range {:#x?} vec range {:#x?}", + phdr, file_range, phys_range, + ); + + let src = &elf_bytes[file_range]; + let dst = &mut rom[phys_range_adjusted]; + dst.copy_from_slice(src); + } + } + + let mut symbols = HashMap::new(); + + let strtab = elf.strtab; + for sym in elf.syms.iter() { + if let Some(Ok(name)) = strtab.get(sym.st_name) { + // TODO do I also want to save the symbol size ? + symbols.insert(name.to_owned(), sym.st_value as u32); + } else { + warn!("failed to parse symbol name sym {:?}", sym); + } + } + + Ok(LoadRom::Elf { + data: rom, + symbols: symbols, + }) +} + +pub(super) fn load_from_file(path: &Path) -> LoadRomResult { + let bytes = read_bin_file(path)?; + + match path.extension() { + Some(extension) => match extension.to_str() { + Some("zip") => try_load_zip(&bytes), + #[cfg(feature = "elf_support")] + Some("elf") => try_load_elf(&bytes), + _ => { + warn!("unknown file extension, loading as raw binary file"); + Ok(LoadRom::Raw(bytes)) + } + }, + None => Ok(LoadRom::Raw(bytes)), + } +} + +pub(super) fn load_from_bytes(bytes: Vec) -> LoadRomResult { + // first try as zip + if let Ok(result) = try_load_zip(&bytes) { + return Ok(result); + } + + // else, try as elf + #[cfg(feature = "elf_support")] + { + if let Ok(result) = try_load_elf(&bytes) { + return Ok(result); + } + } + + // if everything else failed, load the rom as raw binary + Ok(LoadRom::Raw(bytes)) +} diff --git a/rustboyadvance-core/src/core/cartridge/mod.rs b/rustboyadvance-core/src/core/cartridge/mod.rs index 8cadc7f..5866a2f 100644 --- a/rustboyadvance-core/src/core/cartridge/mod.rs +++ b/rustboyadvance-core/src/core/cartridge/mod.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; use super::{Addr, Bus}; @@ -12,6 +14,7 @@ pub use backup::BackupType; use backup::{BackupFile, BackupMemoryInterface}; mod builder; +mod loader; pub use builder::GamepakBuilder; #[derive(Serialize, Deserialize, Clone, Debug)] @@ -22,14 +25,23 @@ pub enum BackupMedia { Undetected, } +pub type SymbolTable = HashMap; + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Cartridge { pub header: CartridgeHeader, bytes: Box<[u8]>, size: usize, + symbols: Option, // TODO move it somewhere else pub(in crate) backup: BackupMedia, } +impl Cartridge { + pub fn get_symbols(&self) -> &Option { + &self.symbols + } +} + use super::sysbus::consts::*; pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;