extern crate alloc;
use crate::ffi::{DyHook, SFHook};
use anyhow::anyhow;
use log::{debug, error, warn};
use std::cell::UnsafeCell;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::process::exit;
use std::ptr::null_mut;
use std::sync::Arc;
mod ffi;
pub type DynarmicContext = Arc<DynarmicContextInner>;
#[derive(Clone)]
pub struct DynarmicContextInner {
inner_context: *mut c_void,
}
impl DynarmicContextInner {
pub fn destroy(&self) {
unsafe {
ffi::dynarmic_context_free(self.inner_context);
}
}
}
impl Drop for DynarmicContextInner {
fn drop(&mut self) {
self.destroy();
}
}
unsafe impl Send for DynarmicContextInner {}
unsafe impl Sync for DynarmicContextInner {}
pub type DynarmicContext32 = Arc<DynarmicContext32Inner>;
#[derive(Clone)]
pub struct DynarmicContext32Inner {
inner_context: *mut c_void,
}
impl DynarmicContext32Inner {
pub fn destroy(&self) {
unsafe {
ffi::dynarmic_context32_free(self.inner_context);
}
}
}
impl Drop for DynarmicContext32Inner {
fn drop(&mut self) {
self.destroy();
}
}
unsafe impl Send for DynarmicContext32Inner {}
unsafe impl Sync for DynarmicContext32Inner {}
pub fn dynarmic_version() -> u32 {
unsafe { ffi::dynarmic_version() }
}
pub fn dynarmic_colorful_egg() -> String {
unsafe {
let c_str = ffi::dynarmic_colorful_egg();
if c_str.is_null() {
return String::new();
}
let c_str = std::ffi::CStr::from_ptr(c_str);
c_str.to_string_lossy().into_owned()
}
}
struct Metadata<'a> {
svc_callback: Option<Box<dyn SFHook + 'a>>,
unmapped_mem_callback: Option<Box<dyn SFHook + 'a>>,
until: u64,
_memory: *mut c_void,
_monitor: *mut c_void,
_page_table: *mut *mut c_void,
handle: *mut c_void,
}
impl Drop for Metadata<'_> {
fn drop(&mut self) {
log::info!("[dynarmic] Dropping Metadata");
unsafe {
ffi::dynarmic_destroy(self.handle);
}
}
}
unsafe impl Send for Metadata<'_> {}
unsafe impl Sync for Metadata<'_> {}
#[derive(Clone)]
pub struct Dynarmic<'a, T: Clone + Send + Sync> {
cur_handle: *mut c_void,
metadata: Arc<UnsafeCell<Metadata<'a>>>,
pd: PhantomData<&'a T>,
}
unsafe impl<'a, T: Clone + Send + Sync> Send for Dynarmic<'a, T> {}
unsafe impl<'a, T: Clone + Send + Sync> Sync for Dynarmic<'a, T> {}
impl<'a, T: Clone + Send + Sync> Dynarmic<'a, T> {
pub fn new() -> Dynarmic<'static, T> {
let memory = unsafe { ffi::dynarmic_init_memory() };
if memory == null_mut() {
error!("Failed to initialize memory");
exit(0)
}
let mut jit_size = std::env::var("DYNARMIC_JIT_SIZE")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(64);
if jit_size < 8 {
warn!("JIT size {}MB is too small, setting to 8MB", jit_size);
jit_size = 8;
} else if jit_size > 512 {
warn!("JIT size {}MB is too large, setting to 512MB", jit_size);
jit_size = 512;
}
let monitor = unsafe { ffi::dynarmic_init_monitor(1) };
let page_table = unsafe { ffi::dynarmic_init_page_table() };
let handle = unsafe {
ffi::dynarmic_new(0, memory, monitor, page_table, jit_size * 1024 * 1024, true)
};
debug!(
"[Dynarmic] Created new Dynarmic instance: {:X} with {}MB JIT",
handle as usize, jit_size
);
Dynarmic {
cur_handle: handle,
metadata: Arc::new(UnsafeCell::new(Metadata {
svc_callback: None,
unmapped_mem_callback: None,
until: 0,
_memory: memory,
_monitor: monitor,
_page_table: page_table,
handle,
})),
pd: PhantomData,
}
}
pub fn new_a32() -> Dynarmic<'static, T> {
let memory = unsafe { ffi::dynarmic_init_memory() };
if memory == null_mut() {
error!("Failed to initialize memory");
exit(0)
}
let mut jit_size = std::env::var("DYNARMIC_JIT_SIZE")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(64);
if jit_size < 8 {
warn!("JIT size {}MB is too small, setting to 8MB", jit_size);
jit_size = 8;
} else if jit_size > 512 {
warn!("JIT size {}MB is too large, setting to 512MB", jit_size);
jit_size = 512;
}
let monitor = unsafe { ffi::dynarmic_init_monitor(1) };
let page_table = unsafe { ffi::dynarmic_init_page_table() };
let handle = unsafe {
ffi::dynarmic_new_a32(
0,
memory,
monitor,
page_table,
jit_size * 1024 * 1024,
true,
null_mut(),
)
};
debug!(
"[Dynarmic] Created new Dynarmic A32 instance: {:X} with {}MB JIT",
handle as usize, jit_size
);
Dynarmic {
cur_handle: handle,
metadata: Arc::new(UnsafeCell::new(Metadata {
svc_callback: None,
unmapped_mem_callback: None,
until: 0,
_memory: memory,
_monitor: monitor,
_page_table: page_table,
handle,
})),
pd: PhantomData,
}
}
pub fn emu_start(&self, pc: u64, until: u64) -> anyhow::Result<()> {
unsafe {
debug!("[Dynarmic] Starting emulator: pc=0x{:x}", pc);
(*self.metadata.get()).until = until + 4;
let ret = ffi::dynarmic_emu_start(self.cur_handle, pc);
if ret != 0 {
return Err(anyhow!("Failed to start emulator: code={}", ret));
}
Ok(())
}
}
pub fn emu_stop(&self) -> anyhow::Result<()> {
unsafe {
debug!("[Dynarmic] Stopping emulator");
let ret = ffi::dynarmic_emu_stop(self.cur_handle);
if ret != 0 {
return Err(anyhow!("Failed to stop emulator: code={}", ret));
}
Ok(())
}
}
pub fn get_cache_size(&self) -> u64 {
unsafe { ffi::dynarmic_get_cache_size(self.cur_handle) }
}
pub fn context_alloc(&self) -> DynarmicContext {
unsafe {
let inner_context = ffi::dynarmic_context_alloc();
Arc::new(DynarmicContextInner { inner_context })
}
}
pub fn context_save(&self, context: &mut DynarmicContext) -> anyhow::Result<()> {
unsafe {
let ret = ffi::dynarmic_context_save(self.cur_handle, context.inner_context);
if ret != 0 {
return Err(anyhow!("Failed to save context: code={}", ret));
}
Ok(())
}
}
pub fn context_restore(&self, context: &DynarmicContext) -> anyhow::Result<()> {
unsafe {
let ret = ffi::dynarmic_context_restore(self.cur_handle, context.inner_context);
if ret != 0 {
return Err(anyhow!("Failed to restore context: code={}", ret));
}
Ok(())
}
}
pub fn context32_alloc(&self) -> DynarmicContext32 {
unsafe {
let inner_context = ffi::dynarmic_context32_alloc();
Arc::new(DynarmicContext32Inner { inner_context })
}
}
pub fn context32_save(&self, context: &mut DynarmicContext32) -> anyhow::Result<()> {
unsafe {
let ret = ffi::dynarmic_context32_save(self.cur_handle, context.inner_context);
if ret != 0 {
return Err(anyhow!("Failed to save A32 context: code={}", ret));
}
Ok(())
}
}
pub fn context32_restore(&self, context: &DynarmicContext32) -> anyhow::Result<()> {
unsafe {
let ret = ffi::dynarmic_context32_restore(self.cur_handle, context.inner_context);
if ret != 0 {
return Err(anyhow!("Failed to restore A32 context: code={}", ret));
}
Ok(())
}
}
pub fn mem_map(&self, addr: u64, size: usize, prot: u32) -> anyhow::Result<()> {
unsafe {
debug!(
"[Dynarmic] Mapping memory: addr=0x{:x}, size=0x{:x}, prot={}",
addr, size, prot
);
let ret =
ffi::dynarmic_mmap(self.cur_handle, addr, size as u64, u32::cast_signed(prot));
if ret == 4 {
warn!("Replace mmap?");
}
if ret != 0 {
return Err(anyhow!("Failed to map memory: code={}", ret));
}
debug!(
"[Dynarmic] Mapped memory: addr=0x{:x}, size=0x{:x}, prot={}",
addr, size, prot
);
Ok(())
}
}
pub fn mem_map_ptr(
&self,
addr: u64,
size: usize,
prot: u32,
ptr: *mut c_void,
) -> anyhow::Result<()> {
unsafe {
debug!(
"[Dynarmic] Mapping memory ptr: addr=0x{:x}, size=0x{:x}, prot={}, ptr={:?}",
addr, size, prot, ptr
);
let ret = ffi::dynarmic_mem_map_ptr(
self.cur_handle,
addr,
size as u64,
u32::cast_signed(prot),
ptr,
);
if ret == 4 {
warn!("Replace mmap?");
}
if ret != 0 {
return Err(anyhow!("Failed to map memory ptr: code={}", ret));
}
debug!(
"[Dynarmic] Mapped memory ptr: addr=0x{:x}, size=0x{:x}, prot={}",
addr, size, prot
);
Ok(())
}
}
pub fn mem_unmap(&self, addr: u64, size: usize) -> anyhow::Result<()> {
unsafe {
debug!(
"[Dynarmic] Unmapping memory: addr=0x{:x}, size=0x{:x}",
addr, size
);
let ret = ffi::dynarmic_munmap(self.cur_handle, addr, size as u64);
if ret != 0 {
return Err(anyhow!("Failed to unmap memory: code={}", ret));
}
Ok(())
}
}
pub fn mem_protect(&self, addr: u64, size: usize, prot: u32) -> anyhow::Result<()> {
unsafe {
debug!(
"[Dynarmic] Protecting memory: addr=0x{:x}, size=0x{:x}, prot={}",
addr, size, prot
);
let ret = ffi::dynarmic_mem_protect(
self.cur_handle,
addr,
size as u64,
u32::cast_signed(prot),
);
if ret != 0 {
return Err(anyhow!("Failed to protect memory: code={}", ret));
}
Ok(())
}
}
pub fn reg_read(&self, index: usize) -> anyhow::Result<u64> {
unsafe { Ok(ffi::reg_read(self.cur_handle, index as u64)) }
}
pub fn reg_read_lr(&self) -> anyhow::Result<u64> {
unsafe { Ok(ffi::reg_read(self.cur_handle, 30)) }
}
pub fn reg_read_nzcv(&self) -> anyhow::Result<u64> {
unsafe { Ok(ffi::reg_read_nzcv(self.cur_handle)) }
}
pub fn reg_read_sp(&self) -> anyhow::Result<u64> {
unsafe { Ok(ffi::reg_read_sp(self.cur_handle)) }
}
pub fn reg_read_tpidr_el0(&self) -> anyhow::Result<u64> {
unsafe { Ok(ffi::reg_read_tpidr_el0(self.cur_handle)) }
}
pub fn reg_read_pc(&self) -> anyhow::Result<u64> {
unsafe { Ok(ffi::reg_read_pc(self.cur_handle)) }
}
pub fn reg_write_pc(&self, value: u64) -> anyhow::Result<()> {
unsafe {
debug!("[Dynarmic] Writing PC: value=0x{:x}", value);
let ret = ffi::reg_write_pc(self.cur_handle, value);
if ret != 0 {
return Err(anyhow!("Failed to write PC: code={}", ret));
}
Ok(())
}
}
pub fn reg_write_sp(&self, value: u64) -> anyhow::Result<()> {
unsafe {
debug!("[Dynarmic] Writing SP: value=0x{:x}", value);
let ret = ffi::reg_write_sp(self.cur_handle, value);
if ret != 0 {
return Err(anyhow!("Failed to write SP: code={}", ret));
}
Ok(())
}
}
pub fn reg_write_lr(&self, value: u64) -> anyhow::Result<()> {
unsafe {
debug!("[Dynarmic] Writing LR: value=0x{:x}", value);
let ret = ffi::reg_write(self.cur_handle, 30, value);
if ret != 0 {
return Err(anyhow!("Failed to write LR: code={}", ret));
}
Ok(())
}
}
pub fn reg_write_tpidr_el0(&self, value: u64) -> anyhow::Result<()> {
unsafe {
debug!("[Dynarmic] Writing TPIDR_EL0: value=0x{:x}", value);
let ret = ffi::reg_write_tpidr_el0(self.cur_handle, value);
if ret != 0 {
return Err(anyhow!("Failed to write TPIDR_EL0: code={}", ret));
}
Ok(())
}
}
pub fn reg_write_tpidrr0_el0(&self, value: u64) -> anyhow::Result<()> {
unsafe {
ffi::reg_write_tpidr_el0(self.cur_handle, value);
}
Ok(())
}
pub fn reg_write_nzcv(&self, value: u64) -> anyhow::Result<()> {
unsafe {
debug!("[Dynarmic] Writing NZCV: value=0x{:x}", value);
let ret = ffi::reg_write_nzcv(self.cur_handle, value);
if ret != 0 {
return Err(anyhow!("Failed to write NZCV: code={}", ret));
}
Ok(())
}
}
pub fn reg_write_raw(&self, index: usize, value: u64) -> anyhow::Result<()> {
unsafe {
debug!(
"[Dynarmic] Writing register: index={}, value=0x{:x}",
index, value
);
let ret = ffi::reg_write(self.cur_handle, index as u64, value);
if ret != 0 {
return Err(anyhow!("Failed to write register: code={}", ret));
}
Ok(())
}
}
pub fn reg_write_c13_c0_3(&self, value: u32) -> anyhow::Result<()> {
unsafe {
debug!("[Dynarmic] Writing CP15 c13 0 3: value=0x{:x}", value);
let ret = ffi::reg_write_c13_c0_3(self.cur_handle, value);
if ret != 0 {
return Err(anyhow!("Failed to write CP15 c13 0 3: code={}", ret));
}
Ok(())
}
}
pub fn reg_read_c13_c0_3(&self) -> anyhow::Result<u32> {
unsafe { Ok(ffi::reg_read_c13_c0_3(self.cur_handle)) }
}
pub fn reg_write_r(&self, index: u32, value: u32) -> anyhow::Result<()> {
unsafe {
let ret = ffi::reg_write_r(self.cur_handle, index, value);
if ret != 0 {
return Err(anyhow!("Failed to write R register: code={}", ret));
}
Ok(())
}
}
pub fn reg_read_r(&self, index: u32) -> anyhow::Result<u32> {
unsafe { Ok(ffi::reg_read_r(self.cur_handle, index)) }
}
pub fn reg_write_cpsr(&self, value: u32) -> anyhow::Result<()> {
unsafe {
let ret = ffi::reg_write_cpsr(self.cur_handle, value);
if ret != 0 {
return Err(anyhow!("Failed to write CPSR: code={}", ret));
}
Ok(())
}
}
pub fn reg_read_cpsr(&self) -> anyhow::Result<u32> {
unsafe { Ok(ffi::reg_read_cpsr(self.cur_handle)) }
}
pub fn mem_read_c_string(&self, mut addr: u64) -> anyhow::Result<String> {
let mut buf = Vec::new();
let mut byte = [0u8];
loop {
self.mem_read(addr, &mut byte)?;
if byte[0] == 0 {
break;
}
buf.push(byte[0]);
addr += 1;
}
unsafe { Ok(String::from_utf8_unchecked(buf)) }
}
pub fn mem_read(&self, addr: u64, dest: &mut [u8]) -> anyhow::Result<()> {
unsafe {
debug!(
"[Dynarmic] Reading memory: addr=0x{:x}, size=0x{:x}",
addr,
dest.len()
);
let ret = ffi::dynarmic_mem_read(
self.cur_handle,
addr,
dest.as_mut_ptr() as *mut _,
dest.len(),
);
if ret != 0 {
return Err(anyhow!("Failed to read memory: code={}", ret));
}
Ok(())
}
}
pub fn mem_read_as_vec(&self, addr: u64, size: usize) -> anyhow::Result<Vec<u8>> {
let mut buf = vec![0; size];
self.mem_read(addr, &mut buf)?;
Ok(buf)
}
pub fn mem_write(&self, addr: u64, value: &[u8]) -> anyhow::Result<()> {
unsafe {
debug!(
"[Dynarmic] Writing memory: addr=0x{:x}, size=0x{:x}",
addr,
value.len()
);
let ret = ffi::dynarmic_mem_write(
self.cur_handle,
addr,
value.as_ptr() as *const _,
value.len(),
);
if ret != 0 {
return Err(anyhow!("Failed to write memory: code={}", ret));
}
Ok(())
}
}
pub fn set_svc_callback<F: 'a>(&self, callback: F)
where
F: FnMut(&Dynarmic<T>, u32, u64, u64) + Send + Sync,
{
debug!("[Dynarmic] Setting SVC callback");
unsafe {
let mut cb = Box::new(DyHook {
callback,
dy: self.clone(),
});
let user_data = cb.as_mut() as *mut _ as *const c_void;
extern "C" fn svc_callback_wrapper<T: Clone + Send + Sync, F>(swi: u32, user_data: *const c_void)
where
F: FnMut(&Dynarmic<T>, u32, u64, u64) + Send + Sync,
{
if swi == 114514 {
return;
}
unsafe {
let cb = &mut *(user_data as *mut DyHook<T, F>);
let dynarmic = &cb.dy;
let pc = ffi::reg_read_pc(dynarmic.cur_handle);
let until = (*dynarmic.metadata.get()).until;
(cb.callback)(dynarmic, swi, until, pc);
}
}
ffi::dynarmic_set_svc_callback(
self.cur_handle,
svc_callback_wrapper::<T, F>,
user_data,
);
(*self.metadata.get()).svc_callback = Some(cb);
}
}
pub fn set_unmapped_mem_callback<F: 'a>(&self, callback: F)
where
F: FnMut(&Dynarmic<T>, u64, usize, u64) -> bool + Send + Sync,
{
debug!("[Dynarmic] Setting unmapped memory callback");
unsafe {
let mut cb = Box::new(DyHook {
callback,
dy: self.clone(),
});
let user_data = cb.as_mut() as *mut _ as *const c_void;
extern "C" fn unmapped_mem_callback_wrapper<T: Clone + Send + Sync, F>(
addr: u64,
size: usize,
value: u64,
user_data: *const c_void,
) -> bool
where
F: FnMut(&Dynarmic<T>, u64, usize, u64) -> bool + Send + Sync,
{
unsafe {
let cb = &mut *(user_data as *mut DyHook<T, F>);
let dynarmic = &cb.dy;
(cb.callback)(dynarmic, addr, size, value)
}
}
ffi::dynarmic_set_unmapped_mem_callback(
self.cur_handle,
unmapped_mem_callback_wrapper::<T, F>,
user_data,
);
(*self.metadata.get()).unmapped_mem_callback = Some(cb);
}
}
pub fn destroy_callback(&self) {
unsafe {
extern "C" fn empty_svc_callback(_: u32, _: *const c_void) {}
extern "C" fn empty_unmapped_callback(
_: u64,
_: usize,
_: u64,
_: *const c_void,
) -> bool {
false
}
ffi::dynarmic_set_svc_callback(self.cur_handle, empty_svc_callback, null_mut());
let callback = (*self.metadata.get()).svc_callback.take();
drop(callback);
ffi::dynarmic_set_unmapped_mem_callback(
self.cur_handle,
empty_unmapped_callback,
null_mut(),
);
let callback = (*self.metadata.get()).unmapped_mem_callback.take();
drop(callback);
}
}
}