use std::{
ffi::CString, marker::PhantomData, mem::MaybeUninit, path::Path, ptr::NonNull, slice,
str::FromStr,
};
mod bindings;
use bindings::*;
opaque!(Architecture);
opaque!(OutputContainer);
const DECOMP_SUCCESS: u32 = 0;
const DECOMP_ERROR_NOT_INITIALIZED: u32 = 1;
const DECOMP_ERROR_ALREADY_INITIALIZED: u32 = 2;
const DECOMP_ERROR_FUNCTION_NOT_DEFINED: u32 = 6;
const DECOMP_ERROR_DECOMP_ANALYSIS_FAILED: u32 = 7;
const DECOMP_ERROR_DECOMP_OUTPUT_FAILED: u32 = 8;
const DECOMP_ERROR_DECOMP_OUTPUT_NONUTF8: u32 = 9;
#[derive(Debug)]
pub enum DecompError {
InitializationError(String),
ContextInitializationError(String),
FunctionDefinitionError(String),
DecompilationError(String),
}
#[repr(C)]
pub struct RawFileContext<'data> {
arch: Option<NonNull<Architecture>>,
data: NonNull<u8>,
data_len: usize,
data_phantom: PhantomData<&'data [u8]>,
}
type DecompResult<T> = Result<T, DecompError>;
pub fn initialize_decompiler(sleigh_home: &Path, enable_reoxide: bool) -> DecompResult<()> {
if !sleigh_home.is_dir() {
return Err(DecompError::InitializationError(
"sleigh_home is not a directory".to_owned(),
));
}
let path_str = sleigh_home
.to_str()
.ok_or(DecompError::InitializationError(
"sleigh_home is not valid UTF-8".to_owned(),
))?;
let path_cstr = CString::from_str(path_str).map_err(|_| {
DecompError::InitializationError("sleigh_home contains nullbytes".to_owned())
})?;
match unsafe { bindings::reoxide_decomp_init(path_cstr.as_ptr(), enable_reoxide as u32) } {
DECOMP_SUCCESS => Ok(()),
DECOMP_ERROR_ALREADY_INITIALIZED => Err(DecompError::InitializationError(
"Decompiler already initialized".to_owned(),
)),
_ => Err(DecompError::InitializationError(
"Decompiler initialization with unspecified error".to_owned(),
)),
}
}
impl<'data> RawFileContext<'data> {
pub fn from_bytes(target_platform: &str, data: &'data [u8]) -> DecompResult<Self> {
let target_cstr = CString::from_str(target_platform).map_err(|_| {
DecompError::ContextInitializationError(
"Target platform identifier contains nullbytes".to_owned(),
)
})?;
unsafe {
let mut ctx = MaybeUninit::uninit();
let result = reoxide_decomp_raw_open_in_memory(
target_cstr.as_ptr(),
data.as_ptr(),
data.len(),
ctx.as_mut_ptr(),
);
match result {
DECOMP_SUCCESS => Ok(ctx.assume_init()),
DECOMP_ERROR_NOT_INITIALIZED => Err(DecompError::ContextInitializationError(
"Decompiler has not been initialized yet".to_owned(),
)),
_ => Err(DecompError::ContextInitializationError(
"Unspecified error happened during context initialization".to_owned(),
)),
}
}
}
pub fn define_function(&mut self, address: u64, name: &str) -> DecompResult<()> {
let name_cstr = CString::from_str(name).map_err(|_| {
DecompError::FunctionDefinitionError("Function name contains nullbytes".to_owned())
})?;
match unsafe { reoxide_decomp_raw_define_function(self, address, name_cstr.as_ptr()) } {
DECOMP_SUCCESS => Ok(()),
_ => Err(DecompError::FunctionDefinitionError(
"Unspecified error happened during function definition".to_owned(),
)),
}
}
pub fn decompile_function(&mut self, address: u64) -> DecompResult<String> {
unsafe {
let mut out = MaybeUninit::<String>::uninit();
let result = reoxide_decomp_raw_decompile_function(
self,
address,
out.as_mut_ptr().cast(),
out_fill_fn,
);
match result {
DECOMP_SUCCESS => Ok(out.assume_init()),
DECOMP_ERROR_FUNCTION_NOT_DEFINED => Err(DecompError::DecompilationError(
"Function to decompile has not been defined/disassembled".to_owned(),
)),
DECOMP_ERROR_DECOMP_ANALYSIS_FAILED=> Err(DecompError::DecompilationError(
"Decompiler analysis failed".to_owned(),
)),
DECOMP_ERROR_DECOMP_OUTPUT_FAILED => Err(DecompError::DecompilationError(
"Decompiler output generation failed".to_owned(),
)),
DECOMP_ERROR_DECOMP_OUTPUT_NONUTF8 => Err(DecompError::DecompilationError(
"Decompiler output was not valid UTF-8".to_owned(),
)),
_ => Err(DecompError::DecompilationError(
"Unspecified error happened during decompilation".to_owned(),
)),
}
}
}
}
extern "C-unwind" fn out_fill_fn(
out: *mut OutputContainer,
data: *const u8,
data_len: usize,
) -> u32 {
let data_slice = unsafe { slice::from_raw_parts(data, data_len) };
let m: &mut MaybeUninit<String> = unsafe { &mut *out.cast() };
match str::from_utf8(data_slice) {
Ok(s) => {
m.write(s.to_owned());
DECOMP_SUCCESS
}
Err(_) => DECOMP_ERROR_DECOMP_OUTPUT_NONUTF8,
}
}
impl<'data> Drop for RawFileContext<'data> {
fn drop(&mut self) {
unsafe {
reoxide_decomp_raw_close(self);
}
}
}