reoxide 0.7.0

Rust-bindings for the ReOxide decompiler extension framework
Documentation
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_INITIALIZATION_FAILED: u32 = 3;
//const DECOMP_ERROR_ARCH_INITIALIZATION_FAILED: u32 = 4;
//const DECOMP_ERROR_FUNCTION_DEF_FAILED: u32 = 5;
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);
        }
    }
}