diff --git a/build.rs b/build.rs index 57b8731..befe091 100644 --- a/build.rs +++ b/build.rs @@ -18,6 +18,7 @@ fn main() -> Result<(), Box> { }; builder .include(Path::new("shrooms-vb-core/core")) + .include(Path::new("shrooms-vb-core/util")) .opt_level(opt_level) .flag_if_supported("-fno-strict-aliasing") .define("VB_LITTLE_ENDIAN", None) @@ -29,7 +30,10 @@ fn main() -> Result<(), Box> { .define("VB_DIRECT_FRAME", "on_frame") .define("VB_DIRECT_READ", "on_read") .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/util/isx.c")) + .file(Path::new("shrooms-vb-core/util/vbu.c")) .compile("vb"); Ok(()) diff --git a/src/emulator.rs b/src/emulator.rs index 5c2c91c..072040e 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -33,6 +33,7 @@ mod cart; mod game_info; mod inline_stack_map; mod shrooms_vb_core; +mod shrooms_vb_util; #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] pub enum SimId { diff --git a/src/emulator/cart.rs b/src/emulator/cart.rs index 6bab2a7..4bb6a39 100644 --- a/src/emulator/cart.rs +++ b/src/emulator/cart.rs @@ -7,7 +7,7 @@ use std::{ 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 file_path: PathBuf, @@ -20,8 +20,9 @@ pub struct Cart { impl Cart { pub fn load(file_path: &Path, sim_id: SimId) -> Result { let rom = fs::read(file_path)?; - let (rom, info) = - try_parse_elf(file_path, &rom).unwrap_or_else(|| (rom, GameInfo::empty(file_path))); + let (rom, info) = try_parse_isx(file_path, &rom) + .or_else(|| try_parse_elf(file_path, &rom)) + .unwrap_or_else(|| (rom, GameInfo::empty(file_path))); let mut sram_file = File::options() .read(true) @@ -60,6 +61,12 @@ impl Cart { } } +fn try_parse_isx(file_path: &Path, data: &[u8]) -> Option<(Vec, 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, GameInfo)> { use object::read::elf::FileHeader; let program = match object::FileKind::parse(data).ok()? { @@ -73,7 +80,7 @@ fn try_parse_elf(file_path: &Path, data: &[u8]) -> Option<(Vec, GameInfo)> { } _ => 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)) } diff --git a/src/emulator/game_info.rs b/src/emulator/game_info.rs index 9f72e7c..5c1ff8e 100644 --- a/src/emulator/game_info.rs +++ b/src/emulator/game_info.rs @@ -14,7 +14,7 @@ pub struct GameInfo { } impl GameInfo { - pub fn new(file_path: &Path, input: &[u8]) -> Result { + pub fn from_elf(file_path: &Path, input: &[u8]) -> Result { let file = object::File::parse(input)?; 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 { let (name, path) = name_and_path(file_path); let library_info = LibraryInfo { @@ -115,6 +135,66 @@ fn build_inline_stack_map(file: object::File) -> Result { Ok(frames.build()) } +fn extract_isx_symbols(input: &[u8]) -> Option> { + 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>; struct ParseContext<'a> { diff --git a/src/emulator/shrooms_vb_util.rs b/src/emulator/shrooms_vb_util.rs new file mode 100644 index 0000000..c6a8675 --- /dev/null +++ b/src/emulator/shrooms_vb_util.rs @@ -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> { + 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. + let rom = + unsafe { Vec::from_raw_parts(raw_rom.cast(), rom_length, rom_length) }; + Some(rom) +}