il2cpp_dumper 0.4.1

A blazing fast and reliable il2cpp dumper cross platfrom.
Documentation
use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, IntelFormatter, Mnemonic, OpKind};
use super::DisassembledInstruction;

pub fn disassemble_x86(
    bytes: &[u8],
    base_address: u64,
    max_instructions: usize,
    bitness: u32,
) -> Vec<DisassembledInstruction> {
    let mut result = Vec::with_capacity(max_instructions.min(512));
    let mut decoder = Decoder::with_ip(bitness, bytes, base_address, DecoderOptions::NONE);
    let mut formatter = IntelFormatter::new();

    formatter.options_mut().set_uppercase_mnemonics(true);
    formatter.options_mut().set_space_after_operand_separator(true);
    formatter.options_mut().set_hex_prefix("0x");
    formatter.options_mut().set_hex_suffix("");
    formatter.options_mut().set_branch_leading_zeros(false);

    let mut instruction = Instruction::default();
    let mut output = String::with_capacity(64);
    let mut count = 0;
    let mut seen_ret = false;

    while decoder.can_decode() && count < max_instructions {
        decoder.decode_out(&mut instruction);

        output.clear();
        formatter.format(&instruction, &mut output);

        let (mnemonic_str, operands) = split_mnemonic_operands(&output);

        let is_return = matches!(
            instruction.mnemonic(),
            Mnemonic::Ret | Mnemonic::Retf
        );

        let is_call = matches!(instruction.mnemonic(), Mnemonic::Call);

        let is_jcc = is_jcc_mnemonic(&instruction);

        let is_unconditional_branch = matches!(
            instruction.mnemonic(),
            Mnemonic::Jmp | Mnemonic::Ret | Mnemonic::Retf
        );

        let is_branch = is_unconditional_branch || is_call || is_jcc;

        let call_target = if is_call {
            extract_branch_target(&instruction)
        } else {
            None
        };

        let branch_target = if (is_jcc || is_unconditional_branch) && !is_return {
            extract_branch_target(&instruction)
        } else {
            None
        };

        let condition_code = if is_jcc {
            extract_x86_condition(&instruction)
        } else {
            None
        };

        let memory_offset = extract_memory_offset(&instruction);

        let raw_start = (instruction.ip() - base_address) as usize;
        let raw_end = (raw_start + instruction.len()).min(bytes.len());
        let raw_bytes = if raw_start < bytes.len() {
            bytes[raw_start..raw_end].to_vec()
        } else {
            vec![0; instruction.len()]
        };

        result.push(DisassembledInstruction {
            address: instruction.ip(),
            size: instruction.len(),
            raw_bytes,
            mnemonic: mnemonic_str,
            operands,
            is_call,
            is_return,
            is_branch,
            is_unconditional_branch,
            call_target,
            branch_target,
            condition_code,
            memory_offset,
        });

        count += 1;

        if is_return {
            if seen_ret {
                break;
            }
            seen_ret = true;
        }
    }

    result
}

fn split_mnemonic_operands(text: &str) -> (String, String) {
    let trimmed = text.trim();
    if let Some(pos) = trimmed.find(' ') {
        let mnemonic = trimmed[..pos].to_string();
        let operands = trimmed[pos..].trim().to_string();
        (mnemonic, operands)
    } else {
        (trimmed.to_string(), String::new())
    }
}

fn is_jcc_mnemonic(instruction: &Instruction) -> bool {
    matches!(
        instruction.mnemonic(),
        Mnemonic::Ja | Mnemonic::Jae | Mnemonic::Jb | Mnemonic::Jbe
        | Mnemonic::Je | Mnemonic::Jne | Mnemonic::Jg | Mnemonic::Jge
        | Mnemonic::Jl | Mnemonic::Jle | Mnemonic::Jo | Mnemonic::Jno
        | Mnemonic::Jp | Mnemonic::Jnp | Mnemonic::Js | Mnemonic::Jns
        | Mnemonic::Jecxz | Mnemonic::Jrcxz
        | Mnemonic::Loop | Mnemonic::Loope | Mnemonic::Loopne
    )
}

fn extract_branch_target(instruction: &Instruction) -> Option<u64> {
    if instruction.op_count() >= 1 {
        match instruction.op0_kind() {
            OpKind::NearBranch16 => Some(instruction.near_branch16() as u64),
            OpKind::NearBranch32 => Some(instruction.near_branch32() as u64),
            OpKind::NearBranch64 => Some(instruction.near_branch64()),
            OpKind::FarBranch16 => Some(instruction.far_branch16() as u64),
            OpKind::FarBranch32 => Some(instruction.far_branch32() as u64),
            _ => None,
        }
    } else {
        None
    }
}

fn extract_x86_condition(instruction: &Instruction) -> Option<String> {
    let cond = match instruction.mnemonic() {
        Mnemonic::Je => "==",
        Mnemonic::Jne => "!=",
        Mnemonic::Jg => ">",
        Mnemonic::Jge => ">=",
        Mnemonic::Jl => "<",
        Mnemonic::Jle => "<=",
        Mnemonic::Ja => "> (unsigned)",
        Mnemonic::Jae => ">= (unsigned)",
        Mnemonic::Jb => "< (unsigned)",
        Mnemonic::Jbe => "<= (unsigned)",
        Mnemonic::Jo => "overflow",
        Mnemonic::Jno => "!overflow",
        Mnemonic::Js => "< 0 (sign)",
        Mnemonic::Jns => ">= 0 (sign)",
        Mnemonic::Jp => "parity",
        Mnemonic::Jnp => "!parity",
        Mnemonic::Jecxz | Mnemonic::Jrcxz => "counter == 0",
        Mnemonic::Loop => "counter != 0",
        Mnemonic::Loope => "counter != 0 && ==",
        Mnemonic::Loopne => "counter != 0 && !=",
        _ => return None,
    };
    Some(cond.to_string())
}

fn extract_memory_offset(instruction: &Instruction) -> Option<i64> {
    for i in 0..instruction.op_count() {
        let kind = match i {
            0 => instruction.op0_kind(),
            1 => instruction.op1_kind(),
            2 => instruction.op2_kind(),
            3 => instruction.op3_kind(),
            _ => continue,
        };

        if kind == OpKind::Memory {
            let disp = instruction.memory_displacement64() as i64;
            if disp != 0 {
                return Some(disp);
            }
        }
    }
    None
}