Add elf loading support
Former-commit-id: 62edab8ec074c099a23fe9223b35d6043d42fbf6
This commit is contained in:
parent
d8d9fc0651
commit
31161eb5e5
|
@ -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 = []
|
||||
|
|
|
@ -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<Cartridge> {
|
||||
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<BackupType> {
|
|||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
134
rustboyadvance-core/src/core/cartridge/loader.rs
Normal file
134
rustboyadvance-core/src/core/cartridge/loader.rs
Normal file
|
@ -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<u8>,
|
||||
symbols: HashMap<String, u32>,
|
||||
},
|
||||
Raw(Vec<u8>),
|
||||
}
|
||||
type LoadRomResult = GBAResult<LoadRom>;
|
||||
|
||||
#[cfg(feature = "elf_support")]
|
||||
impl From<goblin::error::Error> 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<u8>) -> 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))
|
||||
}
|
|
@ -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<String, u32>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Cartridge {
|
||||
pub header: CartridgeHeader,
|
||||
bytes: Box<[u8]>,
|
||||
size: usize,
|
||||
symbols: Option<SymbolTable>, // TODO move it somewhere else
|
||||
pub(in crate) backup: BackupMedia,
|
||||
}
|
||||
|
||||
impl Cartridge {
|
||||
pub fn get_symbols(&self) -> &Option<SymbolTable> {
|
||||
&self.symbols
|
||||
}
|
||||
}
|
||||
|
||||
use super::sysbus::consts::*;
|
||||
|
||||
pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;
|
||||
|
|
Reference in a new issue