razgad 0.1.0

A library for decoding, classifying, normalizing, and re-emitting mangled, decorated, and runtime symbol names across a wide spread of compiler, platform, and language ecosystems.
Documentation
use crate::{Confidence, Name, Scheme, Symbol, SymbolKind};

pub fn decode(scheme: Scheme, input: &str) -> Option<Symbol> {
    match scheme {
        Scheme::UnityIl2Cpp => decode_il2cpp(input),
        Scheme::MonoManaged => decode_mono(input),
        _ => None,
    }
}

pub fn detect(input: &str) -> Option<(Scheme, Confidence)> {
    if looks_like_mono(input) {
        return Some((Scheme::MonoManaged, Confidence::Certain));
    }
    if looks_like_il2cpp(input) {
        return Some((Scheme::UnityIl2Cpp, Confidence::High));
    }
    None
}

fn decode_il2cpp(input: &str) -> Option<Symbol> {
    let base = strip_il2cpp_suffix(input)?;
    let split = base.rfind('_')?;
    let owner = &base[..split];
    let method = &base[split + 1..];

    let mut symbol = Symbol::new(Scheme::UnityIl2Cpp, SymbolKind::Method);
    symbol.path = vec![Name::identifier(owner), Name::identifier(method)];
    Some(
        symbol
            .with_display(format!("{owner}::{method}"))
            .with_verbatim(input),
    )
}

fn decode_mono(input: &str) -> Option<Symbol> {
    let (owner, method) = input.split_once("$$")?;
    let mut symbol = Symbol::new(Scheme::MonoManaged, SymbolKind::Method);
    let mut path = owner
        .split('.')
        .filter(|part| !part.is_empty())
        .map(Name::identifier)
        .collect::<Vec<_>>();
    path.push(Name::identifier(method));
    symbol.path = path;
    Some(
        symbol
            .with_display(format!("{owner}::{method}"))
            .with_verbatim(input),
    )
}

fn looks_like_mono(input: &str) -> bool {
    input.contains("$$") && !input.starts_with("P$")
}

fn looks_like_il2cpp(input: &str) -> bool {
    strip_il2cpp_suffix(input).is_some()
}

fn strip_il2cpp_suffix(input: &str) -> Option<&str> {
    let marker = input.rfind("_m")?;
    let suffix = &input[marker + 2..];
    let hex_len = suffix
        .chars()
        .take_while(|ch| ch.is_ascii_hexdigit())
        .count();
    if hex_len < 16 {
        return None;
    }
    let remainder = &suffix[hex_len..];
    if remainder.is_empty() || remainder.starts_with('_') {
        Some(&input[..marker])
    } else {
        None
    }
}