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 super::expressions::csharpize_expression;
use super::syntax::split_top_level_args;

/// Wrap a bare decimal integer literal that exceeds C#'s `ulong` range in
/// `BigInteger.Parse("...")`.
pub(super) fn match_big_integer_literal(s: &str) -> Option<(String, usize)> {
    let bytes = s.as_bytes();
    if bytes.is_empty() || !bytes[0].is_ascii_digit() {
        return None;
    }
    let mut j = 0;
    while j < bytes.len() && bytes[j].is_ascii_digit() {
        j += 1;
    }
    if j < bytes.len() {
        let after = bytes[j];
        if after == b'x'
            || after == b'X'
            || after.is_ascii_alphabetic()
            || after == b'_'
            || after == b'.'
        {
            return None;
        }
    }
    let digits = &s[..j];
    if !decimal_exceeds_u64(digits) {
        return None;
    }
    Some((format!("BigInteger.Parse(\"{digits}\")"), j))
}

/// Rewrite a non-empty PACKMAP literal `Map(k1: v1, k2: v2)` into a C# map
/// collection initializer.
pub(super) fn match_map_literal(rest: &str) -> Option<(String, usize)> {
    let inner = rest.strip_prefix("Map(")?;
    let close = matching_paren(inner)?;
    let body = inner[..close].trim();
    if body.is_empty() || body.contains("/*") {
        return None;
    }
    let mut entries = Vec::new();
    for entry in split_top_level_args(body) {
        let (key, value) = split_top_level_colon(entry)?;
        entries.push(format!(
            "[{}] = {}",
            csharpize_expression(key.trim()),
            csharpize_expression(value.trim())
        ));
    }
    let consumed = "Map(".len() + close + 1;
    Some((
        format!("new Map<object, object> {{ {} }}", entries.join(", ")),
        consumed,
    ))
}

fn matching_paren(s: &str) -> Option<usize> {
    let bytes = s.as_bytes();
    let mut depth = 0i32;
    let mut in_string: Option<u8> = None;
    let mut i = 0;
    while i < bytes.len() {
        let b = bytes[i];
        if let Some(quote) = in_string {
            if b == b'\\' && i + 1 < bytes.len() {
                i += 2;
                continue;
            }
            if b == quote {
                in_string = None;
            }
            i += 1;
            continue;
        }
        match b {
            b'"' | b'\'' => in_string = Some(b),
            b')' if depth == 0 => return Some(i),
            b'(' | b'[' | b'{' => depth += 1,
            b')' | b']' | b'}' => depth -= 1,
            _ => {}
        }
        i += 1;
    }
    None
}

fn split_top_level_colon(entry: &str) -> Option<(&str, &str)> {
    let bytes = entry.as_bytes();
    let mut depth = 0i32;
    let mut in_string: Option<u8> = None;
    let mut i = 0;
    while i < bytes.len() {
        let b = bytes[i];
        if let Some(quote) = in_string {
            if b == b'\\' && i + 1 < bytes.len() {
                i += 2;
                continue;
            }
            if b == quote {
                in_string = None;
            }
            i += 1;
            continue;
        }
        match b {
            b'"' | b'\'' => in_string = Some(b),
            b'(' | b'[' | b'{' => depth += 1,
            b')' | b']' | b'}' => depth -= 1,
            b':' if depth == 0 => return Some((&entry[..i], &entry[i + 1..])),
            _ => {}
        }
        i += 1;
    }
    None
}

/// Rewrite an oversized `0x...` hex blob into a C# `new byte[] { ... }`
/// literal.
pub(super) fn match_big_byte_literal(s: &str) -> Option<(String, usize)> {
    let bytes = s.as_bytes();
    if bytes.len() < 2 || bytes[0] != b'0' || !(bytes[1] == b'x' || bytes[1] == b'X') {
        return None;
    }
    let mut j = 2;
    while j < bytes.len() && bytes[j].is_ascii_hexdigit() {
        j += 1;
    }
    let hex = &s[2..j];
    if hex.len() <= 16 || hex.len() % 2 != 0 {
        return None;
    }
    if j < bytes.len() && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_') {
        return None;
    }
    let mut rendered = String::from("new byte[] { ");
    for (idx, pair) in hex.as_bytes().chunks_exact(2).enumerate() {
        if idx > 0 {
            rendered.push_str(", ");
        }
        rendered.push_str("0x");
        rendered.push(pair[0] as char);
        rendered.push(pair[1] as char);
    }
    rendered.push_str(" }");
    Some((rendered, j))
}

fn decimal_exceeds_u64(digits: &str) -> bool {
    const U64_MAX: &str = "18446744073709551615";
    let trimmed = digits.trim_start_matches('0');
    let significant = if trimmed.is_empty() { "0" } else { trimmed };
    match significant.len().cmp(&U64_MAX.len()) {
        std::cmp::Ordering::Greater => true,
        std::cmp::Ordering::Less => false,
        std::cmp::Ordering::Equal => significant > U64_MAX,
    }
}