neo-decompiler 0.10.2

Neo N3 NEF decompiler: parse, disassemble, lift bytecode to high-level pseudocode and C# skeletons, with a CLI, JSON reports, and optional WebAssembly bindings.
Documentation
use crate::instruction::{Instruction, OpCode, Operand};

use super::slots::{
    find_resolution_start_index, find_slot_store_before, local_load_index, previous_non_nop_index,
    static_load_index, trace_stack_value_producer_before, SlotDomain,
};

pub(in crate::decompiler::analysis) fn calla_target_from_pusha(
    instructions: &[Instruction],
    index: usize,
) -> Option<usize> {
    let mut cursor = index.checked_sub(1)?;
    loop {
        let prev = instructions.get(cursor)?;
        if prev.opcode == OpCode::Nop {
            cursor = cursor.checked_sub(1)?;
            continue;
        }
        return trace_pointer_target_from_value_source(instructions, cursor);
    }
}

pub(super) fn pusha_absolute_target(instruction: &Instruction) -> Option<usize> {
    // PUSHA's operand is decoded as a signed I32 relative offset (the
    // generated opcode table uses `OperandEncoding::I32`); no u32-to-i32
    // reinterpretation is needed.
    let delta = match instruction.operand {
        Some(Operand::I32(value)) => value as isize,
        _ => return None,
    };
    instruction.offset.checked_add_signed(delta)
}

pub(super) fn resolve_slot_pointer_target(
    instructions: &[Instruction],
    before_index: usize,
    domain: SlotDomain,
) -> Option<usize> {
    let store_index = find_slot_store_before(instructions, before_index, domain)?;
    let source_index = previous_non_nop_index(instructions, store_index.checked_sub(1)?)?;
    trace_pointer_target_from_value_source(instructions, source_index)
}

fn trace_pointer_target_from_value_source(
    instructions: &[Instruction],
    mut source_index: usize,
) -> Option<usize> {
    loop {
        let instruction = instructions.get(source_index)?;
        if instruction.opcode == OpCode::Dup {
            source_index = previous_non_nop_index(instructions, source_index.checked_sub(1)?)?;
            continue;
        }
        if instruction.opcode == OpCode::PushA {
            return pusha_absolute_target(instruction);
        }
        if instruction.opcode == OpCode::Pickitem {
            return resolve_pickitem_pointer_target(instructions, source_index);
        }

        let domain = if let Some(slot) = local_load_index(instruction) {
            SlotDomain::Local(slot)
        } else {
            SlotDomain::Static(static_load_index(instruction)?)
        };

        let store_index = find_slot_store_before(instructions, source_index, domain)?;
        source_index = previous_non_nop_index(instructions, store_index.checked_sub(1)?)?;
    }
}

fn resolve_pickitem_pointer_target(
    instructions: &[Instruction],
    pickitem_index: usize,
) -> Option<usize> {
    let index_source = previous_non_nop_index(instructions, pickitem_index.checked_sub(1)?)?;
    let array_source_index = previous_non_nop_index(instructions, index_source.checked_sub(1)?)?;
    let domain = trace_container_domain_from_value_source(instructions, array_source_index)?;

    let scan_start = match domain {
        SlotDomain::Local(_) => find_resolution_start_index(instructions, pickitem_index),
        SlotDomain::Static(_) => 0,
    };

    let mut resolved_target = None;
    for (index, instruction) in instructions
        .iter()
        .enumerate()
        .take(pickitem_index)
        .skip(scan_start)
    {
        if instruction.opcode != OpCode::Append {
            continue;
        }
        let item_index = trace_stack_value_producer_before(instructions, index, 0)?;
        let array_index = trace_stack_value_producer_before(instructions, index, 1)?;
        let Some(array_domain) =
            trace_container_domain_from_value_source(instructions, array_index)
        else {
            continue;
        };
        if array_domain != domain {
            continue;
        }
        let target = trace_pointer_target_from_value_source(instructions, item_index)?;
        if let Some(existing) = resolved_target {
            if existing != target {
                return None;
            }
        } else {
            resolved_target = Some(target);
        }
    }
    resolved_target
}

fn trace_container_domain_from_value_source(
    instructions: &[Instruction],
    mut source_index: usize,
) -> Option<SlotDomain> {
    loop {
        let instruction = instructions.get(source_index)?;
        if instruction.opcode == OpCode::Dup {
            source_index = previous_non_nop_index(instructions, source_index.checked_sub(1)?)?;
            continue;
        }
        if let Some(slot) = local_load_index(instruction) {
            let domain = SlotDomain::Local(slot);
            let Some(store_index) = find_slot_store_before(instructions, source_index, domain)
            else {
                return Some(domain);
            };
            let source = previous_non_nop_index(instructions, store_index.checked_sub(1)?)?;
            let source_instruction = instructions.get(source)?;
            if source_instruction.opcode == OpCode::Dup {
                source_index = previous_non_nop_index(instructions, source.checked_sub(1)?)?;
                continue;
            }
            if local_load_index(source_instruction).is_some()
                || static_load_index(source_instruction).is_some()
            {
                source_index = source;
                continue;
            }
            return Some(domain);
        }
        if let Some(slot) = static_load_index(instruction) {
            let domain = SlotDomain::Static(slot);
            let Some(store_index) = find_slot_store_before(instructions, source_index, domain)
            else {
                return Some(domain);
            };
            let source = previous_non_nop_index(instructions, store_index.checked_sub(1)?)?;
            let source_instruction = instructions.get(source)?;
            if source_instruction.opcode == OpCode::Dup {
                source_index = previous_non_nop_index(instructions, source.checked_sub(1)?)?;
                continue;
            }
            if local_load_index(source_instruction).is_some()
                || static_load_index(source_instruction).is_some()
            {
                source_index = source;
                continue;
            }
            return Some(domain);
        }
        return None;
    }
}