108 lines
3.1 KiB
Rust
108 lines
3.1 KiB
Rust
use anyhow::Result;
|
|
use rand::Rng;
|
|
use std::{
|
|
fs::{self, File},
|
|
io::{Read, Seek as _, SeekFrom, Write as _},
|
|
path::{Path, PathBuf},
|
|
sync::Arc,
|
|
};
|
|
|
|
use crate::emulator::{SimId, game_info::GameInfo};
|
|
|
|
pub struct Cart {
|
|
pub file_path: PathBuf,
|
|
pub rom: Vec<u8>,
|
|
sram_file: File,
|
|
pub sram: Vec<u8>,
|
|
pub info: Arc<GameInfo>,
|
|
}
|
|
|
|
impl Cart {
|
|
pub fn load(file_path: &Path, sim_id: SimId) -> Result<Self> {
|
|
let rom = fs::read(file_path)?;
|
|
let (rom, info) =
|
|
try_parse_elf(file_path, &rom).unwrap_or_else(|| (rom, GameInfo::empty(file_path)));
|
|
|
|
let mut sram_file = File::options()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(false)
|
|
.open(sram_path(file_path, sim_id))?;
|
|
|
|
let sram = if sram_file.metadata()?.len() == 0 {
|
|
// new SRAM file, randomize the contents
|
|
let mut sram = vec![0; 16 * 1024];
|
|
let mut rng = rand::rng();
|
|
for dst in sram.iter_mut().step_by(2) {
|
|
*dst = rng.random();
|
|
}
|
|
sram
|
|
} else {
|
|
let mut sram = Vec::with_capacity(16 * 1024);
|
|
sram_file.read_to_end(&mut sram)?;
|
|
sram
|
|
};
|
|
|
|
Ok(Cart {
|
|
file_path: file_path.to_path_buf(),
|
|
rom,
|
|
sram_file,
|
|
sram,
|
|
info: Arc::new(info),
|
|
})
|
|
}
|
|
|
|
pub fn save_sram(&mut self) -> Result<()> {
|
|
self.sram_file.seek(SeekFrom::Start(0))?;
|
|
self.sram_file.write_all(&self.sram)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn try_parse_elf(file_path: &Path, data: &[u8]) -> Option<(Vec<u8>, GameInfo)> {
|
|
use object::read::elf::FileHeader;
|
|
let program = match object::FileKind::parse(data).ok()? {
|
|
object::FileKind::Elf32 => {
|
|
let header = object::elf::FileHeader32::parse(data).ok()?;
|
|
parse_elf_program(header, data)?
|
|
}
|
|
object::FileKind::Elf64 => {
|
|
let header = object::elf::FileHeader64::parse(data).ok()?;
|
|
parse_elf_program(header, data)?
|
|
}
|
|
_ => return None,
|
|
};
|
|
let info = GameInfo::new(file_path, data).unwrap_or_else(|_| GameInfo::empty(file_path));
|
|
Some((program, info))
|
|
}
|
|
|
|
fn parse_elf_program<Elf: object::read::elf::FileHeader<Endian = object::Endianness>>(
|
|
header: &Elf,
|
|
data: &[u8],
|
|
) -> Option<Vec<u8>> {
|
|
use object::read::elf::ProgramHeader;
|
|
let endian = header.endian().ok()?;
|
|
let mut bytes = vec![];
|
|
let mut pstart = None;
|
|
for phdr in header.program_headers(endian, data).ok()? {
|
|
let pma = phdr.p_paddr(endian).into();
|
|
if pma < 0x07000000 || phdr.p_filesz(endian).into() == 0 {
|
|
continue;
|
|
}
|
|
let start = pstart.unwrap_or(pma);
|
|
pstart = Some(start);
|
|
bytes.resize((pma - start) as usize, 0);
|
|
let data = phdr.data(endian, data).ok()?;
|
|
bytes.extend_from_slice(data);
|
|
}
|
|
Some(bytes)
|
|
}
|
|
|
|
fn sram_path(file_path: &Path, sim_id: SimId) -> PathBuf {
|
|
match sim_id {
|
|
SimId::Player1 => file_path.with_extension("p1.sram"),
|
|
SimId::Player2 => file_path.with_extension("p2.sram"),
|
|
}
|
|
}
|