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"] }
|
gdbstub = { version = "0.1.2", optional = true, features = ["std"] }
|
||||||
ringbuf = "0.2.1"
|
ringbuf = "0.2.1"
|
||||||
|
|
||||||
|
goblin = { version = "0.2", optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_arch="wasm32")'.dependencies]
|
[target.'cfg(target_arch="wasm32")'.dependencies]
|
||||||
instant = { version = "0.1.2", features = [ "wasm-bindgen" ] }
|
instant = { version = "0.1.2", features = [ "wasm-bindgen" ] }
|
||||||
|
|
||||||
|
@ -44,6 +46,7 @@ criterion = "0.3"
|
||||||
default = ["arm7tdmi_dispatch_table"]
|
default = ["arm7tdmi_dispatch_table"]
|
||||||
debugger = ["nom", "rustyline"]
|
debugger = ["nom", "rustyline"]
|
||||||
gdb = ["gdbstub"]
|
gdb = ["gdbstub"]
|
||||||
|
elf_support = ["goblin"]
|
||||||
# Uses lookup tables when executing instructions instead of `match` statements.
|
# Uses lookup tables when executing instructions instead of `match` statements.
|
||||||
# Faster, but consumes more memory.
|
# Faster, but consumes more memory.
|
||||||
arm7tdmi_dispatch_table = []
|
arm7tdmi_dispatch_table = []
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
use std::fs::File;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use memmem::{Searcher, TwoWaySearcher};
|
use memmem::{Searcher, TwoWaySearcher};
|
||||||
use num::FromPrimitive;
|
use num::FromPrimitive;
|
||||||
use zip::ZipArchive;
|
|
||||||
|
|
||||||
use super::super::{GBAError, GBAResult};
|
use super::super::{GBAError, GBAResult};
|
||||||
use super::backup::eeprom::*;
|
use super::backup::eeprom::*;
|
||||||
|
@ -14,7 +11,7 @@ use super::header;
|
||||||
use super::BackupMedia;
|
use super::BackupMedia;
|
||||||
use super::Cartridge;
|
use super::Cartridge;
|
||||||
|
|
||||||
use crate::util::read_bin_file;
|
use super::loader::{load_from_bytes, load_from_file, LoadRom};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GamepakBuilder {
|
pub struct GamepakBuilder {
|
||||||
|
@ -87,11 +84,16 @@ impl GamepakBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(mut self) -> GBAResult<Cartridge> {
|
pub fn build(mut self) -> GBAResult<Cartridge> {
|
||||||
let bytes = if let Some(bytes) = self.bytes {
|
let (bytes, symbols) = if let Some(bytes) = self.bytes {
|
||||||
Ok(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 {
|
} else if let Some(path) = &self.path {
|
||||||
let loaded_rom = load_rom(&path)?;
|
match load_from_file(&path)? {
|
||||||
Ok(loaded_rom.into())
|
LoadRom::Elf { data, symbols } => Ok((data, Some(symbols))),
|
||||||
|
LoadRom::Raw(data) => Ok((data, None)),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(GBAError::CartridgeLoadError(
|
Err(GBAError::CartridgeLoadError(
|
||||||
"either provide file() or buffer()".to_string(),
|
"either provide file() or buffer()".to_string(),
|
||||||
|
@ -125,9 +127,10 @@ impl GamepakBuilder {
|
||||||
let size = bytes.len();
|
let size = bytes.len();
|
||||||
Ok(Cartridge {
|
Ok(Cartridge {
|
||||||
header: header,
|
header: header,
|
||||||
bytes: bytes,
|
bytes: bytes.into_boxed_slice(),
|
||||||
size: size,
|
size: size,
|
||||||
backup: backup,
|
backup: backup,
|
||||||
|
symbols: symbols,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,31 +166,3 @@ fn detect_backup_type(bytes: &[u8]) -> Option<BackupType> {
|
||||||
}
|
}
|
||||||
None
|
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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{Addr, Bus};
|
use super::{Addr, Bus};
|
||||||
|
@ -12,6 +14,7 @@ pub use backup::BackupType;
|
||||||
use backup::{BackupFile, BackupMemoryInterface};
|
use backup::{BackupFile, BackupMemoryInterface};
|
||||||
|
|
||||||
mod builder;
|
mod builder;
|
||||||
|
mod loader;
|
||||||
pub use builder::GamepakBuilder;
|
pub use builder::GamepakBuilder;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
@ -22,14 +25,23 @@ pub enum BackupMedia {
|
||||||
Undetected,
|
Undetected,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type SymbolTable = HashMap<String, u32>;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Cartridge {
|
pub struct Cartridge {
|
||||||
pub header: CartridgeHeader,
|
pub header: CartridgeHeader,
|
||||||
bytes: Box<[u8]>,
|
bytes: Box<[u8]>,
|
||||||
size: usize,
|
size: usize,
|
||||||
|
symbols: Option<SymbolTable>, // TODO move it somewhere else
|
||||||
pub(in crate) backup: BackupMedia,
|
pub(in crate) backup: BackupMedia,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Cartridge {
|
||||||
|
pub fn get_symbols(&self) -> &Option<SymbolTable> {
|
||||||
|
&self.symbols
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use super::sysbus::consts::*;
|
use super::sysbus::consts::*;
|
||||||
|
|
||||||
pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;
|
pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;
|
||||||
|
|
Reference in a new issue