neo-decompiler 0.10.1

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 std::collections::HashSet;
use std::fmt::Write;

use crate::instruction::Instruction;
use crate::manifest::ManifestMethod;

use super::super::super::super::helpers::offset_as_usize;
use super::super::super::helpers::{
    collect_csharp_parameters, escape_csharp_string, format_csharp_parameters,
    format_manifest_type_csharp, format_method_signature, sanitize_csharp_identifier,
};
use super::MethodsContext;

pub(super) struct ManifestRenderInfo<'a> {
    pub(super) method: &'a ManifestMethod,
    pub(super) method_name: String,
    pub(super) param_signature: String,
    pub(super) return_type: String,
}

pub(super) fn method_render_info<'a>(
    method: &'a ManifestMethod,
    used_signatures: &mut HashSet<(String, String)>,
) -> ManifestRenderInfo<'a> {
    let params = collect_csharp_parameters(&method.parameters);
    let signature_key = params
        .iter()
        .map(|param| param.ty.as_str())
        .collect::<Vec<_>>()
        .join(",");
    let param_signature = format_csharp_parameters(&params);
    let base_name = sanitize_csharp_identifier(&method.name);
    let method_name = make_unique_method_name(base_name, &signature_key, used_signatures);
    let return_type = format_manifest_type_csharp(&method.return_type, true);

    ManifestRenderInfo {
        method,
        method_name,
        param_signature,
        return_type,
    }
}

pub(super) fn write_method_open(output: &mut String, info: &ManifestRenderInfo<'_>) {
    let signature =
        format_method_signature(&info.method_name, &info.param_signature, &info.return_type);
    write_method_attributes(
        output,
        &info.method_name,
        &info.method.name,
        info.method.safe,
    );
    writeln!(output, "        {signature}").unwrap();
    writeln!(output, "        {{").unwrap();
}

pub(super) fn write_method_close(output: &mut String) {
    writeln!(output, "        }}").unwrap();
    writeln!(output).unwrap();
}

pub(super) fn instruction_slice(
    instructions: &[Instruction],
    start: usize,
    end: Option<usize>,
) -> Vec<Instruction> {
    match end {
        Some(end) => instructions
            .iter()
            .filter(|ins| ins.offset >= start && ins.offset < end)
            .cloned()
            .collect(),
        None => instructions.to_vec(),
    }
}

pub(super) fn non_empty_or_all(
    slice: Vec<Instruction>,
    instructions: &[Instruction],
) -> Vec<Instruction> {
    if slice.is_empty() {
        instructions.to_vec()
    } else {
        slice
    }
}

pub(super) fn format_inferred_parameters(
    arg_count: usize,
    method_offset: usize,
    context: &MethodsContext<'_>,
    typed: bool,
    fallback_type: &str,
) -> String {
    let slot_types = typed
        .then(|| {
            context
                .body_context
                .slot_types_by_offset
                .get(&method_offset)
        })
        .flatten();
    (0..arg_count)
        .map(|index| {
            let ty = slot_types
                .and_then(|types| types.argument_type(index))
                .unwrap_or(fallback_type);
            format!("{ty} arg{index}")
        })
        .collect::<Vec<_>>()
        .join(", ")
}

pub(super) fn synthetic_entry_arguments(
    slice: &[Instruction],
    entry_offset: usize,
    context: &MethodsContext<'_>,
    typed: bool,
) -> (String, Vec<String>) {
    let arg_count =
        crate::decompiler::helpers::initslot_argument_count_at(slice, entry_offset).unwrap_or(0);
    let parameters = format_inferred_parameters(arg_count, entry_offset, context, typed, "object");
    let labels: Vec<String> = (0..arg_count).map(|i| format!("arg{i}")).collect();
    (parameters, labels)
}

pub(super) fn method_offset(method: &ManifestMethod) -> usize {
    offset_as_usize(method.offset).unwrap_or(0)
}

fn write_method_attributes(output: &mut String, method_name: &str, raw_name: &str, is_safe: bool) {
    if method_name != raw_name {
        writeln!(
            output,
            "        [DisplayName(\"{}\")]",
            escape_csharp_string(raw_name)
        )
        .unwrap();
    }
    if is_safe {
        writeln!(output, "        [Safe]").unwrap();
    }
}

fn make_unique_method_name(
    base: String,
    signature: &str,
    used: &mut HashSet<(String, String)>,
) -> String {
    let mut candidate = base.clone();
    let mut index = 1usize;
    while !used.insert((candidate.clone(), signature.to_string())) {
        candidate = format!("{base}_{index}");
        index += 1;
    }
    candidate
}