Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c146590fe | ||
|
|
d610e3de14 | ||
|
|
f7f112f7c1 | ||
|
|
db482b5c21 | ||
|
|
3e90d7df6d | ||
|
|
1cab6524a4 | ||
|
|
cd8ebab4bf | ||
|
|
4f681d883e | ||
|
|
a00d89bc36 |
Generated
+1
-1
@@ -2165,7 +2165,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "lemur"
|
||||
version = "0.7.3"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atoi",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ description = "An emulator for the Virtual Boy."
|
||||
repository = "https://git.virtual-boy.com/PVB/lemur"
|
||||
publish = false
|
||||
license = "MIT"
|
||||
version = "0.7.3"
|
||||
version = "0.9.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -41,6 +41,7 @@ ENV PATH="/osxcross/bin:$PATH" \
|
||||
SHROOMS_CFLAGS_x86_64-unknown-linux-gnu="-flto" \
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld" \
|
||||
SHROOMS_CFLAGS_x86_64-pc-windows-msvc="-flto" \
|
||||
CFLAGS_x86_64-pc-windows-msvc="-I/xwin/crt/include -I/xwin/sdk/include/ucrt -I/xwin/sdk/include/um -I/xwin/sdk/include/shared" \
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS="-Lnative=/xwin/crt/lib/x86_64 -Lnative=/xwin/sdk/lib/um/x86_64 -Lnative=/xwin/sdk/lib/ucrt/x86_64 -Clinker-plugin-lto -Clink-arg=-fuse-ld=lld" \
|
||||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER="lld-link-20" \
|
||||
CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER="o64-clang" \
|
||||
|
||||
@@ -18,6 +18,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
};
|
||||
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<dyn Error>> {
|
||||
.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(())
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+15
-7
@@ -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<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 (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<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)> {
|
||||
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<u8>, 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))
|
||||
}
|
||||
|
||||
@@ -86,12 +93,13 @@ fn parse_elf_program<Elf: object::read::elf::FileHeader<Endian = object::Endiann
|
||||
let mut bytes = vec![];
|
||||
let mut pstart = None;
|
||||
for phdr in header.program_headers(endian, data).ok()? {
|
||||
if phdr.p_filesz(endian).into() == 0 {
|
||||
let pma = phdr.p_paddr(endian).into();
|
||||
if pma < 0x07000000 || phdr.p_filesz(endian).into() == 0 {
|
||||
continue;
|
||||
}
|
||||
let start = pstart.unwrap_or(phdr.p_paddr(endian).into());
|
||||
let start = pstart.unwrap_or(pma);
|
||||
pstart = Some(start);
|
||||
bytes.resize((phdr.p_paddr(endian).into() - start) as usize, 0);
|
||||
bytes.resize((pma - start) as usize, 0);
|
||||
let data = phdr.data(endian, data).ok()?;
|
||||
bytes.extend_from_slice(data);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ pub struct 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 (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<InlineStackMap> {
|
||||
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: address & 0x07ffffff,
|
||||
size: Some(4),
|
||||
name: demangle_any(&name_str),
|
||||
});
|
||||
}
|
||||
}
|
||||
0x20..=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> {
|
||||
@@ -173,8 +253,8 @@ fn parse_inline(ctx: &mut ParseContext, node: gimli::EntriesTreeNode<Reader>) ->
|
||||
let name = Arc::new(name);
|
||||
let mut ranges = ctx.dorf.die_ranges(ctx.unit, node.entry())?;
|
||||
while let Some(range) = ranges.next()? {
|
||||
let start = range.begin as u32;
|
||||
let end = range.end as u32;
|
||||
let start = range.begin as u32 & 0x07ffffff;
|
||||
let end = range.end as u32 & 0x07ffffff;
|
||||
ctx.frames.add(start, end, name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ extern "C" fn on_exception(sim: *mut VB, cause: *mut u16) -> c_int {
|
||||
};
|
||||
data.monitor.event = data.monitor.queued_event.take();
|
||||
data.monitor.new_inline_stack = data.monitor.detect_new_inline_stack(pc);
|
||||
data.monitor.queued_event = Some(SimEvent::Interrupt(cause, pc));
|
||||
data.monitor.queued_event = Some(SimEvent::Interrupt(cause, pc & 0x07ffffff));
|
||||
unsafe { vb_set_exception_callback(sim, None) };
|
||||
if data.monitor.event.is_some() || data.monitor.new_inline_stack.is_some() {
|
||||
1
|
||||
@@ -439,7 +439,9 @@ impl EventMonitor {
|
||||
// JAL .+4 is how programs get r31 to a known value for indirect calls
|
||||
// (which we detect later.)
|
||||
// Any other JAL is a function call.
|
||||
return Some(SimEvent::Call(address.wrapping_add_signed(disp)));
|
||||
return Some(SimEvent::Call(
|
||||
address.wrapping_add_signed(disp) & 0x07ffffff,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +455,7 @@ impl EventMonitor {
|
||||
if r31 as u32 == address.wrapping_add(2) {
|
||||
// JMP anywhere else, if r31 points to after the JMP, is an indirect call
|
||||
let target = unsafe { vb_get_program_register(sim, jmp_reg as u32) };
|
||||
return Some(SimEvent::Call(target as u32));
|
||||
return Some(SimEvent::Call(target as u32 & 0x07ffffff));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user