Support running/profiling ISX files
This commit is contained in:
parent
db482b5c21
commit
f7f112f7c1
4
build.rs
4
build.rs
|
@ -18,6 +18,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
};
|
};
|
||||||
builder
|
builder
|
||||||
.include(Path::new("shrooms-vb-core/core"))
|
.include(Path::new("shrooms-vb-core/core"))
|
||||||
|
.include(Path::new("shrooms-vb-core/util"))
|
||||||
.opt_level(opt_level)
|
.opt_level(opt_level)
|
||||||
.flag_if_supported("-fno-strict-aliasing")
|
.flag_if_supported("-fno-strict-aliasing")
|
||||||
.define("VB_LITTLE_ENDIAN", None)
|
.define("VB_LITTLE_ENDIAN", None)
|
||||||
|
@ -29,7 +30,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
.define("VB_DIRECT_FRAME", "on_frame")
|
.define("VB_DIRECT_FRAME", "on_frame")
|
||||||
.define("VB_DIRECT_READ", "on_read")
|
.define("VB_DIRECT_READ", "on_read")
|
||||||
.define("VB_DIRECT_WRITE", "on_write")
|
.define("VB_DIRECT_WRITE", "on_write")
|
||||||
|
.define("VBU_REALLOC", "vbu_realloc_shim")
|
||||||
.file(Path::new("shrooms-vb-core/core/vb.c"))
|
.file(Path::new("shrooms-vb-core/core/vb.c"))
|
||||||
|
.file(Path::new("shrooms-vb-core/util/isx.c"))
|
||||||
|
.file(Path::new("shrooms-vb-core/util/vbu.c"))
|
||||||
.compile("vb");
|
.compile("vb");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -33,6 +33,7 @@ mod cart;
|
||||||
mod game_info;
|
mod game_info;
|
||||||
mod inline_stack_map;
|
mod inline_stack_map;
|
||||||
mod shrooms_vb_core;
|
mod shrooms_vb_core;
|
||||||
|
mod shrooms_vb_util;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||||
pub enum SimId {
|
pub enum SimId {
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::emulator::{SimId, game_info::GameInfo};
|
use crate::emulator::{SimId, game_info::GameInfo, shrooms_vb_util::rom_from_isx};
|
||||||
|
|
||||||
pub struct Cart {
|
pub struct Cart {
|
||||||
pub file_path: PathBuf,
|
pub file_path: PathBuf,
|
||||||
|
@ -20,8 +20,9 @@ pub struct Cart {
|
||||||
impl Cart {
|
impl Cart {
|
||||||
pub fn load(file_path: &Path, sim_id: SimId) -> Result<Self> {
|
pub fn load(file_path: &Path, sim_id: SimId) -> Result<Self> {
|
||||||
let rom = fs::read(file_path)?;
|
let rom = fs::read(file_path)?;
|
||||||
let (rom, info) =
|
let (rom, info) = try_parse_isx(file_path, &rom)
|
||||||
try_parse_elf(file_path, &rom).unwrap_or_else(|| (rom, GameInfo::empty(file_path)));
|
.or_else(|| try_parse_elf(file_path, &rom))
|
||||||
|
.unwrap_or_else(|| (rom, GameInfo::empty(file_path)));
|
||||||
|
|
||||||
let mut sram_file = File::options()
|
let mut sram_file = File::options()
|
||||||
.read(true)
|
.read(true)
|
||||||
|
@ -60,6 +61,12 @@ impl Cart {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_parse_isx(file_path: &Path, data: &[u8]) -> Option<(Vec<u8>, GameInfo)> {
|
||||||
|
let rom = rom_from_isx(data)?;
|
||||||
|
let info = GameInfo::from_isx(file_path, data);
|
||||||
|
Some((rom, info))
|
||||||
|
}
|
||||||
|
|
||||||
fn try_parse_elf(file_path: &Path, data: &[u8]) -> Option<(Vec<u8>, GameInfo)> {
|
fn try_parse_elf(file_path: &Path, data: &[u8]) -> Option<(Vec<u8>, GameInfo)> {
|
||||||
use object::read::elf::FileHeader;
|
use object::read::elf::FileHeader;
|
||||||
let program = match object::FileKind::parse(data).ok()? {
|
let program = match object::FileKind::parse(data).ok()? {
|
||||||
|
@ -73,7 +80,7 @@ fn try_parse_elf(file_path: &Path, data: &[u8]) -> Option<(Vec<u8>, GameInfo)> {
|
||||||
}
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
let info = GameInfo::new(file_path, data).unwrap_or_else(|_| GameInfo::empty(file_path));
|
let info = GameInfo::from_elf(file_path, data).unwrap_or_else(|_| GameInfo::empty(file_path));
|
||||||
Some((program, info))
|
Some((program, info))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub struct GameInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameInfo {
|
impl GameInfo {
|
||||||
pub fn new(file_path: &Path, input: &[u8]) -> Result<Self> {
|
pub fn from_elf(file_path: &Path, input: &[u8]) -> Result<Self> {
|
||||||
let file = object::File::parse(input)?;
|
let file = object::File::parse(input)?;
|
||||||
|
|
||||||
let (name, path) = name_and_path(file_path);
|
let (name, path) = name_and_path(file_path);
|
||||||
|
@ -52,6 +52,26 @@ impl GameInfo {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_isx(file_path: &Path, input: &[u8]) -> Self {
|
||||||
|
let (name, path) = name_and_path(file_path);
|
||||||
|
let symbols = extract_isx_symbols(input);
|
||||||
|
let library_info = LibraryInfo {
|
||||||
|
name: name.clone(),
|
||||||
|
debug_name: name,
|
||||||
|
path: path.clone(),
|
||||||
|
debug_path: path,
|
||||||
|
debug_id: DebugId::default(),
|
||||||
|
code_id: None,
|
||||||
|
arch: None,
|
||||||
|
symbol_table: symbols.map(|syms| Arc::new(SymbolTable::new(syms))),
|
||||||
|
};
|
||||||
|
let inline_stack_map = InlineStackMap::empty();
|
||||||
|
Self {
|
||||||
|
library_info,
|
||||||
|
inline_stack_map,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn empty(file_path: &Path) -> Self {
|
pub fn empty(file_path: &Path) -> Self {
|
||||||
let (name, path) = name_and_path(file_path);
|
let (name, path) = name_and_path(file_path);
|
||||||
let library_info = LibraryInfo {
|
let library_info = LibraryInfo {
|
||||||
|
@ -115,6 +135,66 @@ fn build_inline_stack_map(file: object::File) -> Result<InlineStackMap> {
|
||||||
Ok(frames.build())
|
Ok(frames.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_isx_symbols(input: &[u8]) -> Option<Vec<Symbol>> {
|
||||||
|
let mut syms = vec![];
|
||||||
|
let (_, mut buf) = input.split_at_checked(32)?;
|
||||||
|
while !buf.is_empty() {
|
||||||
|
let typ;
|
||||||
|
(typ, buf) = buf.split_first()?;
|
||||||
|
match typ {
|
||||||
|
0x11 => {
|
||||||
|
// Code (Virtual Boy)
|
||||||
|
(_, buf) = buf.split_at_checked(4)?;
|
||||||
|
let len_bytes;
|
||||||
|
(len_bytes, buf) = buf.split_first_chunk()?;
|
||||||
|
let len = u32::from_le_bytes(*len_bytes);
|
||||||
|
(_, buf) = buf.split_at_checked(len as usize)?;
|
||||||
|
}
|
||||||
|
0x13 => {
|
||||||
|
// Range (Virtual Boy)
|
||||||
|
let count_bytes;
|
||||||
|
(count_bytes, buf) = buf.split_first_chunk()?;
|
||||||
|
let count = u16::from_le_bytes(*count_bytes) + 1;
|
||||||
|
(_, buf) = buf.split_at_checked(count as usize * 9)?;
|
||||||
|
}
|
||||||
|
0x14 => {
|
||||||
|
// Symbol (Virtual Boy)
|
||||||
|
let count_bytes;
|
||||||
|
(count_bytes, buf) = buf.split_first_chunk()?;
|
||||||
|
let count = u16::from_le_bytes(*count_bytes) + 1;
|
||||||
|
for _ in 0..count {
|
||||||
|
let name_len;
|
||||||
|
(name_len, buf) = buf.split_first()?;
|
||||||
|
let name_bytes;
|
||||||
|
(name_bytes, buf) = buf.split_at_checked(*name_len as usize)?;
|
||||||
|
(_, buf) = buf.split_at_checked(2)?;
|
||||||
|
let address_bytes;
|
||||||
|
(address_bytes, buf) = buf.split_first_chunk()?;
|
||||||
|
|
||||||
|
let name_str = String::from_utf8_lossy(name_bytes);
|
||||||
|
let address = u32::from_le_bytes(*address_bytes);
|
||||||
|
syms.push(Symbol {
|
||||||
|
address,
|
||||||
|
size: Some(4),
|
||||||
|
name: demangle_any(&name_str),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x20 | 0x21 | 0x22 => {
|
||||||
|
// System (undocumented)
|
||||||
|
let length_bytes;
|
||||||
|
(length_bytes, buf) = buf.split_first_chunk()?;
|
||||||
|
let length = u32::from_le_bytes(*length_bytes);
|
||||||
|
(_, buf) = buf.split_at_checked(length as usize)?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(syms)
|
||||||
|
}
|
||||||
|
|
||||||
type Reader<'a> = gimli::EndianSlice<'a, gimli::RunTimeEndian>;
|
type Reader<'a> = gimli::EndianSlice<'a, gimli::RunTimeEndian>;
|
||||||
|
|
||||||
struct ParseContext<'a> {
|
struct ParseContext<'a> {
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
use std::ffi::c_void;
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
unsafe fn vbu_realloc_shim(ptr: *mut c_void, new_size: usize) -> *mut c_void {
|
||||||
|
if !ptr.is_null() {
|
||||||
|
// not supporting proper realloc because it needs bookkeeping and it's unnecessary
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
let allocation = vec![0u8; new_size].into_boxed_slice();
|
||||||
|
Box::into_raw(allocation).cast()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[link(name = "vb")]
|
||||||
|
unsafe extern "C" {
|
||||||
|
#[link_name = "vbuFromISX"]
|
||||||
|
fn vbu_from_isx(bytes: *const c_void, length: usize, rom_length: *mut usize) -> *mut c_void;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rom_from_isx(bytes: &[u8]) -> Option<Vec<u8>> {
|
||||||
|
if !bytes.starts_with(b"ISX") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut rom_length = 0;
|
||||||
|
let raw_rom =
|
||||||
|
unsafe { vbu_from_isx(bytes.as_ptr().cast(), bytes.len(), &mut rom_length) };
|
||||||
|
if raw_rom.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// SAFETY: the rom was allocated by vbu_realloc_shim, which created it from a Vec<u8>.
|
||||||
|
let rom =
|
||||||
|
unsafe { Vec::from_raw_parts(raw_rom.cast(), rom_length, rom_length) };
|
||||||
|
Some(rom)
|
||||||
|
}
|
Loading…
Reference in New Issue